WIP
parent
ba6ff51b2f
commit
e3ac9edfe4
@ -1,221 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const expressPromiseRouter = require("express-promise-router");
|
||||
const scryptForHumans = require("scrypt-for-humans");
|
||||
|
||||
const errors = require("../../errors");
|
||||
const normalize = require("../../middlewares/normalize-payload");
|
||||
const validate = require("../../middlewares/validate-payload");
|
||||
const processDeviceId = require("../../process-device-id");
|
||||
const matchValue = require("../../match-value");
|
||||
const canonicalizePhoneNumber = require("../../canonicalize-phone-number");
|
||||
|
||||
function normalizeIdentifier(payload) {
|
||||
if (payload.identifier != null) {
|
||||
/* Never overwrite the `identifier` key if it already exists */
|
||||
return null;
|
||||
} else if (payload.user != null) {
|
||||
return {
|
||||
identifier: {
|
||||
type: "m.id.user",
|
||||
user: payload.user
|
||||
}
|
||||
};
|
||||
} else if (payload.address != null) {
|
||||
return {
|
||||
identifier: {
|
||||
type: "m.id.thirdparty",
|
||||
medium: payload.medium,
|
||||
address: payload.address
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePhoneNumber(payload) {
|
||||
if (payload.identifier != null && payload.identifier.type === "m.id.phone") {
|
||||
return {
|
||||
identifier: {
|
||||
type: "m.id.thirdparty",
|
||||
medium: "msisdn",
|
||||
address: canonicalizePhoneNumber(payload.identifier.country, payload.identifier.phone)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function validateUserIdentifier(userIdentifier) {
|
||||
let {assertProperties, isPresent, isString, isOneOf} = require("../../validate.js");
|
||||
|
||||
assertProperties(userIdentifier, {
|
||||
type: [ isPresent, isOneOf("m.id.user", "m.id.thirdparty", "m.id.phone") ]
|
||||
});
|
||||
|
||||
matchValue(userIdentifier.type, {
|
||||
"m.id.user": () => {
|
||||
assertProperties(userIdentifier, {
|
||||
user: [ isPresent, isString ]
|
||||
});
|
||||
},
|
||||
"m.id.thirdparty": () => {
|
||||
assertProperties(userIdentifier, {
|
||||
medium: [ isPresent, isString ],
|
||||
address: [ isPresent, isString ]
|
||||
});
|
||||
},
|
||||
"m.id.phone": () => {
|
||||
assertProperties(userIdentifier, {
|
||||
country: [ isPresent, isString ],
|
||||
phone: [ isPresent, isString ]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function(state) {
|
||||
const requireAccessToken = require("../../middlewares/require-access-token")(state);
|
||||
|
||||
let {db, configuration, fullyQualifyUser} = state;
|
||||
let router = expressPromiseRouter();
|
||||
|
||||
router.get("/r0/login", (req, res) => {
|
||||
res.json({
|
||||
flows: [{ type: "m.login.password" }]
|
||||
});
|
||||
});
|
||||
|
||||
function validateLoginPayload(payload) {
|
||||
let {assertProperties, isPresent, isString, isOneOf} = require("../../validate.js");
|
||||
|
||||
assertProperties(payload, {
|
||||
type: [ isPresent, isOneOf("m.login.password", "m.login.token") ],
|
||||
device_id: [ isString ],
|
||||
initial_device_display_name: [ isString ],
|
||||
});
|
||||
|
||||
matchValue(payload.type, {
|
||||
"m.login.password": () => {
|
||||
assertProperties(payload, {
|
||||
identifier: [ isPresent, validateUserIdentifier ],
|
||||
password: [ isPresent, isString ]
|
||||
});
|
||||
},
|
||||
"m.login.token": () => {
|
||||
assertProperties(payload, {
|
||||
token: [ isPresent, isString ]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
router.post("/r0/login", normalize([normalizeIdentifier, normalizePhoneNumber]), validate(validateLoginPayload), (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-login
|
||||
// Authenticates the user, and issues an access token they can use to authorize themself in subsequent requests.
|
||||
|
||||
/* FIXME: Rate-limit */
|
||||
|
||||
return Promise.try(() => {
|
||||
if (req.body.identifier.type === "m.id.user") {
|
||||
return db.accounts.byUsername({
|
||||
username: req.body.identifier.user
|
||||
});
|
||||
} else if (req.body.identifier.type === "m.id.thirdparty") {
|
||||
// FIXME
|
||||
} else {
|
||||
/* NOTE: We're not handling m.id.phone here, as that's normalized into m.id.thirdparty in normalizePhoneNumber */
|
||||
throw new errors.Unreachable("Invalid identifier type");
|
||||
}
|
||||
}).then((user) => {
|
||||
let deviceId = processDeviceId(req.body.device_id);
|
||||
|
||||
return Promise.try(() => {
|
||||
if (req.body.type === "m.login.password") {
|
||||
return scryptForHumans.verifyHash(req.body.password, user.password_hash);
|
||||
} else if (req.body.type === "m.login.token") {
|
||||
// FIXME
|
||||
} else {
|
||||
throw new errors.Unreachable("Invalid login type");
|
||||
}
|
||||
}).then(() => {
|
||||
return db.devices.createDeviceSession({
|
||||
name: req.body.initial_device_display_name,
|
||||
deviceId: deviceId,
|
||||
userId: user.id
|
||||
}).then((device) => {
|
||||
res.send({
|
||||
access_token: device.token,
|
||||
user_id: fullyQualifyUser(user.username),
|
||||
home_server: configuration.hostname,
|
||||
device_id: device.deviceId
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch(errors.InvalidUsername, errors.Forbidden.chain("Invalid username specified"))
|
||||
.catch(scryptForHumans.PasswordError, errors.Forbidden.chain("Invalid password specified"));
|
||||
/* FIXME: Formatting above */
|
||||
});
|
||||
|
||||
router.post("/r0/logout", requireAccessToken, (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-logout
|
||||
// Invalidates an existing access token, so that it can no longer be used for authorization.
|
||||
});
|
||||
|
||||
router.post("/r0/logout/all", requireAccessToken, (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-logout-all
|
||||
// Invalidates all access tokens for a user, so that they can no longer be used for authorization. This includes the access token that made this request.
|
||||
});
|
||||
|
||||
router.post("/r0/register/available", (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-register-available
|
||||
// Checks to see if a username is available, and valid, for the server.
|
||||
});
|
||||
|
||||
router.post("/r0/register", (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-register
|
||||
// Register for an account on this homeserver. This API endpoint uses the User-Interactive Authentication API.
|
||||
// Optionally allows user-interactive auth
|
||||
});
|
||||
|
||||
router.post("/r0/register/:source/requestToken", (req, res) => {
|
||||
// source === "email"
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-register-email-requesttoken
|
||||
// Proxies the Identity Service API validate/email/requestToken, but first checks that the given email address is not already associated with an account on this homeserver. See the Identity Service API for further information.
|
||||
|
||||
// source === "msisdn"
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-register-msisdn-requesttoken
|
||||
// Proxies the Identity Service API validate/msisdn/requestToken, but first checks that the given phone number is not already associated with an account on this homeserver. See the Identity Service API for further information.
|
||||
|
||||
// NOTE: Unclear whether it requires authentication or not.
|
||||
});
|
||||
|
||||
router.post("/r0/account/password", (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-account-password
|
||||
// Changes the password for an account on this homeserver.
|
||||
|
||||
// NOTE: Requires access token *or* interactive auth.
|
||||
});
|
||||
|
||||
router.post("/r0/account/password/:source/requestToken", (req, res) => {
|
||||
// source === "email"
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-account-password-email-requesttoken
|
||||
// Proxies the Identity Service API validate/email/requestToken, but first checks that the given email address is associated with an account on this homeserver. This API should be used to request validation tokens when authenticating for the account/password endpoint.
|
||||
|
||||
// source === "msisdn"
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-account-password-msisdn-requesttoken
|
||||
// Proxies the Identity Service API validate/msisdn/requestToken, but first checks that the given phone number is associated with an account on this homeserver. This API should be used to request validation tokens when authenticating for the account/password endpoint.
|
||||
|
||||
// NOTE: Unclear whether it requires authentication or not.
|
||||
});
|
||||
|
||||
router.post("/r0/account/deactivate", (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-account-deactivate
|
||||
// Deactivate the user's account, removing all ability for the user to login again. This API endpoint uses the User-Interactive Authentication API.
|
||||
|
||||
// NOTE: Requires access token *or* interactive auth.
|
||||
});
|
||||
|
||||
// FIXME: Modularize out 'create session for device' logic for both login and register endpoints
|
||||
|
||||
return router;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
"use strict";
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const expressPromiseRouter = require("express-promise-router");
|
||||
|
||||
module.exports = function(state) {
|
||||
const requireAccessToken = require("../../../middlewares/require-access-token")(state);
|
||||
|
||||
let router = expressPromiseRouter();
|
||||
|
||||
router.use(require("./login")(state));
|
||||
|
||||
router.post("/r0/logout", requireAccessToken, (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-logout
|
||||
// Invalidates an existing access token, so that it can no longer be used for authorization.
|
||||
});
|
||||
|
||||
router.post("/r0/logout/all", requireAccessToken, (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-logout-all
|
||||
// Invalidates all access tokens for a user, so that they can no longer be used for authorization. This includes the access token that made this request.
|
||||
});
|
||||
|
||||
router.post("/r0/register/available", (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-register-available
|
||||
// Checks to see if a username is available, and valid, for the server.
|
||||
});
|
||||
|
||||
router.post("/r0/register", (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-register
|
||||
// Register for an account on this homeserver. This API endpoint uses the User-Interactive Authentication API.
|
||||
// Optionally allows user-interactive auth
|
||||
});
|
||||
|
||||
router.post("/r0/register/:source/requestToken", (req, res) => {
|
||||
// source === "email"
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-register-email-requesttoken
|
||||
// Proxies the Identity Service API validate/email/requestToken, but first checks that the given email address is not already associated with an account on this homeserver. See the Identity Service API for further information.
|
||||
|
||||
// source === "msisdn"
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-register-msisdn-requesttoken
|
||||
// Proxies the Identity Service API validate/msisdn/requestToken, but first checks that the given phone number is not already associated with an account on this homeserver. See the Identity Service API for further information.
|
||||
|
||||
// NOTE: Unclear whether it requires authentication or not.
|
||||
});
|
||||
|
||||
router.post("/r0/account/password", (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-account-password
|
||||
// Changes the password for an account on this homeserver.
|
||||
|
||||
// NOTE: Requires access token *or* interactive auth.
|
||||
});
|
||||
|
||||
router.post("/r0/account/password/:source/requestToken", (req, res) => {
|
||||
// source === "email"
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-account-password-email-requesttoken
|
||||
// Proxies the Identity Service API validate/email/requestToken, but first checks that the given email address is associated with an account on this homeserver. This API should be used to request validation tokens when authenticating for the account/password endpoint.
|
||||
|
||||
// source === "msisdn"
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-account-password-msisdn-requesttoken
|
||||
// Proxies the Identity Service API validate/msisdn/requestToken, but first checks that the given phone number is associated with an account on this homeserver. This API should be used to request validation tokens when authenticating for the account/password endpoint.
|
||||
|
||||
// NOTE: Unclear whether it requires authentication or not.
|
||||
});
|
||||
|
||||
router.post("/r0/account/deactivate", (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-account-deactivate
|
||||
// Deactivate the user's account, removing all ability for the user to login again. This API endpoint uses the User-Interactive Authentication API.
|
||||
|
||||
// NOTE: Requires access token *or* interactive auth.
|
||||
});
|
||||
|
||||
// FIXME: Modularize out 'create session for device' logic for both login and register endpoints
|
||||
|
||||
return router;
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
"use strict";
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const scryptForHumans = require("scrypt-for-humans");
|
||||
|
||||
const errors = require("../../../errors");
|
||||
const normalize = require("../../../middlewares/normalize-payload");
|
||||
const validate = require("../../../middlewares/validate-payload");
|
||||
const processDeviceId = require("../../../process-device-id");
|
||||
const matchValue = require("../../../match-value");
|
||||
const canonicalizePhoneNumber = require("../../../canonicalize-phone-number");
|
||||
const validateUserIdentifier = require("../../../validate/user-identifier");
|
||||
|
||||
function normalizeIdentifier(payload) {
|
||||
if (payload.identifier != null) {
|
||||
/* Never overwrite the `identifier` key if it already exists */
|
||||
return null;
|
||||
} else if (payload.user != null) {
|
||||
return {
|
||||
identifier: {
|
||||
type: "m.id.user",
|
||||
user: payload.user
|
||||
}
|
||||
};
|
||||
} else if (payload.address != null) {
|
||||
return {
|
||||
identifier: {
|
||||
type: "m.id.thirdparty",
|
||||
medium: payload.medium,
|
||||
address: payload.address
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePhoneNumber(payload) {
|
||||
if (payload.identifier != null && payload.identifier.type === "m.id.phone") {
|
||||
return {
|
||||
identifier: {
|
||||
type: "m.id.thirdparty",
|
||||
medium: "msisdn",
|
||||
address: canonicalizePhoneNumber(payload.identifier.country, payload.identifier.phone)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(state) {
|
||||
let {db, configuration, fullyQualifyUser} = state;
|
||||
|
||||
let router = require("express-promise-router")();
|
||||
|
||||
router.get("/r0/login", (req, res) => {
|
||||
res.json({
|
||||
flows: [{ type: "m.login.password" }]
|
||||
});
|
||||
});
|
||||
|
||||
function validateLoginPayload(payload) {
|
||||
let {assertProperties, isPresent, isString, isOneOf} = require("../../../validator-lib.js");
|
||||
|
||||
assertProperties(payload, {
|
||||
type: [ isPresent, isOneOf("m.login.password", "m.login.token") ],
|
||||
device_id: [ isString ],
|
||||
initial_device_display_name: [ isString ],
|
||||
});
|
||||
|
||||
matchValue(payload.type, {
|
||||
"m.login.password": () => {
|
||||
assertProperties(payload, {
|
||||
identifier: [ isPresent, validateUserIdentifier ],
|
||||
password: [ isPresent, isString ]
|
||||
});
|
||||
},
|
||||
"m.login.token": () => {
|
||||
assertProperties(payload, {
|
||||
token: [ isPresent, isString ]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
router.post("/r0/login", normalize([normalizeIdentifier, normalizePhoneNumber]), validate(validateLoginPayload), (req, res) => {
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-login
|
||||
// Authenticates the user, and issues an access token they can use to authorize themself in subsequent requests.
|
||||
|
||||
/* FIXME: Rate-limit */
|
||||
|
||||
return Promise.try(() => {
|
||||
if (req.body.identifier.type === "m.id.user") {
|
||||
return db.accounts.byUsername({
|
||||
username: req.body.identifier.user
|
||||
});
|
||||
} else if (req.body.identifier.type === "m.id.thirdparty") {
|
||||
// FIXME
|
||||
} else {
|
||||
/* NOTE: We're not handling m.id.phone here, as that's normalized into m.id.thirdparty in normalizePhoneNumber */
|
||||
throw new errors.Unreachable("Invalid identifier type");
|
||||
}
|
||||
}).then((user) => {
|
||||
let deviceId = processDeviceId(req.body.device_id);
|
||||
|
||||
return Promise.try(() => {
|
||||
if (req.body.type === "m.login.password") {
|
||||
return scryptForHumans.verifyHash(req.body.password, user.password_hash);
|
||||
} else if (req.body.type === "m.login.token") {
|
||||
// FIXME
|
||||
} else {
|
||||
throw new errors.Unreachable("Invalid login type");
|
||||
}
|
||||
}).then(() => {
|
||||
return db.devices.createDeviceSession({
|
||||
name: req.body.initial_device_display_name,
|
||||
deviceId: deviceId,
|
||||
userId: user.id
|
||||
}).then((device) => {
|
||||
res.send({
|
||||
access_token: device.token,
|
||||
user_id: fullyQualifyUser(user.username),
|
||||
home_server: configuration.hostname,
|
||||
device_id: device.deviceId
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch(errors.InvalidUsername, (error) => {
|
||||
throw errors.Forbidden.chain(error, "Invalid username specified");
|
||||
}).catch(scryptForHumans.PasswordError, (error) => {
|
||||
throw errors.Forbidden.chain(error, "Invalid password specified");
|
||||
});
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
|
||||
const matchValue = require("../match-value");
|
||||
|
||||
module.exports = function validateUserIdentifier(userIdentifier) {
|
||||
let {assertProperties, isPresent, isString, isOneOf} = require("../validator-lib");
|
||||
|
||||
assertProperties(userIdentifier, {
|
||||
type: [ isPresent, isOneOf("m.id.user", "m.id.thirdparty", "m.id.phone") ]
|
||||
});
|
||||
|
||||
matchValue(userIdentifier.type, {
|
||||
"m.id.user": () => {
|
||||
assertProperties(userIdentifier, {
|
||||
user: [ isPresent, isString ]
|
||||
});
|
||||
},
|
||||
"m.id.thirdparty": () => {
|
||||
assertProperties(userIdentifier, {
|
||||
medium: [ isPresent, isString ],
|
||||
address: [ isPresent, isString ]
|
||||
});
|
||||
},
|
||||
"m.id.phone": () => {
|
||||
assertProperties(userIdentifier, {
|
||||
country: [ isPresent, isString ],
|
||||
phone: [ isPresent, isString ]
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue