"use strict"; const Promise = require("bluebird"); const defaultValue = require("default-value"); const expressPromiseRouter = require("express-promise-router"); const errors = require("../../../errors"); const validate = require("../../../middlewares/validate-payload"); const processDeviceID = require("../../../process-device-id"); module.exports = function(state) { const userInteractiveAuthentication = require("../../../middlewares/user-interactive-authentication")(state); let { db, knex, configuration, fullyQualifyUser } = state; let router = expressPromiseRouter(); function validateRegisterPayload(payload) { let {assertProperties, isPresent, isString, isOneOf, isBoolean} = require("../../../validator-lib"); assertProperties(payload, { kind: [ isOneOf("user", "guest") ], device_id: [ isString ], initial_device_display_name: [ isString ], username: [ isPresent, isString, (username) => { if (!/^[a-z0-9._=/-]+$/.test(username)) { throw new errors.InvalidUsername("Usernames may only contain a-z, 0-9, and . _ = - /"); } else if (username.startsWith("_")) { throw new errors.ExclusiveResource("Usernames starting with _ are reserved for bridges"); } } ], password: [ isPresent, isString ], /* FIXME: Password strength */ inhibit_login: [ isBoolean ], bind_email: [ isBoolean ] }); } let registerUIAMiddleware = userInteractiveAuthentication({ /* FIXME: Modify settings based on eg. CAPTCHAs being enabled */ required: false, flows: [ { stages: [ "m.login.dummy" ] } ] }); router.post("/r0/register", registerUIAMiddleware, validate(validateRegisterPayload), (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. /* FIXME: Rate-limit */ if (req.body.kind === "guest") { /* TODO: Re-evaluate this, once it becomes clearer how to deal with guest users and peeking across federation (ref. https://github.com/matrix-org/matrix-doc/pull/1777) */ throw new errors.Forbidden("Guest registrations are disabled on this server"); } else { let inhibitLogin = defaultValue(req.body.inhibit_login, false); /* FILEBUG: Default for bind_email is not clearly specified in spec */ /* FIXME: Actually implement e-mail binding on identity server */ let bindEmail = defaultValue(req.body.bind_email, false); let deviceId = processDeviceID(req.body.deviceID); return knex.transaction((transaction) => { return Promise.try(() => { return db.users.create({ type: "user", username: req.body.username, password: req.body.password }, transaction); }).then((user) => { return Promise.try(() => { if (!inhibitLogin) { return db.devices.createDeviceSession({ name: req.body.initial_device_display_name, deviceId: deviceId, userId: user.id }, transaction); } }).then((device) => { let sessionDetails = (device != null) ? { access_token: device.token, device_id: device.device_id } : {}; res.send({ user_id: fullyQualifyUser(user.username), home_server: configuration.hostname, ...sessionDetails }); }); }); }).catch({ name: "UniqueConstraintViolationError", table: "users", column: "username" }, (_error) => { throw new errors.UserExists("That username is already taken"); }); } }); return router; }