From 449922490f2ade94a88cf6682c52e11bdb3f94ab Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 14:02:58 +0100 Subject: [PATCH 01/12] MagicLinkAuthMethod: do not interrupt auth with used magic links --- src/auth/magic_link/MagicLinkAuthMethod.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/auth/magic_link/MagicLinkAuthMethod.ts b/src/auth/magic_link/MagicLinkAuthMethod.ts index 9048594..75a0e96 100644 --- a/src/auth/magic_link/MagicLinkAuthMethod.ts +++ b/src/auth/magic_link/MagicLinkAuthMethod.ts @@ -49,6 +49,7 @@ export default class MagicLinkAuthMethod implements AuthMethod { public async interruptAuth(req: Request, res: Response): Promise { const pendingLink = await MagicLink.select() .where('session_id', req.getSession().id) + .where('used', 0) .first(); if (pendingLink) { From b9ac4d0f0581acd2fb86c73727167506ecaba9be Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 14:07:20 +0100 Subject: [PATCH 02/12] AddUsedToMagicLinksMigration: delete all magic links after install --- src/auth/magic_link/AddUsedToMagicLinksMigration.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/auth/magic_link/AddUsedToMagicLinksMigration.ts b/src/auth/magic_link/AddUsedToMagicLinksMigration.ts index 3e7f639..2a0d141 100644 --- a/src/auth/magic_link/AddUsedToMagicLinksMigration.ts +++ b/src/auth/magic_link/AddUsedToMagicLinksMigration.ts @@ -4,6 +4,7 @@ export default class AddUsedToMagicLinksMigration extends Migration { public async install(): Promise { await this.query(`ALTER TABLE magic_links ADD COLUMN used BOOLEAN NOT NULL`); + await this.query(`DELETE FROM magic_links`); } public async rollback(): Promise { From 27bce2da0a2a12f773e479c67ff747847ea6b32e Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 14:07:42 +0100 Subject: [PATCH 03/12] package.json: reorder build script to a more natural position --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 18e82f9..6641945 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "clean": "(test ! -d dist || rm -r dist)", "prepareSources": "cp package.json src/", "compile": "yarn clean && tsc", - "dev": "yarn prepareSources && concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon\" \"maildev\"", "build": "yarn prepareSources && yarn compile && cp -r yarn.lock README.md config/ views/ dist/ && mkdir dist/types && cp src/types/* dist/types/", + "dev": "yarn prepareSources && concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon\" \"maildev\"", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "release": "yarn build && yarn lint && yarn test && cd dist && yarn publish" }, From b211845f572f3f2d46f786b986ff790455741c25 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 14:36:27 +0100 Subject: [PATCH 04/12] Mail: also debug log data --- src/mail/Mail.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mail/Mail.ts b/src/mail/Mail.ts index da66d79..6853cd4 100644 --- a/src/mail/Mail.ts +++ b/src/mail/Mail.ts @@ -110,7 +110,7 @@ export default class Mail { this.data.app = config.get('app'); // Log - logger.debug('Send mail', this.options); + logger.debug('Send mail', this.options, this.data); // Render email this.options.html = Mail.parse(this.environment, 'mails/' + this.template.template + '.mjml.njk', From 8a25f214abe816bdffa79565516f17916345d3a9 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 14:37:50 +0100 Subject: [PATCH 05/12] AuthGuard: always provide a string to pending account mail username --- src/auth/AuthGuard.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/auth/AuthGuard.ts b/src/auth/AuthGuard.ts index e13b8eb..2742f62 100644 --- a/src/auth/AuthGuard.ts +++ b/src/auth/AuthGuard.ts @@ -11,6 +11,7 @@ import Application from "../Application"; import NunjucksComponent from "../components/NunjucksComponent"; import AuthMethod from "./AuthMethod"; import {Session, SessionData} from "express-session"; +import UserNameComponent from "./models/UserNameComponent"; export default class AuthGuard { private readonly authMethods: AuthMethod>[]; @@ -140,7 +141,9 @@ export default class AuthGuard { if (!user.isApproved()) { await new Mail(this.app.as(NunjucksComponent).getEnvironment(), PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, { - username: (await user.mainEmail.get())?.getOrFail('email'), + username: user.asOptional(UserNameComponent)?.name || + (await user.mainEmail.get())?.getOrFail('email') || + 'Could not find an identifier', link: config.get('public_url') + Controller.route('accounts-approval'), }).send(config.get('app.contact_email')); } From 4817563dc197637826cdf823bf06f6c36bcbb79d Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 16:21:24 +0100 Subject: [PATCH 06/12] Make models and components available immediately after their migration --- src/db/MysqlConnectionManager.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/db/MysqlConnectionManager.ts b/src/db/MysqlConnectionManager.ts index cc8600d..1b973cb 100644 --- a/src/db/MysqlConnectionManager.ts +++ b/src/db/MysqlConnectionManager.ts @@ -206,9 +206,7 @@ export default class MysqlConnectionManager { ]); }); } - } - for (const migration of this.migrations) { migration.registerModels?.(); } } From 8b98c8cc59e0c75659af9076c8e6f22f2fbcf148 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 16:22:51 +0100 Subject: [PATCH 07/12] AddNameToUsersMigration: fix can't work when db already has users --- src/auth/migrations/AddNameToUsersMigration.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/auth/migrations/AddNameToUsersMigration.ts b/src/auth/migrations/AddNameToUsersMigration.ts index 0c3bfb0..a9eadc6 100644 --- a/src/auth/migrations/AddNameToUsersMigration.ts +++ b/src/auth/migrations/AddNameToUsersMigration.ts @@ -4,13 +4,24 @@ import User from "../models/User"; import UserNameComponent from "../models/UserNameComponent"; import MagicLink from "../models/MagicLink"; import MagicLinkUserNameComponent from "../models/MagicLinkUserNameComponent"; +import {nanoid} from "nanoid"; export default class AddNameToUsersMigration extends Migration { public async install(): Promise { await this.query(`ALTER TABLE users - ADD COLUMN name VARCHAR(64) UNIQUE NOT NULL`); + ADD COLUMN name VARCHAR(64) NOT NULL`); await this.query(`ALTER TABLE magic_links ADD COLUMN username VARCHAR(64) DEFAULT NULL`); + + // Give every user a random name + const users = await User.select().get(this.getCurrentConnection()); + await Promise.all(users.map(user => { + user.name = nanoid(); + return user.save(this.getCurrentConnection()); + })); + + await this.query(`ALTER TABLE users + ADD CONSTRAINT UNIQUE (name)`); } public async rollback(): Promise { From 4745ae4e1742ce1603c9eb7f78d985bb21665a19 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 16:36:15 +0100 Subject: [PATCH 08/12] Fix session id not available in websocket listeners Fixes #21 --- src/components/WebSocketServerComponent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/WebSocketServerComponent.ts b/src/components/WebSocketServerComponent.ts index 98824cb..1a55ed1 100644 --- a/src/components/WebSocketServerComponent.ts +++ b/src/components/WebSocketServerComponent.ts @@ -10,6 +10,7 @@ import Application from "../Application"; import RedisComponent from "./RedisComponent"; import WebSocketListener from "../WebSocketListener"; import NunjucksComponent from "./NunjucksComponent"; +import {Session} from "express-session"; export default class WebSocketServerComponent extends ApplicationComponent { private wss?: WebSocket.Server; @@ -65,7 +66,7 @@ export default class WebSocketServerComponent extends ApplicationComponent { session.id = sid; store.createSession(request, session); - listener.handle(socket, request, (request).session).catch(err => { + listener.handle(socket, request, session as Session).catch(err => { logger.error(err, 'Error in websocket listener.'); }); }); From 744923dc78cc5fbd535f291b15067bd701de9a40 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 17:25:07 +0100 Subject: [PATCH 09/12] Auth view: merge register forms in one with js switch --- views/auth/auth.njk | 89 ++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 29 deletions(-) diff --git a/views/auth/auth.njk b/views/auth/auth.njk index 545135e..eac53d9 100644 --- a/views/auth/auth.njk +++ b/views/auth/auth.njk @@ -16,7 +16,7 @@ {% endif %}
-

Log in

+

Log in

{{ macros.field(_locals, 'text', 'identifier', query.identifier or '', 'Your email address or username', null, 'required') }} @@ -32,7 +32,7 @@
-

Register with email

+

Register

@@ -42,38 +42,69 @@ {{ macros.field(_locals, 'text', 'name', null, 'Choose your username', 'This cannot be changed later.', 'pattern="[0-9a-z_-]+" required') }} {% endif %} - {{ macros.field(_locals, 'email', 'identifier', null, 'Your email address', null, 'required') }} + + + {{ macros.field(_locals, 'checkbox', 'terms', null, 'I accept the Terms Of Services.' | safe, null, 'required') }}
- - {% if register_with_password %} -
-

Register with password

- -
- - {{ macros.csrf(getCsrfToken) }} - -
-

Username

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

Password

- {{ macros.field(_locals, 'password', 'password', null, 'Choose a password', null, 'required') }} - {{ macros.field(_locals, 'password', 'password_confirmation', null, 'Confirm your password', null, 'required') }} -
- - {{ macros.field(_locals, 'checkbox', 'terms', null, 'I accept the Terms Of Services.' | safe, null, 'required') }} - - -
-
- {% endif %} {% endblock %} + +{% block scripts %} + +{% endblock %} From 5caa0be8623029e9f767ddd2b19813410df56f49 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 17:26:14 +0100 Subject: [PATCH 10/12] FormHelperComponent: add field ID prefix to prevent conflicts Fixes #20 --- src/components/FormHelperComponent.ts | 9 +++++++++ views/auth/auth.njk | 2 ++ views/macros.njk | 13 +++++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/FormHelperComponent.ts b/src/components/FormHelperComponent.ts index 93f0693..3f46ace 100644 --- a/src/components/FormHelperComponent.ts +++ b/src/components/FormHelperComponent.ts @@ -23,6 +23,15 @@ export default class FormHelperComponent extends ApplicationComponent { return _previousFormData; }; + + let _formPrefix: string | null; + res.locals.getFormPrefix = () => { + return _formPrefix; + }; + res.locals.setFormPrefix = (formPrefix: string) => { + _formPrefix = formPrefix; + return ''; + }; next(); }); diff --git a/views/auth/auth.njk b/views/auth/auth.njk index eac53d9..9accf33 100644 --- a/views/auth/auth.njk +++ b/views/auth/auth.njk @@ -18,6 +18,7 @@

Log in

+ {{ setFormPrefix('login-') }}
{{ macros.field(_locals, 'text', 'identifier', query.identifier or '', 'Your email address or username', null, 'required') }} @@ -34,6 +35,7 @@

Register

+ {{ setFormPrefix('register-') }} {{ macros.csrf(getCsrfToken) }} diff --git a/views/macros.njk b/views/macros.njk index b62170b..8396aea 100644 --- a/views/macros.njk +++ b/views/macros.njk @@ -37,6 +37,7 @@ {% set validation = validation[name] if validation[name] or null %} {% set previousFormData = _locals.previousFormData() %} {% set value = previousFormData[name] or value or validation.value or '' %} + {% set prefix = _locals.getFormPrefix() | default('no-') %} {% if type == 'hidden' %} {% if validation %} @@ -63,16 +64,16 @@ {% else %} {% set v = (value % 60) if f == 's' else (((value - value % 60) / 60 % 60) if f == 'm' else ((value - value % 3600) / 3600 if f == 'h')) %} {% endif %} - - + {% endfor %} {% elseif type == 'select' %} - {% for option in extraData %} @@ -80,16 +81,16 @@ {% elseif type == 'textarea' %} - {% else %} - {% endif %} - + {{ fieldError(_locals, name) }} From aed825c4d6c3d81d80ce02be51dd0db8900d8f4d Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 17:26:29 +0100 Subject: [PATCH 11/12] FormHelperComponent: don't flash empty previous form data --- src/components/FormHelperComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FormHelperComponent.ts b/src/components/FormHelperComponent.ts index 3f46ace..47527ca 100644 --- a/src/components/FormHelperComponent.ts +++ b/src/components/FormHelperComponent.ts @@ -37,7 +37,7 @@ export default class FormHelperComponent extends ApplicationComponent { router.use((req, res, next) => { if (['GET', 'POST'].find(m => m === req.method)) { - if (typeof req.body === 'object') { + if (typeof req.body === 'object' && Object.keys(req.body).length > 0) { req.flash('previousFormData', req.body); } } From 44f2c2979b90d9dd8eb9161ef1b1c04c2b8533af Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Mon, 25 Jan 2021 17:28:19 +0100 Subject: [PATCH 12/12] Version 0.23.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6641945..9e6b3cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "swaf", - "version": "0.23.2", + "version": "0.23.3", "description": "Structure Web Application Framework.", "repository": "https://eternae.ink/arisu/swaf", "author": "Alice Gaudon ",