swaf/test/Authentication.test.ts

1257 lines
43 KiB
TypeScript

import * as querystring from "querystring";
import supertest from "supertest";
import User from "../src/auth/models/User.js";
import UserEmail from "../src/auth/models/UserEmail.js";
import UserNameComponent from "../src/auth/models/UserNameComponent.js";
import UserPasswordComponent from "../src/auth/password/UserPasswordComponent.js";
import useApp from "./_app.js";
import {authAppProvider, followMagicLinkFromMail, testLogout} from "./_authentication_common.js";
import {popEmail} from "./_mail_server.js";
const app = useApp(authAppProvider());
let agent: supertest.SuperTest<supertest.Test>;
beforeAll(() => {
agent = supertest(app().getExpressApp());
});
test('Approval Mode', () => {
expect(User.isApprovalMode()).toStrictEqual(false);
});
describe('Register with username and password (password)', () => {
let cookies: string[];
let csrf: string;
test('General case', async () => {
const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie');
csrf = res.text;
// Register user
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'password',
identifier: 'entrapta',
password: 'darla_is_cute',
password_confirmation: 'darla_is_cute',
terms: 'on',
})
.expect(302)
.expect('Location', '/');
// Verify saved user
const user = await User.select()
.where('name', 'entrapta')
.first();
expect(user).toBeDefined();
expect(user?.as(UserNameComponent).getName()).toStrictEqual('entrapta');
await expect(user?.as(UserPasswordComponent).verifyPassword('darla_is_cute')).resolves.toStrictEqual(true);
// Proof must not be revoked
await agent.get('/has-any-password-auth-proof')
.set('Cookie', cookies)
.expect(200);
});
test('Can\'t register when logged in', async () => {
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'password',
identifier: 'entrapta2',
password: 'darla_is_cute',
password_confirmation: 'darla_is_cute',
terms: 'on',
})
.expect(302)
.expect('Location', '/');
const user2 = await User.select()
.where('name', 'entrapta2')
.first();
expect(user2).toBeNull();
});
test('Logout', async () => {
await testLogout(agent, cookies, csrf);
});
test('Cannot register taken username', async () => {
// Check that there is no hordak in DB
expect(await User.select()
.where('name', 'hordak')
.count()).toStrictEqual(0);
const res1 = await agent.get('/csrf').expect(200);
const cookies = res1.get('Set-Cookie');
const csrf = res1.text;
// Register user
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'password',
identifier: 'hordak',
password: 'horde_prime_will_rise',
password_confirmation: 'horde_prime_will_rise',
terms: 'on',
})
.expect(302)
.expect('Location', '/');
await testLogout(agent, cookies, csrf);
// Verify saved user
expect(await User.select()
.where('name', 'hordak')
.count()).toStrictEqual(1);
// Attempt register same user
const res = await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'password',
identifier: 'hordak',
password: 'horde_prime_will_rise_unless',
password_confirmation: 'horde_prime_will_rise_unless',
terms: 'on',
})
.expect(400);
// username field should be translated from identifier
expect(res.body.messages?.identifier?.name).toStrictEqual('AlreadyExistsValidationError');
// Verify nothing changed
expect(await User.select()
.where('name', 'hordak')
.count()).toStrictEqual(1);
});
});
describe('Register with email (magic_link)', () => {
test('General case', async () => {
const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
await agent.post('/auth/register?' + querystring.stringify({redirect_uri: '/redirect-uri'}))
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'glimmer@example.org',
name: 'glimmer',
})
.expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fredirect-uri');
await followMagicLinkFromMail(agent, cookies);
// Proof must not be revoked
await agent.get('/has-any-magic-link')
.set('Cookie', cookies)
.expect(200);
await testLogout(agent, cookies, csrf);
// Verify saved user
const user = await User.select()
.with('mainEmail')
.where('name', 'glimmer')
.first();
expect(user).toBeDefined();
const email = user?.mainEmail.getOrFail();
expect(email).toBeDefined();
expect(email?.email).toStrictEqual('glimmer@example.org');
expect(user?.as(UserNameComponent).getName()).toStrictEqual('glimmer');
await expect(user?.as(UserPasswordComponent).verifyPassword('')).resolves.toStrictEqual(false);
});
test('Cannot register without specifying username', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
res = await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'no_user_name@example.org',
})
.expect(400);
expect(res.body.messages?.name?.name).toStrictEqual('UndefinedValueValidationError');
expect(await popEmail()).toBeNull();
});
test('Cannot register taken username', async () => {
const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'angella@example.org',
name: 'angella',
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
// Verify saved user
const user = await User.select()
.with('mainEmail')
.where('name', 'angella')
.first();
expect(user).toBeDefined();
// Attempt register with another mail but same username
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'angella_something_else@example.org',
name: 'angella',
})
.expect(400);
expect(await popEmail()).toBeNull();
});
test('Cannot register taken email', async () => {
const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'bow@example.org',
name: 'bow',
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
// Verify saved user
const user = await User.select()
.with('mainEmail')
.where('name', 'bow')
.first();
expect(user).toBeDefined();
// Attempt register with another mail but same username
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'bow@example.org',
name: 'bow2',
})
.expect(400);
expect(await popEmail()).toBeNull();
});
});
describe('Authenticate with username and password (password)', () => {
test('Force auth_method', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Bad password
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'entrapta',
password: 'darla_is_not_cute',
auth_method: 'password',
})
.expect(400);
expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError');
// Authenticate
await agent.post('/auth/login?' + querystring.stringify({redirect_uri: '/redirect-uri'}))
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'entrapta',
password: 'darla_is_cute',
auth_method: 'password',
})
.expect(302)
.expect('Location', '/redirect-uri');
await testLogout(agent, cookies, csrf);
});
test('Automatic auth_method', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Bad password
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'entrapta',
password: 'darla_is_not_cute',
})
.expect(400);
expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError');
// Authenticate
await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'entrapta',
password: 'darla_is_cute',
})
.expect(302)
.expect('Location', '/');
await testLogout(agent, cookies, csrf);
});
test('Non-existing username', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Authenticate
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'i_do_not_exist',
password: 'there_is_no_point',
auth_method: 'password',
})
.expect(400);
expect(res.body.messages?.identifier?.name).toStrictEqual('UnknownRelationValidationError');
// Authenticate (automatic method)
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'i_do_not_exist',
password: 'there_is_no_point',
auth_method: 'password',
})
.expect(400);
expect(res.body.messages?.identifier?.name).toStrictEqual('UnknownRelationValidationError');
});
test('No password user', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Authenticate
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'glimmer',
password: '',
auth_method: 'password',
})
.expect(400);
expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError');
// Authenticate (automatic method)
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'glimmer',
password: '',
})
.expect(400);
expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError');
// Authenticate without password
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'angella',
auth_method: 'password',
})
.expect(400);
expect(res.body.messages?.password?.name).toStrictEqual('UndefinedValueValidationError');
// Authenticate without password (automatic method)
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'angella',
})
.expect(400);
expect(res.body.messages?.password?.name).toStrictEqual('UndefinedValueValidationError');
});
});
describe('Authenticate with email (magic_link)', () => {
test('Force auth_method', async () => {
const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Authenticate
await agent.post('/auth/login?' + querystring.stringify({redirect_uri: '/redirect-uri'}))
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'glimmer@example.org',
auth_method: 'magic_link',
})
.expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fredirect-uri');
await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
});
test('Automatic auth_method', async () => {
const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Authenticate
await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'angella@example.org',
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
});
test('Non-existing email (forced auth_method)', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Authenticate
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'i_do_not_exist@invalid.org',
auth_method: 'magic_link',
})
.expect(400);
expect(res.body.messages?.identifier?.name).toStrictEqual('UnknownRelationValidationError');
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
});
test('Non-existing email (automatic auth_method)', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Authenticate
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'i_do_not_exist@invalid.org',
})
.expect(400);
expect(res.body.messages?.identifier?.name).toStrictEqual('UnknownRelationValidationError');
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
});
});
describe('Authenticate with email and password (password)', () => {
test('Prepare user', async () => {
const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'double-trouble@example.org',
name: 'double-trouble',
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
// Verify saved user
const user = await User.select()
.with('mainEmail')
.where('name', 'double-trouble')
.first();
await user?.as(UserPasswordComponent).setPassword('trick-or-treat');
await user?.save();
expect(user).toBeDefined();
const email = user?.mainEmail.getOrFail();
expect(email).toBeDefined();
expect(email?.email).toStrictEqual('double-trouble@example.org');
expect(user?.as(UserNameComponent).getName()).toStrictEqual('double-trouble');
await expect(user?.as(UserPasswordComponent).verifyPassword('trick-or-treat')).resolves.toStrictEqual(true);
});
test('Force auth_method', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Bad password
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'double-trouble@example.org',
password: 'i_have_no_imagination',
auth_method: 'password',
})
.expect(400);
expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError');
// Authenticate
await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'double-trouble@example.org',
password: 'trick-or-treat',
auth_method: 'password',
})
.expect(302)
.expect('Location', '/');
await testLogout(agent, cookies, csrf);
});
test('Automatic auth_method', async () => {
let res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const csrf = res.text;
// Not authenticated
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
// Bad password
res = await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'double-trouble@example.org',
password: 'i_have_no_imagination',
})
.expect(400);
expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError');
// Authenticate
await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
identifier: 'double-trouble@example.org',
password: 'trick-or-treat',
})
.expect(302)
.expect('Location', '/');
await testLogout(agent, cookies, csrf);
});
});
describe('Change password', () => {
let cookies: string[], csrf: string;
test('Prepare user', async () => {
const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie');
csrf = res.text;
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'aang@example.org',
name: 'aang',
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
});
test('Set password to blank from blank', async () => {
await agent.post('/account/change-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
'current_password': '',
'new_password': '',
'new_password_confirmation': '',
})
.expect(400);
const user = await User.select()
.where('name', 'aang')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy();
});
test('Set password to something from blank', async () => {
await agent.post('/account/change-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
'current_password': '',
'new_password': 'a_very_strong_password',
'new_password_confirmation': 'a_very_strong_password',
})
.expect(302)
.expect('Location', '/account/');
const user = await User.select()
.where('name', 'aang')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy();
expect(await user?.as(UserPasswordComponent).verifyPassword('')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password')).toBeTruthy();
});
test('Set password to blank from something', async () => {
await agent.post('/account/change-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
'current_password': 'a_very_strong_password',
'new_password': '',
})
.expect(400);
const user = await User.select()
.where('name', 'aang')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy();
expect(await user?.as(UserPasswordComponent).verifyPassword('')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password')).toBeTruthy();
});
test('Set password to something from something', async () => {
await agent.post('/account/change-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
'current_password': 'a_very_strong_password',
'new_password': 'a_very_strong_password_but_different',
'new_password_confirmation': 'a_very_strong_password_but_different',
})
.expect(302)
.expect('Location', '/account/');
const user = await User.select()
.where('name', 'aang')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy();
expect(await user?.as(UserPasswordComponent).verifyPassword('')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password_but_different')).toBeTruthy();
});
test('Set password to unconfirmed', async () => {
await agent.post('/account/change-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
'current_password': 'a_very_strong_password_but_different',
'new_password': 'a_very_strong_password',
'new_password_confirmation': '',
})
.expect(400);
await agent.post('/account/change-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
'current_password': 'a_very_strong_password_but_different',
'new_password': 'a_very_strong_password',
'new_password_confirmation': 'a_very_strong_password_but_different2',
})
.expect(400);
const user = await User.select()
.where('name', 'aang')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy();
expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password')).toBeFalsy();
expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password_but_different')).toBeTruthy();
expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password_but_different2')).toBeFalsy();
});
test('Set password to too short password', async () => {
await agent.post('/account/change-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
'current_password': 'a_very_strong_password_but_different',
'new_password': '123',
'new_password_confirmation': '123',
})
.expect(400);
const user = await User.select()
.where('name', 'aang')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy();
expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password_but_different')).toBeTruthy();
expect(await user?.as(UserPasswordComponent).verifyPassword('123')).toBeFalsy();
});
test('Remove password', async () => {
let user = await User.select()
.where('name', 'aang')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBe(true);
await agent.post('/account/remove-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
})
.expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Faccount%2F');
await followMagicLinkFromMail(agent, cookies, '/account/');
user = await User.select()
.where('name', 'aang')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBe(false);
});
test('Can\'t remove password without contact email', async () => {
const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie');
csrf = res.text;
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'password',
identifier: 'eleanor',
password: 'this_is_a_very_strong_password',
password_confirmation: 'this_is_a_very_strong_password',
terms: 'on',
})
.expect(302)
.expect('Location', '/');
let user = await User.select()
.where('name', 'eleanor')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBe(true);
await agent.post('/account/remove-password')
.set('Cookie', cookies)
.send({
csrf: csrf,
})
.expect(302)
.expect('Location', '/account/');
user = await User.select()
.where('name', 'eleanor')
.first();
expect(user).toBeDefined();
expect(user?.as(UserPasswordComponent).hasPassword()).toBe(true);
});
});
describe('Change username', () => {
let cookies: string[], csrf: string;
test('Prepare user', async () => {
const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie');
csrf = res.text;
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'password',
identifier: 'richard',
password: 'a_very_strong_password',
password_confirmation: 'a_very_strong_password',
terms: 'on',
})
.expect(302)
.expect('Location', '/');
});
test('Initial value of name_changed_at should be the same as created_at', async () => {
const user = await User.select().where('name', 'richard').first();
expect(user).toBeDefined();
expect(user?.as(UserNameComponent).getNameChangedAt()?.getTime()).toBe(user?.created_at?.getTime());
});
test('Cannot change username just after registration', async () => {
expect(await User.select().where('name', 'richard').count()).toBe(1);
await agent.post('/account/change-name')
.set('Cookie', cookies)
.send({
csrf: csrf,
name: 'robert',
})
.expect(302)
.expect('Location', '/account/');
expect(await User.select().where('name', 'richard').count()).toBe(1);
});
test('Can change username after hold period', async () => {
// Set last username change to older date
const user = await User.select().where('name', 'richard').first();
if (user) {
user.as(UserNameComponent).forgetNameChangeDate();
await user.save();
}
expect(await User.select().where('name', 'richard').count()).toBe(1);
expect(await User.select().where('name', 'robert').count()).toBe(0);
await agent.post('/account/change-name')
.set('Cookie', cookies)
.send({
csrf: csrf,
name: 'robert',
})
.expect(302)
.expect('Location', '/account/');
expect(await User.select().where('name', 'richard').count()).toBe(0);
expect(await User.select().where('name', 'robert').count()).toBe(1);
});
test('Cannot change username just after changing username', async () => {
expect(await User.select().where('name', 'robert').count()).toBe(1);
expect(await User.select().where('name', 'bebert').count()).toBe(0);
await agent.post('/account/change-name')
.set('Cookie', cookies)
.send({
csrf: csrf,
name: 'bebert',
})
.expect(302)
.expect('Location', '/account/');
expect(await User.select().where('name', 'robert').count()).toBe(1);
expect(await User.select().where('name', 'bebert').count()).toBe(0);
});
});
describe('Manage email addresses', () => {
async function testMainSecondaryState(main: string, secondary: string) {
const user = await User.select('main_email_id').where('name', 'katara').first();
const mainEmail = await UserEmail.select().where('email', main).first();
expect(mainEmail).not.toBeNull();
expect(user?.main_email_id).toBe(mainEmail?.id);
const secondaryEmail = await UserEmail.select().where('email', secondary).first();
expect(secondaryEmail).not.toBeNull();
expect(user?.main_email_id).not.toBe(secondaryEmail?.id);
return secondaryEmail;
}
let cookies: string[], csrf: string;
test('Prepare user', async () => {
const res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie');
csrf = res.text;
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'katara@example.org',
name: 'katara',
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
// Add fake, not owned secondary email
const user = User.create({
name: 'not_katara',
});
await user.save();
await UserEmail.create({
email: 'not_katara@example.org',
user_id: user.id,
}).save();
});
describe('Add', () => {
test('Add invalid email addresses', async () => {
await agent.post('/account/add-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
})
.expect(400);
await agent.post('/account/add-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
email: '',
})
.expect(400);
await agent.post('/account/add-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
email: 'katara@example.org',
})
.expect(400);
expect(await UserEmail.select().where('email', 'katara@example.org').count()).toBe(1);
});
test('Add valid email addresses', async () => {
const expectedUserId = (await User.select('id').where('name', 'katara').first())?.id;
for (const email of [
'katara2@example.org',
'katara3@example.org',
'katara4@example.org',
]) {
await agent.post('/account/add-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
email: email,
})
.expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Faccount%2F');
await followMagicLinkFromMail(agent, cookies, '/account/');
const userEmail = await UserEmail.select().where('email', email).first();
expect(userEmail).not.toBeNull();
expect(userEmail?.user_id).toBe(expectedUserId);
}
});
});
describe('Set main', () => {
test('Set main email address as main email address', async () => {
await testMainSecondaryState('katara@example.org', 'katara3@example.org');
// Set secondary as main
await agent.post('/account/set-main-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
id: (await UserEmail.select().where('email', 'katara@example.org').first())?.id,
})
.expect(400);
await testMainSecondaryState('katara@example.org', 'katara3@example.org');
});
test('Set secondary email address as main email address', async () => {
const beforeSecondaryEmail = await testMainSecondaryState('katara@example.org', 'katara3@example.org');
// Set secondary as main
await agent.post('/account/set-main-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
id: beforeSecondaryEmail?.id,
})
.expect(302)
.expect('Location', '/account/');
await testMainSecondaryState('katara3@example.org', 'katara@example.org');
});
test('Set non-owned address as main email address', async () => {
const beforeSecondaryEmail = await testMainSecondaryState('katara3@example.org', 'not_katara@example.org');
// Set secondary as main
await agent.post('/account/set-main-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
id: beforeSecondaryEmail?.id,
})
.expect(403);
await testMainSecondaryState('katara3@example.org', 'not_katara@example.org');
});
test('Set non-existing address as main email address', async () => {
await testMainSecondaryState('katara3@example.org', 'katara4@example.org');
// Set secondary as main
await agent.post('/account/set-main-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
id: 999,
})
.expect(404);
await testMainSecondaryState('katara3@example.org', 'katara4@example.org');
});
});
describe('Remove', () => {
test('Remove secondary email address', async () => {
expect(await UserEmail.select().where('email', 'katara2@example.org').count()).toBe(1);
// Set secondary as main
await agent.post('/account/remove-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
id: (await UserEmail.select().where('email', 'katara2@example.org').first())?.id,
})
.expect(302)
.expect('Location', '/account/');
expect(await UserEmail.select().where('email', 'katara2@example.org').count()).toBe(0);
});
test('Remove non-owned email address', async () => {
expect(await UserEmail.select().where('email', 'not_katara@example.org').count()).toBe(1);
// Set secondary as main
await agent.post('/account/remove-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
id: (await UserEmail.select().where('email', 'not_katara@example.org').first())?.id,
})
.expect(403);
expect(await UserEmail.select().where('email', 'not_katara@example.org').count()).toBe(1);
});
test('Remove non-existing email address', async () => {
// Set secondary as main
await agent.post('/account/remove-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
id: 999,
})
.expect(404);
});
test('Remove main email address', async () => {
expect(await UserEmail.select().where('email', 'katara3@example.org').count()).toBe(1);
// Set secondary as main
await agent.post('/account/remove-email')
.set('Cookie', cookies)
.send({
csrf: csrf,
id: (await UserEmail.select().where('email', 'katara3@example.org').first())?.id,
})
.expect(400);
expect(await UserEmail.select().where('email', 'katara3@example.org').count()).toBe(1);
});
});
});
describe('Session persistence', () => {
let cookies: string[], csrf: string;
test('Not persistent at registration', async () => {
let res = await agent.get('/csrf').expect(200);
cookies = res.get('Set-Cookie');
csrf = res.text;
await agent.post('/auth/register')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'zuko@example.org',
name: 'zuko',
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
expect(cookies[0]).toMatch(/^connect\.sid=.+; Path=\/; SameSite=Strict$/);
res = await agent.get('/csrf')
.set('Cookie', cookies)
.expect(200);
expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; Expires=.+; SameSite=Strict$/);
// Logout
await agent.post('/auth/logout')
.set('Cookie', cookies)
.send({csrf})
.expect(302)
.expect('Location', '/');
});
test('Login with persistence', async () => {
await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'zuko@example.org',
persist_session: 'on',
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
const res = await agent.get('/csrf')
.set('Cookie', cookies)
.expect(200);
expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; Expires=.+; SameSite=Strict$/);
// Logout
await agent.post('/auth/logout')
.set('Cookie', cookies)
.send({csrf})
.expect(302)
.expect('Location', '/');
});
test('Login without persistence', async () => {
await agent.post('/auth/login')
.set('Cookie', cookies)
.send({
csrf: csrf,
auth_method: 'magic_link',
identifier: 'zuko@example.org',
persist_session: undefined,
})
.expect(302)
.expect('Location', '/magic/lobby');
await followMagicLinkFromMail(agent, cookies);
const res = await agent.get('/csrf')
.set('Cookie', cookies)
.expect(200);
expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; SameSite=Strict$/);
// Logout
await agent.post('/auth/logout')
.set('Cookie', cookies)
.send({csrf})
.expect(302)
.expect('Location', '/');
});
});