From c8f9947ef1d530b310e5646c473be0e458189aa3 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sat, 25 Apr 2020 16:13:07 +0200 Subject: [PATCH 1/3] Add LDAP auth server and make username required and unique per user Closes #5 --- .gitignore | 3 +- assets/js/register.js | 9 +- package.json | 4 +- src/Aldap.ts | 10 +- src/LDAPServerComponent.ts | 74 ++++++++++ src/controllers/AuthController.ts | 17 ++- src/migrations.ts | 2 + src/migrations/CreateUserPasswordsTable.ts | 1 + src/migrations/CreateUsernamesTable.ts | 18 +++ src/models/UserPassword.ts | 6 +- src/models/Username.ts | 32 +++++ views/register.njk | 13 +- yarn.lock | 152 +++++++++++++++++++-- 13 files changed, 316 insertions(+), 25 deletions(-) create mode 100644 src/LDAPServerComponent.ts create mode 100644 src/migrations/CreateUsernamesTable.ts create mode 100644 src/models/Username.ts diff --git a/.gitignore b/.gitignore index 700344c..7626c60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules .idea public -dist \ No newline at end of file +dist +yarn-error.log diff --git a/assets/js/register.js b/assets/js/register.js index 5db27cc..2eeb15a 100644 --- a/assets/js/register.js +++ b/assets/js/register.js @@ -1,6 +1,7 @@ document.addEventListener('DOMContentLoaded', () => { const createEmailAddress = document.getElementById('field-create_email'); const username = document.getElementById('field-username'); + const email_username = document.getElementById('email_username'); const domain = document.getElementById('field-domain'); const recovery_email = document.getElementById('field-recovery_email'); const recovery_email_label = recovery_email.parentElement.querySelector('.hint'); @@ -9,14 +10,18 @@ document.addEventListener('DOMContentLoaded', () => { if (createEmailAddress.checked) { recovery_email.removeAttribute('required'); recovery_email_label.style.display = 'block'; - username.disabled = domain.disabled = false; + domain.disabled = false; } else { recovery_email.setAttribute('required', 'required'); recovery_email_label.style.display = 'none'; - username.disabled = domain.disabled = true; + domain.disabled = true; } + username.value = username.value.toLowerCase(); + email_username.innerText = username.value + '@'; } createEmailAddress.addEventListener('change', updateForm); + username.addEventListener('change', updateForm); + username.addEventListener('keyup', updateForm); updateForm(); }); \ No newline at end of file diff --git a/package.json b/package.json index 2f2cbdc..597349a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@types/config": "^0.0.36", "@types/express": "^4.17.6", "@types/jest": "^25.2.1", + "@types/ldapjs": "^1.0.7", "@types/node": "^13.13.2", "babel-loader": "^8.1.0", "concurrently": "^5.1.0", @@ -47,6 +48,7 @@ "dependencies": { "argon2": "^0.26.2", "config": "^3.3.1", - "express": "^4.17.1" + "express": "^4.17.1", + "ldapjs": "^1.0.2" } } diff --git a/src/Aldap.ts b/src/Aldap.ts index 4c9af44..031d457 100644 --- a/src/Aldap.ts +++ b/src/Aldap.ts @@ -20,6 +20,7 @@ import AuthComponent from "wms-core/auth/AuthComponent"; import AuthGuard from "wms-core/auth/AuthGuard"; import {PasswordAuthProof} from "./models/UserPassword"; import {MIGRATIONS} from "./migrations"; +import LDAPServerComponent from "./LDAPServerComponent"; export default class Aldap extends Application { private readonly port: number; @@ -72,15 +73,18 @@ export default class Aldap extends Application { // Middlewares this.use(new CsrfProtectionComponent()); - // WebSocket server - this.use(new WebSocketServerComponent(this, expressAppComponent, redisComponent)); - // Auth this.use(new AuthComponent(new class extends AuthGuard { public async getProofForSession(session: Express.Session): Promise { return PasswordAuthProof.getProofForSession(session); } })); + + // WebSocket server + this.use(new WebSocketServerComponent(this, expressAppComponent, redisComponent)); + + // LDAP server + this.use(new LDAPServerComponent()); } private registerWebSocketListeners() { diff --git a/src/LDAPServerComponent.ts b/src/LDAPServerComponent.ts new file mode 100644 index 0000000..3026faf --- /dev/null +++ b/src/LDAPServerComponent.ts @@ -0,0 +1,74 @@ +import ApplicationComponent from "wms-core/ApplicationComponent"; +import {Express, Router} from "express"; +import ldap, {InsufficientAccessRightsError, InvalidCredentialsError, Server} from "ldapjs"; +import Logger from "wms-core/Logger"; +import Username from "./models/Username"; +import UserEmail from "wms-core/auth/models/UserEmail"; +import {PasswordAuthProof} from "./models/UserPassword"; +import Throttler from "wms-core/Throttler"; + +export default class LDAPServerComponent extends ApplicationComponent { + private server?: Server; + + public async start(app: Express, router: Router): Promise { + this.server = ldap.createServer({ + log: console + }); + let authorize = (req: any, res: any, next: any) => { + Logger.debug(req); + + if (!req.connection.ldap.bindDN.equals('cn=root')) + return next(new InsufficientAccessRightsError()); + + return next(); + }; + this.server.bind('ou=users,dc=toot,dc=party', async (req: any, res: any, next: any) => { + const rdns = req.dn.toString().split(', ').map((rdn: string) => rdn.split('=')); + let username; + let email; + for (const rdn of rdns) { + if (rdn[0] === 'cn') { + username = rdn[1]; + } else if (rdn[0] === 'email') { + email = rdn[1]; + } + } + + Logger.debug('Matrix authentication attempt:', username, email); + + try { + Throttler.throttle('ldap_auth', 3, 30 * 1000, username); + } catch (e) { + Logger.debug('Too many auth requests'); + next(new InvalidCredentialsError()); + return; + } + + const user = await Username.getUserFromUsername(username); + if (user) { + const email = await UserEmail.getMainFromUser(user.id!); + if (email) { + const authProof = new PasswordAuthProof(email.email!); + if (await authProof.authorize(req.credentials)) { + Logger.debug('Success'); + res.end(); + return; + } + } + } + + Logger.debug('Fail'); + next(new InvalidCredentialsError()); + }); + this.server.unbind((req: any, res: any, next: any) => { + Logger.debug('Unbind', req); + next(); + }); + this.server.listen(8389, '127.0.0.1', () => { + Logger.info(`LDAP server listening on ${this.server!.url}`); + }); + } + + public async stop(): Promise { + } +} \ No newline at end of file diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts index 5372e99..fc27cae 100644 --- a/src/controllers/AuthController.ts +++ b/src/controllers/AuthController.ts @@ -5,6 +5,7 @@ import Validator from "wms-core/db/Validator"; import {EMAIL_REGEX} from "wms-core/db/Model"; import {PasswordAuthProof} from "../models/UserPassword"; import UserEmail from "wms-core/auth/models/UserEmail"; +import Username, {USERNAME_REGEXP} from "../models/Username"; export default class AuthController extends Controller { routes(): void { @@ -33,7 +34,7 @@ export default class AuthController extends Controller { return; } - await passwordAuthProof.authorize(req.session!, req.body.password); + await passwordAuthProof.authorize(req.body.password, req.session); await req.authGuard.authenticateOrRegister(req.session!, passwordAuthProof); req.flash('success', `Welcome, ${user.name}.`); @@ -46,6 +47,7 @@ export default class AuthController extends Controller { private async postRegister(req: Request, res: Response): Promise { const validationMap: any = { + username: new Validator().defined().between(3, 64).regexp(USERNAME_REGEXP).unique(Username), password: new Validator().defined().minLength(8), password_confirmation: new Validator().defined().sameAs('password', req.body.password), terms: new Validator().defined(), @@ -53,9 +55,8 @@ export default class AuthController extends Controller { let email: string; if (req.body.create_email) { - validationMap['username'] = new Validator().defined().minLength(3).regexp(/^[0-9a-zA-Z_-]+$/); validationMap['domain'] = new Validator().defined().regexp(/^(toot\.party)$/); - validationMap['recovery_email'] = new Validator().acceptUndefined().regexp(EMAIL_REGEX).unique(UserEmail, 'email'); + validationMap['recovery_email'] = new Validator().acceptUndefined(true).regexp(EMAIL_REGEX).unique(UserEmail, 'email'); email = req.body.email = req.body.username + '@' + req.body.domain; validationMap['email'] = new Validator().defined().regexp(EMAIL_REGEX).unique(UserEmail, 'email'); } else { @@ -66,11 +67,18 @@ export default class AuthController extends Controller { const passwordAuthProof = new PasswordAuthProof(email, false); const userPassword = await passwordAuthProof.register(req.body.password); - await passwordAuthProof.authorize(req.session!, req.body.password_confirmation); + await passwordAuthProof.authorize(req.body.password_confirmation, req.session); await req.authGuard.authenticateOrRegister(req.session!, passwordAuthProof, async (connection, userID) => { const callbacks: (() => Promise)[] = []; + + // Password await userPassword.setUser(userID); await userPassword.save(connection, c => callbacks.push(c)); + + // Username + await new Username({user_id: userID, username: req.body.username}).save(connection, c => callbacks.push(c)); + + // Email if (req.body.create_email && req.body.recovery_email) { await new UserEmail({ user_id: userID, @@ -78,6 +86,7 @@ export default class AuthController extends Controller { main: false, }).save(connection, c => callbacks.push(c)); } + return callbacks; }); diff --git a/src/migrations.ts b/src/migrations.ts index b703e11..a911950 100644 --- a/src/migrations.ts +++ b/src/migrations.ts @@ -2,10 +2,12 @@ import CreateMigrationsTable from "wms-core/migrations/CreateMigrationsTable"; import CreateLogsTable from "wms-core/migrations/CreateLogsTable"; import CreateUsersAndUserEmailsTable from "wms-core/auth/migrations/CreateUsersAndUserEmailsTable"; import CreateUserPasswordsTable from "./migrations/CreateUserPasswordsTable"; +import CreateUsernamesTable from "./migrations/CreateUsernamesTable"; export const MIGRATIONS = [ CreateMigrationsTable, CreateLogsTable, CreateUsersAndUserEmailsTable, CreateUserPasswordsTable, + CreateUsernamesTable, ]; \ No newline at end of file diff --git a/src/migrations/CreateUserPasswordsTable.ts b/src/migrations/CreateUserPasswordsTable.ts index 7305cc5..40509eb 100644 --- a/src/migrations/CreateUserPasswordsTable.ts +++ b/src/migrations/CreateUserPasswordsTable.ts @@ -13,5 +13,6 @@ export default class CreateUserPasswordsTable extends Migration { } public async rollback(): Promise { + await query('DROP TABLE user_passwords'); } } \ No newline at end of file diff --git a/src/migrations/CreateUsernamesTable.ts b/src/migrations/CreateUsernamesTable.ts new file mode 100644 index 0000000..1ee20db --- /dev/null +++ b/src/migrations/CreateUsernamesTable.ts @@ -0,0 +1,18 @@ +import Migration from "wms-core/db/Migration"; +import {query} from "wms-core/db/MysqlConnectionManager"; + +export default class CreateUserPasswordsTable extends Migration { + public async install(): Promise { + await query('CREATE TABLE usernames(' + + 'id INT NOT NULL AUTO_INCREMENT,' + + 'user_id INT UNIQUE NOT NULL,' + + 'username VARCHAR(64) UNIQUE NOT NULL,' + + 'PRIMARY KEY(id),' + + 'FOREIGN KEY user_name_fk (user_id) REFERENCES users (id) ON DELETE CASCADE' + + ')'); + } + + public async rollback(): Promise { + await query('DROP TABLE usernames'); + } +} \ No newline at end of file diff --git a/src/models/UserPassword.ts b/src/models/UserPassword.ts index 511dc42..20f122a 100644 --- a/src/models/UserPassword.ts +++ b/src/models/UserPassword.ts @@ -91,12 +91,14 @@ export class PasswordAuthProof implements AuthProof { return this.userPassword ? this.userPassword : await UserPassword.getByEmail(await this.getEmail()); } - public async authorize(session: Express.Session, passwordGuess: string): Promise { + public async authorize(passwordGuess: string, session?: Express.Session): Promise { const password = await this.getUserPassword(); if (!password || !await password.verifyPassword(passwordGuess)) return false; this.authorized = true; - this.save(session); + if (session) { + this.save(session); + } return true; } diff --git a/src/models/Username.ts b/src/models/Username.ts new file mode 100644 index 0000000..ffbcb6d --- /dev/null +++ b/src/models/Username.ts @@ -0,0 +1,32 @@ +import Model from "wms-core/db/Model"; +import Validator from "wms-core/db/Validator"; +import User from "wms-core/auth/models/User"; + +export const USERNAME_REGEXP = /^[0-9a-z_-]+$/; + +export default class Username extends Model { + public static async fromUser(userID: number): Promise { + const models = await this.models(this.select().where('user_id', userID)); + return models && models.length > 0 ? models[0] : null; + } + + public static async getUserFromUsername(username: string): Promise { + const models = await this.models(this.select().where('username', username.toLowerCase())); + if (!models || models.length === 0) return null; + return await User.getById(models[0].user_id!); + } + + private user_id?: number; + public username?: string; + + + constructor(data: any) { + super(data); + } + + protected defineProperties(): void { + this.defineProperty('user_id', new Validator().defined().exists(User, 'id').unique(this)); + this.defineProperty('username', new Validator().defined().between(3, 64).regexp(USERNAME_REGEXP).unique(this)); + } + +} \ No newline at end of file diff --git a/views/register.njk b/views/register.njk index 8872960..f62501d 100644 --- a/views/register.njk +++ b/views/register.njk @@ -7,21 +7,26 @@ {% endblock %} {% block body %} -

Register

-
+

Register

+
+
+

Username

+ {{ macros.field(_locals, 'text', 'username', null, 'Choose your username', 'This cannot be changed later.', 'pattern="[0-9a-z_-]+" required') }} +
+

Email

{{ macros.field(_locals, 'checkbox', 'create_email', null, 'Create an email address') }}
- {{ macros.field(_locals, 'text', 'username', null, 'Choose your username', null, 'disabled') }} - @ + @ {{ macros.field(_locals, 'select', 'domain', null, 'Choose your domain', null, 'disabled', ['toot.party']) }}
{{ macros.fieldError(_locals, 'email') }} +
This cannot be changed later.
diff --git a/yarn.lock b/yarn.lock index b774376..5c44257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1134,6 +1134,13 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" +"@types/ldapjs@^1.0.7": + version "1.0.7" + resolved "http://127.0.0.1:4873/@types%2fldapjs/-/ldapjs-1.0.7.tgz#e5829b8ce008098d599cfd86d403e6cafd508b76" + integrity sha512-Cacc0pQ6fw8+J5Qwebbj2+YpYANOl09WEDjJsNyPHpNSza318mUIuAhgXDfC8kXDViymlEQIvgbvuPKovRIVEQ== + dependencies: + "@types/node" "*" + "@types/mime@*": version "2.0.1" resolved "http://127.0.0.1:4873/@types%2fmime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" @@ -1628,6 +1635,11 @@ asn1.js@^4.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" +asn1@0.2.3: + version "0.2.3" + resolved "http://127.0.0.1:4873/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + integrity sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y= + asn1@~0.2.3: version "0.2.4" resolved "http://127.0.0.1:4873/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -1635,6 +1647,11 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" +assert-plus@0.1.5: + version "0.1.5" + resolved "http://127.0.0.1:4873/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" + integrity sha1-7nQAlBMALYTOxyGcasgRgS5yMWA= + assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "http://127.0.0.1:4873/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" @@ -1774,6 +1791,13 @@ babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" +backoff@^2.5.0: + version "2.5.0" + resolved "http://127.0.0.1:4873/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f" + integrity sha1-9hbtqdPktmuMp/ynn2lXIsX44m8= + dependencies: + precond "0.2" + balanced-match@^1.0.0: version "1.0.0" resolved "http://127.0.0.1:4873/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -2125,6 +2149,16 @@ builtin-status-codes@^3.0.0: resolved "http://127.0.0.1:4873/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= +bunyan@^1.8.3: + version "1.8.12" + resolved "http://127.0.0.1:4873/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797" + integrity sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c= + optionalDependencies: + dtrace-provider "~0.8" + moment "^2.10.6" + mv "~2" + safe-json-stringify "~1" + bytes@3.1.0: version "3.1.0" resolved "http://127.0.0.1:4873/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -2904,7 +2938,7 @@ cyclist@^1.0.1: resolved "http://127.0.0.1:4873/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -dashdash@^1.12.0: +dashdash@^1.12.0, dashdash@^1.14.0: version "1.14.1" resolved "http://127.0.0.1:4873/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= @@ -3271,6 +3305,13 @@ download@^7.1.0: p-event "^2.1.0" pify "^3.0.0" +dtrace-provider@~0.8: + version "0.8.8" + resolved "http://127.0.0.1:4873/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" + integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg== + dependencies: + nan "^2.14.0" + duplexer3@^0.1.4: version "0.1.4" resolved "http://127.0.0.1:4873/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -3705,6 +3746,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extsprintf@1.2.0: + version "1.2.0" + resolved "http://127.0.0.1:4873/extsprintf/-/extsprintf-1.2.0.tgz#5ad946c22f5b32ba7f8cd7426711c6e8a3fc2529" + integrity sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk= + extsprintf@1.3.0: version "1.3.0" resolved "http://127.0.0.1:4873/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -4151,6 +4197,17 @@ glob-parent@^5.1.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob@^6.0.1: + version "6.0.4" + resolved "http://127.0.0.1:4873/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1: version "7.1.6" resolved "http://127.0.0.1:4873/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -5698,6 +5755,30 @@ lcid@^2.0.0: dependencies: invert-kv "^2.0.0" +ldap-filter@0.2.2: + version "0.2.2" + resolved "http://127.0.0.1:4873/ldap-filter/-/ldap-filter-0.2.2.tgz#f2b842be0b86da3352798505b31ebcae590d77d0" + integrity sha1-8rhCvguG2jNSeYUFsx68rlkNd9A= + dependencies: + assert-plus "0.1.5" + +ldapjs@^1.0.2: + version "1.0.2" + resolved "http://127.0.0.1:4873/ldapjs/-/ldapjs-1.0.2.tgz#544ff7032b7b83c68f0701328d9297aa694340f9" + integrity sha1-VE/3Ayt7g8aPBwEyjZKXqmlDQPk= + dependencies: + asn1 "0.2.3" + assert-plus "^1.0.0" + backoff "^2.5.0" + bunyan "^1.8.3" + dashdash "^1.14.0" + ldap-filter "0.2.2" + once "^1.4.0" + vasync "^1.6.4" + verror "^1.8.1" + optionalDependencies: + dtrace-provider "~0.8" + leven@^3.1.0: version "3.1.0" resolved "http://127.0.0.1:4873/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -6184,7 +6265,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "http://127.0.0.1:4873/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "http://127.0.0.1:4873/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -6592,6 +6673,11 @@ mkdirp@1.x, mkdirp@~1.0.3: dependencies: minimist "^1.2.5" +moment@^2.10.6: + version "2.24.0" + resolved "http://127.0.0.1:4873/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + move-concurrently@^1.0.1: version "1.0.1" resolved "http://127.0.0.1:4873/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -6628,6 +6714,15 @@ ms@^2.1.1: resolved "http://127.0.0.1:4873/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +mv@~2: + version "2.1.1" + resolved "http://127.0.0.1:4873/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" + integrity sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI= + dependencies: + mkdirp "~0.5.1" + ncp "~2.0.0" + rimraf "~2.4.0" + mysql@^2.18.1: version "2.18.1" resolved "http://127.0.0.1:4873/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717" @@ -6638,7 +6733,7 @@ mysql@^2.18.1: safe-buffer "5.1.2" sqlstring "2.3.1" -nan@^2.12.1, nan@^2.13.2: +nan@^2.12.1, nan@^2.13.2, nan@^2.14.0: version "2.14.1" resolved "http://127.0.0.1:4873/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== @@ -6665,6 +6760,11 @@ natural-compare@^1.4.0: resolved "http://127.0.0.1:4873/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +ncp@~2.0.0: + version "2.0.0" + resolved "http://127.0.0.1:4873/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= + needle@^2.2.1: version "2.4.1" resolved "http://127.0.0.1:4873/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" @@ -7591,6 +7691,11 @@ postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0. source-map "^0.6.1" supports-color "^6.1.0" +precond@0.2: + version "0.2.3" + resolved "http://127.0.0.1:4873/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" + integrity sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw= + prelude-ls@~1.1.2: version "1.1.2" resolved "http://127.0.0.1:4873/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -8196,6 +8301,13 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" +rimraf@~2.4.0: + version "2.4.5" + resolved "http://127.0.0.1:4873/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" + integrity sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto= + dependencies: + glob "^6.0.1" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "http://127.0.0.1:4873/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -8238,6 +8350,11 @@ safe-buffer@5.2.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s resolved "http://127.0.0.1:4873/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-json-stringify@~1: + version "1.2.0" + resolved "http://127.0.0.1:4873/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" + integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== + safe-regex@^1.1.0: version "1.1.0" resolved "http://127.0.0.1:4873/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -9611,7 +9728,14 @@ vary@~1.1.2: resolved "http://127.0.0.1:4873/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -verror@1.10.0: +vasync@^1.6.4: + version "1.6.4" + resolved "http://127.0.0.1:4873/vasync/-/vasync-1.6.4.tgz#dfe93616ad0e7ae801b332a9d88bfc5cdc8e1d1f" + integrity sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8= + dependencies: + verror "1.6.0" + +verror@1.10.0, verror@^1.8.1: version "1.10.0" resolved "http://127.0.0.1:4873/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= @@ -9620,6 +9744,13 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +verror@1.6.0: + version "1.6.0" + resolved "http://127.0.0.1:4873/verror/-/verror-1.6.0.tgz#7d13b27b1facc2e2da90405eb5ea6e5bdd252ea5" + integrity sha1-fROyex+swuLakEBetepuW90lLqU= + dependencies: + extsprintf "1.2.0" + vm-browserify@^1.0.1: version "1.1.2" resolved "http://127.0.0.1:4873/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -9798,9 +9929,9 @@ widest-line@^3.1.0: string-width "^4.0.0" wms-core@^0: - version "0.4.15" - resolved "http://127.0.0.1:4873/wms-core/-/wms-core-0.4.15.tgz#3b4ef49cbf1bcf24b52c337439c71f361665a5bf" - integrity sha512-YoAsdJ69himgb3dT+6+aildMShFQJAD8yhQGQJNhY32RmAbl3C2z0xIhBy1kL/YAzypqZQxvBYZS14f/qd94QQ== + version "0.4.17" + resolved "http://127.0.0.1:4873/wms-core/-/wms-core-0.4.17.tgz#9f9d1fd93f61c81f620f96e1affd2d68b31a803f" + integrity sha512-ybHtCDTUSXbrEme/GztBgRZCR87V7xHF7SkqJgXcxvq3RvlBomfHme72YGiCNCnMop9bmJS07QUs6kD9hEWfYw== dependencies: "@types/express" "^4.17.6" "@types/express-session" "^1.17.0" @@ -9879,11 +10010,16 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.0.0, ws@^7.2.3: +ws@^7.0.0: version "7.2.3" resolved "http://127.0.0.1:4873/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== +ws@^7.2.3: + version "7.2.5" + resolved "http://127.0.0.1:4873/ws/-/ws-7.2.5.tgz#abb1370d4626a5a9cd79d8de404aa18b3465d10d" + integrity sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA== + xdg-basedir@^4.0.0: version "4.0.0" resolved "http://127.0.0.1:4873/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" From 4318edfce88ca6231e21bd2d24ed2a768c2599c0 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sat, 25 Apr 2020 16:13:40 +0200 Subject: [PATCH 2/3] Add prelaunch wall --- config/default.ts | 3 +- config/production.ts | 1 + src/Aldap.ts | 2 ++ src/controllers/PreLaunchWall.ts | 50 ++++++++++++++++++++++++++++++++ views/prelaunch-wall.njk | 19 ++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/controllers/PreLaunchWall.ts create mode 100644 views/prelaunch-wall.njk diff --git a/config/default.ts b/config/default.ts index 7d2bfdb..2d5176a 100644 --- a/config/default.ts +++ b/config/default.ts @@ -11,5 +11,6 @@ export default Object.assign(require("wms-core/config/default").default, { host: "127.0.0.1", port: 6379, prefix: 'aldap' - } + }, + 'prelaunch-password': '$argon2i$v=19$m=4096,t=3,p=1$V7njt+IBmIQ/epc7tuQcfA$ypJCNauYSPrjOhtb5UqTbRlqCHkEGikBApOrYmbdYC0', }); \ No newline at end of file diff --git a/config/production.ts b/config/production.ts index 7765a4f..cb5968f 100644 --- a/config/production.ts +++ b/config/production.ts @@ -1,2 +1,3 @@ export default Object.assign(require("wms-core/config/production").default, { + 'prelaunch-password': 'CHANGE ME', }); \ No newline at end of file diff --git a/src/Aldap.ts b/src/Aldap.ts index 031d457..97d5a1e 100644 --- a/src/Aldap.ts +++ b/src/Aldap.ts @@ -21,6 +21,7 @@ import AuthGuard from "wms-core/auth/AuthGuard"; import {PasswordAuthProof} from "./models/UserPassword"; import {MIGRATIONS} from "./migrations"; import LDAPServerComponent from "./LDAPServerComponent"; +import PreLaunchWall from "./controllers/PreLaunchWall"; export default class Aldap extends Application { private readonly port: number; @@ -91,6 +92,7 @@ export default class Aldap extends Application { } private registerControllers() { + this.use(new PreLaunchWall()); this.use(new HomeController()); this.use(new AuthController()); } diff --git a/src/controllers/PreLaunchWall.ts b/src/controllers/PreLaunchWall.ts new file mode 100644 index 0000000..53f2686 --- /dev/null +++ b/src/controllers/PreLaunchWall.ts @@ -0,0 +1,50 @@ +import Controller from "wms-core/Controller"; +import {Request, RequestHandler, Response} from "express"; +import {ForbiddenHttpError} from "wms-core/HttpError"; +import Validator from "wms-core/db/Validator"; +import argon2 from "argon2"; +import config from "config"; + +export default class PreLaunchWall extends Controller { + public getGlobalHandlers(): RequestHandler[] { + return [ + (req, res, next) => { + if (!req.session) throw new ForbiddenHttpError('page', req.url); + + if (!req.session.authorized) { + const route = Controller.route('prelaunch-wall'); + if (req.url !== route) { + res.redirect(route); + return; + } + } + + next(); + } + ]; + } + + routes(): void { + this.get('/prelaunch-wall', this.getWall, 'prelaunch-wall'); + this.post('/prelaunch-wall', this.postWall, 'prelaunch-wall'); + } + + private async getWall(req: Request, res: Response) { + res.render('prelaunch-wall'); + } + + private async postWall(req: Request, res: Response) { + await this.validate({ + password: new Validator().defined(), + }, req.body); + + if (await argon2.verify(config.get('prelaunch-password'), req.body.password)) { + req.session!.authorized = true; + req.flash('success', 'Authentication success!'); + res.redirect(Controller.route('home')); + } + + req.flash('error', 'Invalid password.'); + res.redirectBack(); + } +} \ No newline at end of file diff --git a/views/prelaunch-wall.njk b/views/prelaunch-wall.njk new file mode 100644 index 0000000..99e8741 --- /dev/null +++ b/views/prelaunch-wall.njk @@ -0,0 +1,19 @@ +{% extends 'layouts/base.njk' %} + +{% set title = 'ALDAP - Early access' %} + +{% block body %} +
+
+

{{ title }}

+ + + {{ macros.field(_locals, 'password', 'password', null, 'Enter password') }} + + + + {{ macros.csrf(getCSRFToken) }} + +
+
+{% endblock %} \ No newline at end of file From 9b46691b99aef81bc9636576d125cfcf0d53cb67 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sat, 25 Apr 2020 16:14:04 +0200 Subject: [PATCH 3/3] Version 0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 597349a..1642808 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aldap", - "version": "0.2.0", + "version": "0.3.0", "description": "Authentication LDAP server", "repository": "git@gitlab.com:ArisuOngaku/aldap.git", "author": "Alice Gaudon ",