Add formatViewData to response object to fix tests and prepare for async navigation

This commit is contained in:
Alice Gaudon 2021-11-28 21:26:45 +01:00
parent 366e48757e
commit d6b530d16c
12 changed files with 46 additions and 14 deletions

View File

@ -198,9 +198,9 @@ export default abstract class Application implements Extendable<ApplicationCompo
error_instructions: httpError.instructions,
error_id: errorId,
};
res.render('errors/' + httpError.errorCode, locals, (err: Error | undefined, html) => {
res.formatViewData('errors/' + httpError.errorCode, locals, (err: Error | undefined, html) => {
if (err) {
res.render('templates/ErrorTemplate', locals);
res.formatViewData('templates/ErrorTemplate', locals);
} else {
res.send(html);
}

View File

@ -148,10 +148,10 @@ export default class TestApp extends Application {
this.use(new class extends Controller {
public routes(): void {
this.get('/', (req, res) => {
res.render('home');
res.formatViewData('home');
}, 'home');
this.get('/tests', (req, res) => {
res.render('tests');
res.formatViewData('tests');
}, 'tests');
this.get('/design', (req, res) => {
req.flash('success', 'Success.');
@ -159,7 +159,7 @@ export default class TestApp extends Application {
req.flash('warning', 'Warning.');
req.flash('error', 'Error.');
req.flash('error-alert', 'Error alert.');
res.render('design');
res.formatViewData('design');
}, 'design');
}
}());

View File

@ -57,7 +57,7 @@ export default class AccountController extends Controller {
const nameChangedAt = nameComponent?.getNameChangedAt()?.getTime() || Date.now();
const nameChangeRemainingTime = new Date(nameChangedAt + nameChangeWaitPeriod);
res.render('auth/account/account', {
res.formatViewData('auth/account/account', {
user_personal_info_fields: user.getPersonalInfoFields(),
main_email: await user.mainEmail.get(),
emails: await user.emails.get(),

View File

@ -35,7 +35,7 @@ export default class AuthController extends Controller {
const userModelFactory = ModelFactory.get(User);
const hasUsername = userModelFactory.hasComponent(UserNameComponent);
res.render('auth/auth', {
res.formatViewData('auth/auth', {
auth_methods: authGuard.getAuthMethodNames(),
has_username: hasUsername,
register_with_password: hasUsername && userModelFactory.hasComponent(UserPasswordComponent),

View File

@ -147,7 +147,7 @@ export default class MagicLinkController<A extends Application> extends Controll
return;
}
res.render('magic_link_lobby', {
res.formatViewData('magic_link_lobby', {
email: validLink.getOrFail('email'),
type: validLink.getOrFail('action_type'),
validUntil: validLink.getExpirationDate().getTime(),
@ -179,7 +179,7 @@ export default class MagicLinkController<A extends Application> extends Controll
}
}
res.render('magic_link', {
res.formatViewData('magic_link', {
magicLink: magicLink,
err: err,
success: success && err === null,

View File

@ -48,6 +48,26 @@ export default class ExpressAppComponent extends ApplicationComponent {
if (!middleware) throw new Error('Middleware ' + type.name + ' not present in this request.');
return middleware as M;
};
res.formatViewData = function (
viewName: string,
data?: Record<string, unknown>,
callback?: (err: Error, html: string) => void,
) {
this.format({
html: () => {
this.render(viewName, data, callback);
},
json: () => {
if (typeof data === 'undefined') data = {};
const serialized = JSON.stringify({...data, viewName}, (key, value) => {
if (key.startsWith('_') || typeof value === 'function') return undefined;
else return value;
});
this.contentType('application/json');
this.send(serialized);
},
});
};
next();
});
}

View File

@ -53,7 +53,7 @@ export default class BackendController extends Controller {
}
protected async getIndex(req: Request, res: Response): Promise<void> {
res.render('backend/index', {
res.formatViewData('backend/index', {
menu: await Promise.all(BackendController.menu.map(async m => ({
link: await m.getLink(),
display_string: await m.getDisplayString(),
@ -66,7 +66,7 @@ export default class BackendController extends Controller {
const accounts = await User.paginate(req, 20, User.select()
.where('approved', 0)
.with('mainEmail'));
res.render('backend/accounts_approval', {
res.formatViewData('backend/accounts_approval', {
accounts: accounts.map(account => Object.assign({
mainEmailStr: account.mainEmail.getOrFail()?.email,
created_at_iso: account.created_at?.toISOString(),

View File

@ -7,8 +7,8 @@ export default class MailController extends Controller {
this.get("/mail/:template", this.getMail, 'mail');
}
protected async getMail(request: Request, response: Response): Promise<void> {
protected async getMail(request: Request, res: Response): Promise<void> {
const template = request.params['template'];
response.render(`mails/${template}.mnjk`, request.query);
res.formatViewData(`mails/${template}.mnjk`, request.query);
}
}

View File

@ -36,6 +36,12 @@ declare global {
export interface Response {
setLazyLocal(key: string, valueProvider: () => unknown): void;
formatViewData(
viewName: string,
data?: Record<string, unknown>,
callback?: (err: Error, html: string) => void,
): void;
}
}
}

View File

@ -142,6 +142,7 @@ describe('Authenticate with email (magic_link)', () => {
// Authenticate
await agent.post('/auth/login?' + querystring.stringify({redirect_uri: '/redirect-uri'}))
.accept('json')
.set('Cookie', cookies)
.send({
csrf: csrf,

View File

@ -35,6 +35,7 @@ describe('Test CSRF protection', () => {
test('no csrf token should be in session at first', (done) => {
const agent = supertest(app.getExpressApp());
agent.post('/')
.accept('json')
.expect(401)
.then(res => {
expect(res.text).toContain(`You weren't assigned any CSRF token.`);
@ -55,6 +56,7 @@ describe('Test CSRF protection', () => {
const agent = supertest(app.getExpressApp());
agent.post('/')
.accept('json')
.set('Cookie', cookies)
.expect(401)
.then((res) => {
@ -68,6 +70,7 @@ describe('Test CSRF protection', () => {
const agent = supertest(app.getExpressApp());
agent.post('/')
.accept('json')
.set('Cookie', cookies)
.set('Content-Type', 'application/json')
.send({csrf: 'not_a_valid_csrf'})

View File

@ -23,8 +23,10 @@ export async function followMagicLinkFromMail(
expect(query).toBeDefined();
await agent.get('/magic/link?' + query)
.accept('json')
.expect(200);
await agent.get('/magic/lobby')
.accept('json')
.set('Cookie', cookies)
.expect(302)
.expect('Location', expectedRedirectUrl);
@ -55,7 +57,7 @@ export function authAppProvider(withUsername: boolean = true, approvalMode: bool
this.use(new class extends Controller {
public routes(): void {
this.get('/', (req, res) => {
res.render('home');
res.formatViewData('home');
}, 'home');
this.get('/csrf', (req, res) => {
res.send(this.getApp().as(CsrfProtectionComponent).getSessionCsrfToken(req.getSession()));