Upgrade dependencies

- Add a new migration to fix password hash length `IncreaseMagicLinkTokenLengthMigration`
- Fix types
This commit is contained in:
Alice Gaudon 2024-09-21 18:37:51 +02:00
parent bbd9480000
commit 8ae520aa07
18 changed files with 3114 additions and 2637 deletions

View File

@ -38,7 +38,7 @@
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@types/mjml": "^4.0.4", "@types/mjml": "^4.0.4",
"@types/mysql": "^2.15.10", "@types/mysql": "^2.15.10",
"@types/node": "^17.0.21", "@types/node": "^20.15.0",
"@types/nodemailer": "^6.4.0", "@types/nodemailer": "^6.4.0",
"@types/nunjucks": "^3.1.3", "@types/nunjucks": "^3.1.3",
"@types/on-finished": "^2.3.1", "@types/on-finished": "^2.3.1",
@ -58,7 +58,7 @@
"jest": "^27.3.1", "jest": "^27.3.1",
"jest-resolve": "^27.3.1", "jest-resolve": "^27.3.1",
"jest-ts-webcompat-resolver": "^1.0.0", "jest-ts-webcompat-resolver": "^1.0.0",
"maildev": "^1.1.0", "maildev": "^2.1.0",
"node-fetch": "^3.0.0", "node-fetch": "^3.0.0",
"nodemon": "^2.0.6", "nodemon": "^2.0.6",
"sass": "^1.32.12", "sass": "^1.32.12",

View File

@ -326,8 +326,8 @@ export default abstract class Application implements Extendable<ApplicationCompo
for (const file of fs.readdirSync(configDir)) { for (const file of fs.readdirSync(configDir)) {
const fullPath = path.resolve(configDir, file); const fullPath = path.resolve(configDir, file);
const stats = fs.lstatSync(fullPath); const stats = fs.lstatSync(fullPath);
if (stats.uid !== process.getuid()) if (stats.uid !== process.getuid?.())
throw new SecurityError(`${fullPath} is not owned by this process (${process.getuid()}).`); throw new SecurityError(`${fullPath} is not owned by this process (${process.getuid?.()}).`);
const mode = (stats.mode & parseInt('777', 8)).toString(8); const mode = (stats.mode & parseInt('777', 8)).toString(8);
if (mode !== '400') if (mode !== '400')

View File

@ -4,12 +4,12 @@ import Application from "./Application.js";
import AccountController from "./auth/AccountController.js"; import AccountController from "./auth/AccountController.js";
import AuthComponent from "./auth/AuthComponent.js"; import AuthComponent from "./auth/AuthComponent.js";
import AuthController from "./auth/AuthController.js"; import AuthController from "./auth/AuthController.js";
import AddUsedToMagicLinksMigration from "./auth/magic_link/AddUsedToMagicLinksMigration.js"; import AddUsedToMagicLinksMigration from "./auth/magic_link/migrations/AddUsedToMagicLinksMigration.js";
import CreateMagicLinksTableMigration from "./auth/magic_link/CreateMagicLinksTableMigration.js"; import CreateMagicLinksTableMigration from "./auth/magic_link/migrations/CreateMagicLinksTableMigration.js";
import MagicLinkAuthMethod from "./auth/magic_link/MagicLinkAuthMethod.js"; import MagicLinkAuthMethod from "./auth/magic_link/MagicLinkAuthMethod.js";
import MagicLinkController from "./auth/magic_link/MagicLinkController.js"; import MagicLinkController from "./auth/magic_link/MagicLinkController.js";
import MagicLinkWebSocketListener from "./auth/magic_link/MagicLinkWebSocketListener.js"; import MagicLinkWebSocketListener from "./auth/magic_link/MagicLinkWebSocketListener.js";
import MakeMagicLinksSessionNotUniqueMigration from "./auth/magic_link/MakeMagicLinksSessionNotUniqueMigration.js"; import MakeMagicLinksSessionNotUniqueMigration from "./auth/magic_link/migrations/MakeMagicLinksSessionNotUniqueMigration.js";
import AddApprovedFieldToUsersTableMigration from "./auth/migrations/AddApprovedFieldToUsersTableMigration.js"; import AddApprovedFieldToUsersTableMigration from "./auth/migrations/AddApprovedFieldToUsersTableMigration.js";
import AddNameChangedAtToUsersMigration from "./auth/migrations/AddNameChangedAtToUsersMigration.js"; import AddNameChangedAtToUsersMigration from "./auth/migrations/AddNameChangedAtToUsersMigration.js";
import AddNameToUsersMigration from "./auth/migrations/AddNameToUsersMigration.js"; import AddNameToUsersMigration from "./auth/migrations/AddNameToUsersMigration.js";
@ -42,6 +42,7 @@ import BackendController from "./helpers/BackendController.js";
import MailController from "./mail/MailController.js"; import MailController from "./mail/MailController.js";
import {MAGIC_LINK_MAIL} from "./Mails.js"; import {MAGIC_LINK_MAIL} from "./Mails.js";
import CreateMigrationsTable from "./migrations/CreateMigrationsTable.js"; import CreateMigrationsTable from "./migrations/CreateMigrationsTable.js";
import IncreaseMagicLinkTokenLengthMigration from "./auth/magic_link/migrations/IncreaseMagicLinkTokenLengthMigration.js";
export const MIGRATIONS = [ export const MIGRATIONS = [
CreateMigrationsTable, CreateMigrationsTable,
@ -52,6 +53,7 @@ export const MIGRATIONS = [
MakeMagicLinksSessionNotUniqueMigration, MakeMagicLinksSessionNotUniqueMigration,
AddUsedToMagicLinksMigration, AddUsedToMagicLinksMigration,
AddNameChangedAtToUsersMigration, AddNameChangedAtToUsersMigration,
IncreaseMagicLinkTokenLengthMigration,
]; ];
export default class TestApp extends Application { export default class TestApp extends Application {

View File

@ -83,7 +83,7 @@ export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
} }
private async auth(req: Request, res: Response, isRegistration: boolean, email: string): Promise<void> { private async auth(req: Request, res: Response, isRegistration: boolean, email: string): Promise<void> {
const geo = geoip.lookup(req.ip); const geo = req.ip ? geoip.lookup(req.ip) : null;
const actionType = isRegistration ? AuthMagicLinkActionType.REGISTER : AuthMagicLinkActionType.LOGIN; const actionType = isRegistration ? AuthMagicLinkActionType.REGISTER : AuthMagicLinkActionType.LOGIN;
if (isRegistration) { if (isRegistration) {

View File

@ -1,4 +1,4 @@
import Migration from "../../db/Migration.js"; import Migration from "../../../db/Migration.js";
export default class AddUsedToMagicLinksMigration extends Migration { export default class AddUsedToMagicLinksMigration extends Migration {
public async install(): Promise<void> { public async install(): Promise<void> {

View File

@ -1,6 +1,6 @@
import Migration from "../../db/Migration.js"; import Migration from "../../../db/Migration.js";
import ModelFactory from "../../db/ModelFactory.js"; import ModelFactory from "../../../db/ModelFactory.js";
import MagicLink from "../models/MagicLink.js"; import MagicLink from "../../models/MagicLink.js";
export default class CreateMagicLinksTableMigration extends Migration { export default class CreateMagicLinksTableMigration extends Migration {
public async install(): Promise<void> { public async install(): Promise<void> {

View File

@ -0,0 +1,13 @@
import Migration from "../../../db/Migration.js";
export default class IncreaseMagicLinkTokenLengthMigration extends Migration {
public async install(): Promise<void> {
await this.query(`ALTER TABLE magic_links
MODIFY COLUMN token VARCHAR(128)`);
}
public async rollback(): Promise<void> {
await this.query(`ALTER TABLE magic_links
MODIFY COLUMN token CHAR(96)`);
}
}

View File

@ -1,4 +1,4 @@
import Migration from "../../db/Migration.js"; import Migration from "../../../db/Migration.js";
export default class MakeMagicLinksSessionNotUniqueMigration extends Migration { export default class MakeMagicLinksSessionNotUniqueMigration extends Migration {
public async install(): Promise<void> { public async install(): Promise<void> {

View File

@ -26,7 +26,7 @@ export default class MagicLink extends Model implements AuthProof<User> {
protected init(): void { protected init(): void {
this.setValidation('session_id').defined().length(32); this.setValidation('session_id').defined().length(32);
this.setValidation('email').defined().regexp(EMAIL_REGEX); this.setValidation('email').defined().regexp(EMAIL_REGEX);
this.setValidation('token').defined().length(96); this.setValidation('token').defined().maxLength(128);
this.setValidation('action_type').defined().maxLength(64); this.setValidation('action_type').defined().maxLength(64);
this.setValidation('original_url').defined().maxLength(1745); this.setValidation('original_url').defined().maxLength(1745);
this.setValidation('authorized').defined(); this.setValidation('authorized').defined();

View File

@ -74,7 +74,7 @@ export default class PasswordAuthMethod implements AuthMethod<PasswordAuthProof>
Throttler.throttle('login_failed_attempts_user', 3, 3 * 60 * 1000, // 3min Throttler.throttle('login_failed_attempts_user', 3, 3 * 60 * 1000, // 3min
user.getOrFail('id').toString(), 1000, 60 * 1000); // 1min user.getOrFail('id').toString(), 1000, 60 * 1000); // 1min
Throttler.throttle('login_failed_attempts_ip', 50, 60 * 1000, // 1min Throttler.throttle('login_failed_attempts_ip', 50, 60 * 1000, // 1min
req.ip, 1000, 3600 * 1000); // 1h req.ip || "", 1000, 3600 * 1000); // 1h
if (e instanceof PendingApprovalAuthError) { if (e instanceof PendingApprovalAuthError) {
req.flash('error', 'Your account is still being reviewed.'); req.flash('error', 'Your account is still being reviewed.');
@ -98,7 +98,7 @@ export default class PasswordAuthMethod implements AuthMethod<PasswordAuthProof>
if (!ModelFactory.get(User).hasComponent(UserNameComponent)) if (!ModelFactory.get(User).hasComponent(UserNameComponent))
throw new ServerError('Cannot register with password without UserNameComponent.'); throw new ServerError('Cannot register with password without UserNameComponent.');
Throttler.throttle('register_password', 10, 30000, req.ip); Throttler.throttle('register_password', 10, 30000, req.ip || "");
req.body.identifier = identifier; req.body.identifier = identifier;

View File

@ -59,8 +59,8 @@ export default abstract class Model implements Extendable<ModelComponent<Model>>
[key: string]: ModelFieldData; [key: string]: ModelFieldData;
public constructor(factory: ModelFactory<never>, isNew: boolean) { public constructor(isNew: boolean) {
if (!(factory instanceof ModelFactory)) throw new Error('Cannot instantiate model directly.'); const factory = ModelFactory.get(this.constructor as ModelType<Model>);
this._factory = factory; this._factory = factory;
this.init?.(); this.init?.();
this._exists = !isNew; this._exists = !isNew;
@ -245,10 +245,10 @@ export default abstract class Model implements Extendable<ModelComponent<Model>>
} }
} }
export interface ModelType<M extends Model> extends Type<M> { export interface ModelType<M extends Model> {
table: string; table: string;
new(factory: ModelFactory<never>, isNew: boolean): M; new(isNew: boolean): M;
getPrimaryKeyFields(): (keyof M & string)[]; getPrimaryKeyFields(): (keyof M & string)[];

View File

@ -40,7 +40,7 @@ export default class ModelFactory<M extends Model> {
} }
public create(data: Pick<M, keyof M>, isNewModel: boolean): M { public create(data: Pick<M, keyof M>, isNewModel: boolean): M {
const model = new this.modelType(this as unknown as ModelFactory<never>, isNewModel); const model = new this.modelType(isNewModel);
for (const component of this.components) { for (const component of this.components) {
model.addComponent(new component(model)); model.addComponent(new component(model));
} }

View File

@ -2,14 +2,14 @@ import {Connection} from "mysql";
import {ServerError} from "../HttpError.js"; import {ServerError} from "../HttpError.js";
import Model, {ModelType} from "./Model.js"; import Model, {ModelType} from "./Model.js";
import ModelQuery, {WhereTest} from "./ModelQuery.js"; import ModelQuery, {ModelFieldData, WhereTest} from "./ModelQuery.js";
export const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/; export const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
export default class Validator<V> { export default class Validator<V extends ModelFieldData> {
public static async validate( public static async validate(
validationMap: { [p: string]: Validator<unknown> }, validationMap: { [p: string]: Validator<ModelFieldData> },
body: { [p: string]: unknown }, body: { [p: string]: ModelFieldData },
): Promise<void> { ): Promise<void> {
const bag = new ValidationBag(); const bag = new ValidationBag();

View File

@ -27,7 +27,7 @@ describe('Register with username and password (password)', () => {
test('General case', async () => { test('General case', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie'); cookies = res.get('Set-Cookie') ?? [];
csrf = res.text; csrf = res.text;
// Register user // Register user
@ -90,7 +90,7 @@ describe('Register with username and password (password)', () => {
.count()).toStrictEqual(0); .count()).toStrictEqual(0);
const res1 = await agent.get('/csrf').expect(200); const res1 = await agent.get('/csrf').expect(200);
const cookies = res1.get('Set-Cookie'); const cookies = res1.get('Set-Cookie') || [];
const csrf = res1.text; const csrf = res1.text;
// Register user // Register user
@ -139,7 +139,7 @@ describe('Register with username and password (password)', () => {
describe('Register with email (magic_link)', () => { describe('Register with email (magic_link)', () => {
test('General case', async () => { test('General case', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
await agent.post('/auth/register?' + querystring.stringify({redirect_uri: '/redirect-uri'})) await agent.post('/auth/register?' + querystring.stringify({redirect_uri: '/redirect-uri'}))
@ -180,7 +180,7 @@ describe('Register with email (magic_link)', () => {
test('Cannot register without specifying username', async () => { test('Cannot register without specifying username', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
res = await agent.post('/auth/register') res = await agent.post('/auth/register')
@ -198,7 +198,7 @@ describe('Register with email (magic_link)', () => {
test('Cannot register taken username', async () => { test('Cannot register taken username', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -240,7 +240,7 @@ describe('Register with email (magic_link)', () => {
test('Cannot register taken email', async () => { test('Cannot register taken email', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -284,7 +284,7 @@ describe('Register with email (magic_link)', () => {
describe('Authenticate with username and password (password)', () => { describe('Authenticate with username and password (password)', () => {
test('Force auth_method', async () => { test('Force auth_method', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -319,7 +319,7 @@ describe('Authenticate with username and password (password)', () => {
test('Automatic auth_method', async () => { test('Automatic auth_method', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -352,7 +352,7 @@ describe('Authenticate with username and password (password)', () => {
test('Non-existing username', async () => { test('Non-existing username', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -385,7 +385,7 @@ describe('Authenticate with username and password (password)', () => {
test('No password user', async () => { test('No password user', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -440,13 +440,14 @@ describe('Authenticate with username and password (password)', () => {
describe('Authenticate with email (magic_link)', () => { describe('Authenticate with email (magic_link)', () => {
test('Force auth_method', async () => { test('Force auth_method', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401); await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Authenticate // Authenticate
// TODO deprecated querystring => URLSearchParams
await agent.post('/auth/login?' + querystring.stringify({redirect_uri: '/redirect-uri'})) await agent.post('/auth/login?' + querystring.stringify({redirect_uri: '/redirect-uri'}))
.set('Cookie', cookies) .set('Cookie', cookies)
.send({ .send({
@ -464,7 +465,7 @@ describe('Authenticate with email (magic_link)', () => {
test('Automatic auth_method', async () => { test('Automatic auth_method', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -487,7 +488,7 @@ describe('Authenticate with email (magic_link)', () => {
test('Non-existing email (forced auth_method)', async () => { test('Non-existing email (forced auth_method)', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -508,7 +509,7 @@ describe('Authenticate with email (magic_link)', () => {
test('Non-existing email (automatic auth_method)', async () => { test('Non-existing email (automatic auth_method)', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -530,7 +531,7 @@ describe('Authenticate with email (magic_link)', () => {
describe('Authenticate with email and password (password)', () => { describe('Authenticate with email and password (password)', () => {
test('Prepare user', async () => { test('Prepare user', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -569,7 +570,7 @@ describe('Authenticate with email and password (password)', () => {
test('Force auth_method', async () => { test('Force auth_method', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -604,7 +605,7 @@ describe('Authenticate with email and password (password)', () => {
test('Automatic auth_method', async () => { test('Automatic auth_method', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -641,7 +642,7 @@ describe('Change password', () => {
let cookies: string[], csrf: string; let cookies: string[], csrf: string;
test('Prepare user', async () => { test('Prepare user', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie'); cookies = res.get('Set-Cookie') || [];
csrf = res.text; csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -821,7 +822,7 @@ describe('Change password', () => {
test('Can\'t remove password without contact email', async () => { test('Can\'t remove password without contact email', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie'); cookies = res.get('Set-Cookie') || [];
csrf = res.text; csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -863,7 +864,7 @@ describe('Change username', () => {
let cookies: string[], csrf: string; let cookies: string[], csrf: string;
test('Prepare user', async () => { test('Prepare user', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie'); cookies = res.get('Set-Cookie') || [];
csrf = res.text; csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -962,7 +963,7 @@ describe('Manage email addresses', () => {
let cookies: string[], csrf: string; let cookies: string[], csrf: string;
test('Prepare user', async () => { test('Prepare user', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie'); cookies = res.get('Set-Cookie') || [];
csrf = res.text; csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -1169,7 +1170,7 @@ describe('Session persistence', () => {
test('Not persistent at registration', async () => { test('Not persistent at registration', async () => {
let res = await agent.get('/csrf').expect(200); let res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie'); cookies = res.get('Set-Cookie') || [];
csrf = res.text; csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -1190,7 +1191,7 @@ describe('Session persistence', () => {
res = await agent.get('/csrf') res = await agent.get('/csrf')
.set('Cookie', cookies) .set('Cookie', cookies)
.expect(200); .expect(200);
expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; Expires=.+; SameSite=Strict$/); expect(res.get('Set-Cookie')?.[0]).toMatch(/^connect\.sid=.+; Path=\/; Expires=.+; SameSite=Strict$/);
// Logout // Logout
await agent.post('/auth/logout') await agent.post('/auth/logout')
@ -1217,7 +1218,7 @@ describe('Session persistence', () => {
const res = await agent.get('/csrf') const res = await agent.get('/csrf')
.set('Cookie', cookies) .set('Cookie', cookies)
.expect(200); .expect(200);
expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; Expires=.+; SameSite=Strict$/); expect(res.get('Set-Cookie')?.[0]).toMatch(/^connect\.sid=.+; Path=\/; Expires=.+; SameSite=Strict$/);
// Logout // Logout
await agent.post('/auth/logout') await agent.post('/auth/logout')
@ -1244,7 +1245,7 @@ describe('Session persistence', () => {
const res = await agent.get('/csrf') const res = await agent.get('/csrf')
.set('Cookie', cookies) .set('Cookie', cookies)
.expect(200); .expect(200);
expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; SameSite=Strict$/); expect(res.get('Set-Cookie')?.[0]).toMatch(/^connect\.sid=.+; Path=\/; SameSite=Strict$/);
// Logout // Logout
await agent.post('/auth/logout') await agent.post('/auth/logout')

View File

@ -25,7 +25,7 @@ describe('Register with username and password (password)', () => {
test('General case', async () => { test('General case', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie'); cookies = res.get('Set-Cookie') || [];
csrf = res.text; csrf = res.text;
// Register user // Register user
@ -63,7 +63,7 @@ describe('Register with username and password (password)', () => {
describe('Register with email (magic_link)', () => { describe('Register with email (magic_link)', () => {
test('General case', async () => { test('General case', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
await agent.post('/auth/register?' + querystring.stringify({redirect_uri: '/redirect-uri'})) await agent.post('/auth/register?' + querystring.stringify({redirect_uri: '/redirect-uri'}))
@ -106,7 +106,7 @@ describe('Register with email (magic_link)', () => {
describe('Authenticate with username and password (password)', () => { describe('Authenticate with username and password (password)', () => {
test('Force auth_method', async () => { test('Force auth_method', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated
@ -134,7 +134,7 @@ describe('Authenticate with username and password (password)', () => {
describe('Authenticate with email (magic_link)', () => { describe('Authenticate with email (magic_link)', () => {
test('Force auth_method', async () => { test('Force auth_method', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Not authenticated // Not authenticated

View File

@ -22,7 +22,7 @@ test('Approval Mode', () => {
describe('Register with username and password (password)', () => { describe('Register with username and password (password)', () => {
test('Must be disabled', async () => { test('Must be disabled', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
// Register user // Register user
@ -43,7 +43,7 @@ describe('Register with username and password (password)', () => {
describe('Register with email (magic_link)', () => { describe('Register with email (magic_link)', () => {
test('General case', async () => { test('General case', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -77,7 +77,7 @@ describe('Register with email (magic_link)', () => {
test('Cannot register taken email', async () => { test('Cannot register taken email', async () => {
const res = await agent.get('/csrf').expect(200); const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie'); const cookies = res.get('Set-Cookie') || [];
const csrf = res.text; const csrf = res.text;
await agent.post('/auth/register') await agent.post('/auth/register')
@ -106,7 +106,7 @@ describe('Register with email (magic_link)', () => {
const res2 = await agent.get('/csrf').expect(200); const res2 = await agent.get('/csrf').expect(200);
await agent.post('/auth/register') await agent.post('/auth/register')
.set('Cookie', res2.get('Set-Cookie')) .set('Cookie', res2.get('Set-Cookie') || [])
.send({ .send({
csrf: res2.text, csrf: res2.text,
auth_method: 'magic_link', auth_method: 'magic_link',

View File

@ -39,7 +39,7 @@ describe('Test CSRF protection', () => {
.expect(401) .expect(401)
.then(res => { .then(res => {
expect(res.text).toContain(`You weren't assigned any CSRF token.`); expect(res.text).toContain(`You weren't assigned any CSRF token.`);
cookies = res.get('Set-Cookie'); cookies = res.get('Set-Cookie') || [];
agent.get('/') agent.get('/')
.set('Cookie', cookies) .set('Cookie', cookies)

5617
yarn.lock

File diff suppressed because it is too large Load Diff