Approval mode: revoke unapproved users auth proofs
Also add tests for auth approval mode
This commit is contained in:
parent
85e23b7f42
commit
cfc632ba1a
@ -1,14 +1,18 @@
|
|||||||
{
|
{
|
||||||
mysql: {
|
mysql: {
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
user: "root",
|
user: "root",
|
||||||
password: "",
|
password: "",
|
||||||
database: "swaf_test",
|
database: "swaf_test",
|
||||||
create_database_automatically: true,
|
create_database_automatically: true,
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
cookie: {
|
cookie: {
|
||||||
maxAge: 1000, // 1s
|
// 1s
|
||||||
},
|
maxAge: 1000,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
approval_mode: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,10 @@ import Controller from "./Controller";
|
|||||||
import AccountController from "./auth/AccountController";
|
import AccountController from "./auth/AccountController";
|
||||||
import MakeMagicLinksSessionNotUniqueMigration from "./auth/magic_link/MakeMagicLinksSessionNotUniqueMigration";
|
import MakeMagicLinksSessionNotUniqueMigration from "./auth/magic_link/MakeMagicLinksSessionNotUniqueMigration";
|
||||||
import AddUsedToMagicLinksMigration from "./auth/magic_link/AddUsedToMagicLinksMigration";
|
import AddUsedToMagicLinksMigration from "./auth/magic_link/AddUsedToMagicLinksMigration";
|
||||||
import packageJson = require('./package.json');
|
|
||||||
import PreviousUrlComponent from "./components/PreviousUrlComponent";
|
import PreviousUrlComponent from "./components/PreviousUrlComponent";
|
||||||
import AddNameChangedAtToUsersMigration from "./auth/migrations/AddNameChangedAtToUsersMigration";
|
import AddNameChangedAtToUsersMigration from "./auth/migrations/AddNameChangedAtToUsersMigration";
|
||||||
|
import BackendController from "./helpers/BackendController";
|
||||||
|
import packageJson = require('./package.json');
|
||||||
|
|
||||||
export const MIGRATIONS = [
|
export const MIGRATIONS = [
|
||||||
CreateMigrationsTable,
|
CreateMigrationsTable,
|
||||||
@ -105,6 +106,7 @@ export default class TestApp extends Application {
|
|||||||
this.use(new MailController());
|
this.use(new MailController());
|
||||||
this.use(new AuthController());
|
this.use(new AuthController());
|
||||||
this.use(new AccountController());
|
this.use(new AccountController());
|
||||||
|
this.use(new BackendController());
|
||||||
|
|
||||||
this.use(new MagicLinkController(this.as<MagicLinkWebSocketListener<this>>(MagicLinkWebSocketListener)));
|
this.use(new MagicLinkController(this.as<MagicLinkWebSocketListener<this>>(MagicLinkWebSocketListener)));
|
||||||
|
|
||||||
|
@ -119,6 +119,11 @@ export default class AuthGuard {
|
|||||||
|
|
||||||
let user = await proof.getResource();
|
let user = await proof.getResource();
|
||||||
|
|
||||||
|
// Revoke proof early if user is not approved
|
||||||
|
if (user && !user.isApproved() || !user && User.isApprovalMode()) {
|
||||||
|
await proof.revoke();
|
||||||
|
}
|
||||||
|
|
||||||
// Register if user doesn't exist
|
// Register if user doesn't exist
|
||||||
if (!user) {
|
if (!user) {
|
||||||
const callbacks: RegisterCallback[] = [];
|
const callbacks: RegisterCallback[] = [];
|
||||||
@ -139,7 +144,7 @@ export default class AuthGuard {
|
|||||||
await callback();
|
await callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.isApproved()) {
|
if (User.isApprovalMode()) {
|
||||||
await new Mail(this.app.as(NunjucksComponent).getEnvironment(), PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, {
|
await new Mail(this.app.as(NunjucksComponent).getEnvironment(), PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, {
|
||||||
username: user.asOptional(UserNameComponent)?.getName() ||
|
username: user.asOptional(UserNameComponent)?.getName() ||
|
||||||
(await user.mainEmail.get())?.getOrFail('email') ||
|
(await user.mainEmail.get())?.getOrFail('email') ||
|
||||||
|
@ -87,18 +87,8 @@ export default class MagicLinkController<A extends Application> extends Controll
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof PendingApprovalAuthError) {
|
if (e instanceof PendingApprovalAuthError) {
|
||||||
res.format({
|
req.flash('warning', `Your account is pending review. You'll receive an email once you're approved.`);
|
||||||
json: () => {
|
res.redirect(Controller.route('auth'));
|
||||||
res.json({
|
|
||||||
'status': 'warning',
|
|
||||||
'message': `Your account is pending review. You'll receive an email once you're approved.`,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
html: () => {
|
|
||||||
req.flash('warning', `Your account is pending review. You'll receive an email once you're approved.`);
|
|
||||||
res.redirect('/');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -8,6 +8,9 @@ import UserApprovedComponent from "./UserApprovedComponent";
|
|||||||
import UserNameComponent from "./UserNameComponent";
|
import UserNameComponent from "./UserNameComponent";
|
||||||
|
|
||||||
export default class User extends Model {
|
export default class User extends Model {
|
||||||
|
/**
|
||||||
|
* If true, new users are unapproved by default.
|
||||||
|
*/
|
||||||
public static isApprovalMode(): boolean {
|
public static isApprovalMode(): boolean {
|
||||||
return config.get<boolean>('auth.approval_mode') &&
|
return config.get<boolean>('auth.approval_mode') &&
|
||||||
MysqlConnectionManager.hasMigration(AddApprovedFieldToUsersTableMigration);
|
MysqlConnectionManager.hasMigration(AddApprovedFieldToUsersTableMigration);
|
||||||
|
@ -26,6 +26,7 @@ export default class PasswordAuthProof implements AuthProof<User> {
|
|||||||
private forRegistration: boolean = false;
|
private forRegistration: boolean = false;
|
||||||
private userId: number | null;
|
private userId: number | null;
|
||||||
private userPassword: UserPasswordComponent | null = null;
|
private userPassword: UserPasswordComponent | null = null;
|
||||||
|
private revoked: boolean = false;
|
||||||
|
|
||||||
private constructor(session: Session & Partial<SessionData>) {
|
private constructor(session: Session & Partial<SessionData>) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
@ -54,7 +55,14 @@ export default class PasswordAuthProof implements AuthProof<User> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async revoke(): Promise<void> {
|
public async revoke(): Promise<void> {
|
||||||
|
this.revoked = true;
|
||||||
this.session.authPasswordProof = undefined;
|
this.session.authPasswordProof = undefined;
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
this.session.save(err => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getUserPassword(): Promise<UserPasswordComponent | null> {
|
private async getUserPassword(): Promise<UserPasswordComponent | null> {
|
||||||
@ -74,11 +82,13 @@ export default class PasswordAuthProof implements AuthProof<User> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private save() {
|
private save() {
|
||||||
this.session.authPasswordProof = {
|
if (!this.revoked) {
|
||||||
authorized: this.authorized,
|
this.session.authPasswordProof = {
|
||||||
forRegistration: this.forRegistration,
|
authorized: this.authorized,
|
||||||
userId: this.userId,
|
forRegistration: this.forRegistration,
|
||||||
};
|
userId: this.userId,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,46 +1,23 @@
|
|||||||
import TestApp from "../src/TestApp";
|
|
||||||
import useApp from "./_app";
|
import useApp from "./_app";
|
||||||
import Controller from "../src/Controller";
|
|
||||||
import supertest from "supertest";
|
import supertest from "supertest";
|
||||||
import CsrfProtectionComponent from "../src/components/CsrfProtectionComponent";
|
|
||||||
import User from "../src/auth/models/User";
|
import User from "../src/auth/models/User";
|
||||||
import UserNameComponent from "../src/auth/models/UserNameComponent";
|
import UserNameComponent from "../src/auth/models/UserNameComponent";
|
||||||
import UserPasswordComponent from "../src/auth/password/UserPasswordComponent";
|
import UserPasswordComponent from "../src/auth/password/UserPasswordComponent";
|
||||||
import {popEmail} from "./_mail_server";
|
import {popEmail} from "./_mail_server";
|
||||||
import AuthComponent from "../src/auth/AuthComponent";
|
import {authAppProvider, followMagicLinkFromMail, testLogout} from "./_authentication_common";
|
||||||
import {followMagicLinkFromMail, testLogout} from "./_authentication_common";
|
|
||||||
import UserEmail from "../src/auth/models/UserEmail";
|
import UserEmail from "../src/auth/models/UserEmail";
|
||||||
import * as querystring from "querystring";
|
import * as querystring from "querystring";
|
||||||
|
|
||||||
let app: TestApp;
|
const app = useApp(authAppProvider());
|
||||||
useApp(async (addr, port) => {
|
|
||||||
return app = new class extends TestApp {
|
|
||||||
protected async init(): Promise<void> {
|
|
||||||
this.use(new class extends Controller {
|
|
||||||
public routes(): void {
|
|
||||||
this.get('/', (req, res) => {
|
|
||||||
res.render('home');
|
|
||||||
}, 'home');
|
|
||||||
this.get('/csrf', (req, res) => {
|
|
||||||
res.send(CsrfProtectionComponent.getCsrfToken(req.getSession()));
|
|
||||||
}, 'csrf');
|
|
||||||
this.get('/is-auth', async (req, res) => {
|
|
||||||
const proofs = await this.getApp().as(AuthComponent).getAuthGuard().getProofs(req);
|
|
||||||
if (proofs.length > 0) res.sendStatus(200);
|
|
||||||
else res.sendStatus(401);
|
|
||||||
}, 'is-auth');
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
|
|
||||||
await super.init();
|
|
||||||
}
|
|
||||||
}(addr, port, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
let agent: supertest.SuperTest<supertest.Test>;
|
let agent: supertest.SuperTest<supertest.Test>;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
agent = supertest(app.getExpressApp());
|
agent = supertest(app().getExpressApp());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Approval Mode', () => {
|
||||||
|
expect(User.isApprovalMode()).toStrictEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Register with username and password (password)', () => {
|
describe('Register with username and password (password)', () => {
|
||||||
@ -74,6 +51,11 @@ describe('Register with username and password (password)', () => {
|
|||||||
expect(user).toBeDefined();
|
expect(user).toBeDefined();
|
||||||
expect(user?.as(UserNameComponent).getName()).toStrictEqual('entrapta');
|
expect(user?.as(UserNameComponent).getName()).toStrictEqual('entrapta');
|
||||||
await expect(user?.as(UserPasswordComponent).verifyPassword('darla_is_cute')).resolves.toStrictEqual(true);
|
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 () => {
|
test('Can\'t register when logged in', async () => {
|
||||||
@ -172,6 +154,11 @@ describe('Register with email (magic_link)', () => {
|
|||||||
|
|
||||||
await followMagicLinkFromMail(agent, cookies);
|
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);
|
await testLogout(agent, cookies, csrf);
|
||||||
|
|
||||||
// Verify saved user
|
// Verify saved user
|
||||||
|
160
test/AuthenticationApprovalMode.test.ts
Normal file
160
test/AuthenticationApprovalMode.test.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import useApp from "./_app";
|
||||||
|
import supertest from "supertest";
|
||||||
|
import {authAppProvider, followMagicLinkFromMail} from "./_authentication_common";
|
||||||
|
import User from "../src/auth/models/User";
|
||||||
|
import querystring from "querystring";
|
||||||
|
import UserApprovedComponent from "../src/auth/models/UserApprovedComponent";
|
||||||
|
import {popEmail} from "./_mail_server";
|
||||||
|
|
||||||
|
const app = useApp(authAppProvider(true, true));
|
||||||
|
|
||||||
|
let agent: supertest.SuperTest<supertest.Test>;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
agent = supertest(app().getExpressApp());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Approval Mode', () => {
|
||||||
|
expect(User.isApprovalMode()).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
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: 'entrapta2',
|
||||||
|
password: 'darla_is_cute',
|
||||||
|
password_confirmation: 'darla_is_cute',
|
||||||
|
terms: 'on',
|
||||||
|
})
|
||||||
|
.expect(302)
|
||||||
|
.expect('Location', '/auth/');
|
||||||
|
|
||||||
|
// Verify saved user
|
||||||
|
const user = await User.select()
|
||||||
|
.where('name', 'entrapta2')
|
||||||
|
.first();
|
||||||
|
|
||||||
|
expect(user).toBeDefined();
|
||||||
|
expect(user?.isApproved()).toBeFalsy();
|
||||||
|
expect(user?.as(UserApprovedComponent).approved).toBeFalsy();
|
||||||
|
|
||||||
|
// Proof must be revoked
|
||||||
|
await agent.get('/has-any-password-auth-proof')
|
||||||
|
.set('Cookie', cookies)
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
|
await popEmail();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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: 'glimmer2@example.org',
|
||||||
|
name: 'glimmer2',
|
||||||
|
})
|
||||||
|
.expect(302)
|
||||||
|
.expect('Location', '/magic/lobby?redirect_uri=%2Fredirect-uri');
|
||||||
|
|
||||||
|
await followMagicLinkFromMail(agent, cookies, '/auth/');
|
||||||
|
|
||||||
|
// Verify saved user
|
||||||
|
const user = await User.select()
|
||||||
|
.with('mainEmail')
|
||||||
|
.where('name', 'glimmer2')
|
||||||
|
.first();
|
||||||
|
|
||||||
|
expect(user).toBeDefined();
|
||||||
|
|
||||||
|
const email = user?.mainEmail.getOrFail();
|
||||||
|
expect(email).toBeDefined();
|
||||||
|
|
||||||
|
expect(user?.isApproved()).toBeFalsy();
|
||||||
|
expect(user?.as(UserApprovedComponent).approved).toBeFalsy();
|
||||||
|
|
||||||
|
// Proof must be revoked
|
||||||
|
await agent.get('/has-any-magic-link')
|
||||||
|
.set('Cookie', cookies)
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
|
await popEmail();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Authenticate with username and password (password)', () => {
|
||||||
|
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: 'entrapta2',
|
||||||
|
password: 'darla_is_cute',
|
||||||
|
auth_method: 'password',
|
||||||
|
})
|
||||||
|
.expect(302)
|
||||||
|
.expect('Location', '/auth/');
|
||||||
|
|
||||||
|
// Proof must be revoked
|
||||||
|
await agent.get('/has-any-password-auth-proof')
|
||||||
|
.set('Cookie', cookies)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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: 'glimmer2@example.org',
|
||||||
|
auth_method: 'magic_link',
|
||||||
|
})
|
||||||
|
.expect(302)
|
||||||
|
.expect('Location', '/magic/lobby?redirect_uri=%2Fredirect-uri');
|
||||||
|
|
||||||
|
await followMagicLinkFromMail(agent, cookies, '/auth/');
|
||||||
|
|
||||||
|
// Proof must be revoked
|
||||||
|
await agent.get('/has-any-magic-link')
|
||||||
|
.set('Cookie', cookies)
|
||||||
|
.expect(404);
|
||||||
|
});
|
||||||
|
});
|
@ -1,49 +1,21 @@
|
|||||||
import TestApp from "../src/TestApp";
|
|
||||||
import useApp from "./_app";
|
import useApp from "./_app";
|
||||||
import Controller from "../src/Controller";
|
|
||||||
import supertest from "supertest";
|
import supertest from "supertest";
|
||||||
import CsrfProtectionComponent from "../src/components/CsrfProtectionComponent";
|
|
||||||
import UserPasswordComponent from "../src/auth/password/UserPasswordComponent";
|
import UserPasswordComponent from "../src/auth/password/UserPasswordComponent";
|
||||||
import {popEmail} from "./_mail_server";
|
import {popEmail} from "./_mail_server";
|
||||||
import AuthComponent from "../src/auth/AuthComponent";
|
import {authAppProvider, followMagicLinkFromMail, testLogout} from "./_authentication_common";
|
||||||
import Migration, {MigrationType} from "../src/db/Migration";
|
|
||||||
import AddNameToUsersMigration from "../src/auth/migrations/AddNameToUsersMigration";
|
|
||||||
import {followMagicLinkFromMail, testLogout} from "./_authentication_common";
|
|
||||||
import UserEmail from "../src/auth/models/UserEmail";
|
import UserEmail from "../src/auth/models/UserEmail";
|
||||||
|
import User from "../src/auth/models/User";
|
||||||
|
|
||||||
let app: TestApp;
|
const app = useApp(authAppProvider(false));
|
||||||
useApp(async (addr, port) => {
|
|
||||||
return app = new class extends TestApp {
|
|
||||||
protected async init(): Promise<void> {
|
|
||||||
this.use(new class extends Controller {
|
|
||||||
public routes(): void {
|
|
||||||
this.get('/', (req, res) => {
|
|
||||||
res.render('home');
|
|
||||||
}, 'home');
|
|
||||||
this.get('/csrf', (req, res) => {
|
|
||||||
res.send(CsrfProtectionComponent.getCsrfToken(req.getSession()));
|
|
||||||
}, 'csrf');
|
|
||||||
this.get('/is-auth', async (req, res) => {
|
|
||||||
const proofs = await this.getApp().as(AuthComponent).getAuthGuard().getProofs(req);
|
|
||||||
if (proofs.length > 0) res.sendStatus(200);
|
|
||||||
else res.sendStatus(401);
|
|
||||||
}, 'is-auth');
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
|
|
||||||
await super.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getMigrations(): MigrationType<Migration>[] {
|
|
||||||
return super.getMigrations().filter(m => m !== AddNameToUsersMigration);
|
|
||||||
}
|
|
||||||
}(addr, port, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
let agent: supertest.SuperTest<supertest.Test>;
|
let agent: supertest.SuperTest<supertest.Test>;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
agent = supertest(app.getExpressApp());
|
agent = supertest(app().getExpressApp());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Approval Mode', () => {
|
||||||
|
expect(User.isApprovalMode()).toStrictEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Register with username and password (password)', () => {
|
describe('Register with username and password (password)', () => {
|
||||||
|
11
test/_app.ts
11
test/_app.ts
@ -1,12 +1,11 @@
|
|||||||
import Application from "../src/Application";
|
|
||||||
import {setupMailServer, teardownMailServer} from "./_mail_server";
|
import {setupMailServer, teardownMailServer} from "./_mail_server";
|
||||||
import TestApp from "../src/TestApp";
|
import TestApp from "../src/TestApp";
|
||||||
import MysqlConnectionManager from "../src/db/MysqlConnectionManager";
|
import MysqlConnectionManager from "../src/db/MysqlConnectionManager";
|
||||||
import config from "config";
|
import config from "config";
|
||||||
|
|
||||||
|
|
||||||
export default function useApp(appSupplier?: AppSupplier): void {
|
export default function useApp<T extends TestApp>(appSupplier: AppSupplier<T>): () => T {
|
||||||
let app: Application;
|
let app: T;
|
||||||
|
|
||||||
beforeAll(async (done) => {
|
beforeAll(async (done) => {
|
||||||
await MysqlConnectionManager.prepare();
|
await MysqlConnectionManager.prepare();
|
||||||
@ -14,7 +13,7 @@ export default function useApp(appSupplier?: AppSupplier): void {
|
|||||||
await MysqlConnectionManager.endPool();
|
await MysqlConnectionManager.endPool();
|
||||||
|
|
||||||
await setupMailServer();
|
await setupMailServer();
|
||||||
app = appSupplier ? await appSupplier('127.0.0.1', 8966) : new TestApp('127.0.0.1', 8966, true);
|
app = await appSupplier('127.0.0.1', 8966);
|
||||||
|
|
||||||
await app.start();
|
await app.start();
|
||||||
done();
|
done();
|
||||||
@ -38,6 +37,8 @@ export default function useApp(appSupplier?: AppSupplier): void {
|
|||||||
if (errors.length > 0) throw errors;
|
if (errors.length > 0) throw errors;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return () => app;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppSupplier = (addr: string, port: number) => Promise<TestApp>;
|
export type AppSupplier<T extends TestApp> = (addr: string, port: number) => Promise<T>;
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
import {popEmail} from "./_mail_server";
|
import {popEmail} from "./_mail_server";
|
||||||
import supertest from "supertest";
|
import supertest from "supertest";
|
||||||
|
import {AppSupplier} from "./_app";
|
||||||
|
import TestApp from "../src/TestApp";
|
||||||
|
import Controller from "../src/Controller";
|
||||||
|
import CsrfProtectionComponent from "../src/components/CsrfProtectionComponent";
|
||||||
|
import AuthComponent from "../src/auth/AuthComponent";
|
||||||
|
import Migration, {MigrationType} from "../src/db/Migration";
|
||||||
|
import AddNameToUsersMigration from "../src/auth/migrations/AddNameToUsersMigration";
|
||||||
|
import AddApprovedFieldToUsersTableMigration from "../src/auth/migrations/AddApprovedFieldToUsersTableMigration";
|
||||||
|
import PasswordAuthProof from "../src/auth/password/PasswordAuthProof";
|
||||||
|
import MagicLink from "../src/auth/models/MagicLink";
|
||||||
|
|
||||||
export async function followMagicLinkFromMail(
|
export async function followMagicLinkFromMail(
|
||||||
agent: supertest.SuperTest<supertest.Test>,
|
agent: supertest.SuperTest<supertest.Test>,
|
||||||
@ -37,3 +47,53 @@ export async function testLogout(
|
|||||||
// Not authenticated
|
// Not authenticated
|
||||||
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
|
await agent.get('/is-auth').set('Cookie', cookies).expect(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function authAppProvider(withUsername: boolean = true, approvalMode: boolean = false): AppSupplier<TestApp> {
|
||||||
|
return async (addr, port) => {
|
||||||
|
return new class extends TestApp {
|
||||||
|
protected async init(): Promise<void> {
|
||||||
|
this.use(new class extends Controller {
|
||||||
|
public routes(): void {
|
||||||
|
this.get('/', (req, res) => {
|
||||||
|
res.render('home');
|
||||||
|
}, 'home');
|
||||||
|
this.get('/csrf', (req, res) => {
|
||||||
|
res.send(CsrfProtectionComponent.getCsrfToken(req.getSession()));
|
||||||
|
}, 'csrf');
|
||||||
|
this.get('/is-auth', async (req, res) => {
|
||||||
|
const proofs = await this.getApp().as(AuthComponent).getAuthGuard().getProofs(req);
|
||||||
|
if (proofs.length > 0) res.sendStatus(200);
|
||||||
|
else res.sendStatus(401);
|
||||||
|
}, 'is-auth');
|
||||||
|
this.get('/has-any-password-auth-proof', async (req, res) => {
|
||||||
|
const proof = await PasswordAuthProof.getProofForSession(req.getSession());
|
||||||
|
if (proof) res.sendStatus(200);
|
||||||
|
else res.sendStatus(404);
|
||||||
|
}, 'is-auth');
|
||||||
|
this.get('/has-any-magic-link', async (req, res) => {
|
||||||
|
const proofs = await MagicLink.select()
|
||||||
|
.where('session_id', req.getSession().id)
|
||||||
|
.get();
|
||||||
|
if (proofs.length > 0) res.sendStatus(200);
|
||||||
|
else res.sendStatus(404);
|
||||||
|
}, 'is-auth');
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
|
||||||
|
await super.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getMigrations(): MigrationType<Migration>[] {
|
||||||
|
let migrations = withUsername ?
|
||||||
|
super.getMigrations() :
|
||||||
|
super.getMigrations().filter(m => m !== AddNameToUsersMigration);
|
||||||
|
|
||||||
|
migrations = approvalMode ?
|
||||||
|
[...migrations, AddApprovedFieldToUsersTableMigration] :
|
||||||
|
migrations;
|
||||||
|
|
||||||
|
return migrations;
|
||||||
|
}
|
||||||
|
}(addr, port, true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user