Fix auth redirect_uri chain

This commit is contained in:
Alice Gaudon 2020-07-15 11:42:49 +02:00
parent 634edda704
commit eb935bf52a
11 changed files with 50 additions and 40 deletions

View File

@ -1,6 +1,6 @@
{
"name": "wms-core",
"version": "0.14.0",
"version": "0.15.0",
"description": "Node web framework",
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
"author": "Alice Gaudon <alice@gaudon.pro>",

View File

@ -4,11 +4,13 @@ import config from "config";
import Logger from "./Logger";
import Validator, {FileError, ValidationBag} from "./db/Validator";
import FileUploadMiddleware from "./FileUploadMiddleware";
import * as querystring from "querystring";
import {ParsedUrlQueryInput} from "querystring";
export default abstract class Controller {
private static readonly routes: { [p: string]: string } = {};
public static route(route: string, params: RouteParams = [], absolute: boolean = false): string {
public static route(route: string, params: RouteParams = [], query: ParsedUrlQueryInput = {}, absolute: boolean = false): string {
let path = this.routes[route];
if (path === undefined) throw new Error(`Unknown route for name ${route}.`);
@ -31,7 +33,8 @@ export default abstract class Controller {
}
}
return `${absolute ? config.get<string>('public_url') : ''}${path}`;
const queryStr = querystring.stringify(query);
return `${absolute ? config.get<string>('public_url') : ''}${path}` + (queryStr.length > 0 ? '?' + queryStr : '');
}
private readonly router: Router = express.Router();

View File

@ -5,12 +5,8 @@ import nunjucks from 'nunjucks';
import * as util from "util";
import {WrappingError} from "./Utils";
import mjml2html from "mjml";
import * as querystring from "querystring";
import Logger from "./Logger";
export function mailRoute(template: string): string {
return `/mail/${template}`;
}
import Controller from "./Controller";
export default class Mail {
private static transporter: Transporter;
@ -101,7 +97,7 @@ export default class Mail {
// Set data
this.data.mail_subject = this.options.subject;
this.data.mail_to = this.options.to;
this.data.mail_link = `${config.get<string>('public_url')}${mailRoute(this.template.template)}?${querystring.stringify(this.data)}`;
this.data.mail_link = config.get<string>('public_url') + Controller.route('mail', [this.template.template], this.data);
this.data.app = config.get('app');
// Log

View File

@ -3,7 +3,6 @@ import {NextFunction, Request, Response, Router} from "express";
import AuthGuard from "./AuthGuard";
import Controller from "../Controller";
import {ForbiddenHttpError} from "../HttpError";
import * as querystring from "querystring";
export default class AuthComponent extends ApplicationComponent<void> {
private readonly authGuard: AuthGuard<any>;
@ -25,7 +24,7 @@ export default class AuthComponent extends ApplicationComponent<void> {
export const REQUIRE_REQUEST_AUTH_MIDDLEWARE = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
if (!await req.authGuard.isAuthenticatedViaRequest(req)) {
req.flash('error', `You must be logged in to access ${req.url}.`);
res.redirect((Controller.route('auth') || '/') + '?' + querystring.stringify({
res.redirect(Controller.route('auth', undefined, {
redirect_uri: req.url,
}));
return;
@ -42,7 +41,7 @@ export const REQUIRE_AUTH_MIDDLEWARE = async (req: Request, res: Response, next:
} else {
if (!await req.authGuard.isAuthenticated(req.session!)) {
req.flash('error', `You must be logged in to access ${req.url}.`);
res.redirect((Controller.route('auth') || '/') + '?' + querystring.stringify({
res.redirect(Controller.route('auth', undefined, {
redirect_uri: req.url,
}));
return;

View File

@ -28,7 +28,7 @@ export default abstract class AuthController extends Controller {
protected async postLogout(req: Request, res: Response, next: NextFunction): Promise<void> {
await req.authGuard.logout(req.session!);
req.flash('success', 'Successfully logged out.');
res.redirectBack('/');
res.redirect(req.query.redirect_uri?.toString() || '/');
}
}

View File

@ -4,7 +4,7 @@ import Mail from "../Mail";
export default class MailController extends Controller {
routes(): void {
this.get("/mail/:template", this.getMail);
this.get("/mail/:template", this.getMail, 'mail');
}
private async getMail(request: Request, response: Response) {

View File

@ -8,6 +8,7 @@ import {MailTemplate} from "../../Mail";
import {AuthError, PendingApprovalAuthError} from "../AuthGuard";
import geoip from "geoip-lite";
import AuthController from "../AuthController";
import NunjucksComponent from "../../components/NunjucksComponent";
export default abstract class MagicLinkAuthController extends AuthController {
@ -30,7 +31,7 @@ export default abstract class MagicLinkAuthController extends AuthController {
},
html: () => {
req.flash('warning', `Your account is pending review. You'll receive an email once you're approved.`);
res.redirectBack('/');
res.redirect('/');
}
});
return;
@ -49,14 +50,16 @@ export default abstract class MagicLinkAuthController extends AuthController {
this.magicLinkMailTemplate = magicLinkMailTemplate;
}
protected async getAuth(request: Request, response: Response, next: NextFunction): Promise<void> {
const link = await MagicLink.bySessionID(request.sessionID!, [this.loginMagicLinkActionType, this.registerMagicLinkActionType]);
protected async getAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
const link = await MagicLink.bySessionID(req.sessionID!, [this.loginMagicLinkActionType, this.registerMagicLinkActionType]);
if (link && await link.isValid()) {
response.redirect(Controller.route('magic_link_lobby'));
res.redirect(Controller.route('magic_link_lobby', undefined, {
redirect_uri: req.query.redirect_uri?.toString() || undefined,
}));
return;
}
await super.getAuth(request, response, next);
await super.getAuth(req, res, next);
}
protected async postAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
@ -81,21 +84,27 @@ export default abstract class MagicLinkAuthController extends AuthController {
await MagicLinkController.sendMagicLink(
req.sessionID!,
isRegistration ? this.registerMagicLinkActionType : this.loginMagicLinkActionType,
Controller.route('auth'),
Controller.route('auth', undefined, {
redirect_uri: req.query.redirect_uri?.toString() || undefined,
}),
email,
this.magicLinkMailTemplate,
{
type: isRegistration ? 'register' : 'login',
ip: req.ip,
geo: geo ? `${geo.city}, ${geo.country}` : 'Unknown location',
},
req,
res
}
);
res.redirect(Controller.route('magic_link_lobby', undefined, {
redirect_uri: req.query.redirect_uri?.toString() || NunjucksComponent.getPreviousURL(req),
}));
} else {
// Confirm registration req
req.flash('register_confirm_email', email);
res.redirect(Controller.route('auth'));
res.redirect(Controller.route('auth', undefined, {
redirect_uri: req.query.redirect_uri?.toString() || undefined,
}));
}
}

View File

@ -2,38 +2,33 @@ import Controller from "../../Controller";
import {Request, Response} from "express";
import MagicLinkWebSocketListener from "./MagicLinkWebSocketListener";
import {BadRequestError, NotFoundHttpError} from "../../HttpError";
import querystring from "querystring";
import Throttler from "../../Throttler";
import Mail, {MailTemplate} from "../../Mail";
import MagicLink from "../models/MagicLink";
import config from "config";
export default abstract class MagicLinkController extends Controller {
public static async sendMagicLink(sessionID: string, actionType: string, original_url: string, email: string, mailTemplate: MailTemplate, data: object, req: Request, res: Response): Promise<void> {
public static async sendMagicLink(sessionID: string, actionType: string, original_url: string, email: string, mailTemplate: MailTemplate, data: object): Promise<void> {
Throttler.throttle('magic_link', 2, MagicLink.validityPeriod(), sessionID, 0, 0);
Throttler.throttle('magic_link', 1, MagicLink.validityPeriod(), email, 0, 0);
let link = await MagicLink.bySessionID(sessionID, actionType);
if (!link) {
link = new MagicLink({
const link = await MagicLink.bySessionID(sessionID, actionType) ||
new MagicLink({
session_id: sessionID,
action_type: actionType,
original_url: original_url,
});
}
const token = await link.generateToken(email);
await link.save();
// Send email
await new Mail(mailTemplate, Object.assign(data, {
link: `${config.get<string>('base_url')}${Controller.route('magic_link')}?${querystring.stringify({
link: `${config.get<string>('base_url')}${Controller.route('magic_link', undefined, {
id: link.id,
token: token,
})}`,
})).send(email);
res.redirect(Controller.route('magic_link_lobby'));
}

View File

@ -8,8 +8,6 @@ export default class FormHelperComponent extends ApplicationComponent<void> {
throw new Error('Session is unavailable.');
}
res.locals.query = req.query;
let _validation: any = null;
res.locals.validation = () => {
if (!_validation) {

View File

@ -5,10 +5,14 @@ import ApplicationComponent from "../ApplicationComponent";
import Controller from "../Controller";
import {ServerError} from "../HttpError";
import * as querystring from "querystring";
import {ParsedUrlQueryInput} from "querystring";
export default class NunjucksComponent extends ApplicationComponent<void> {
public static getPreviousURL(req: Request, defaultUrl?: string): string {
return req.query.redirect_uri?.toString() || req.headers.referer?.[0] || defaultUrl || '/';
let referrer = req.headers.referer?.toString() || req.headers.referrer?.toString() || defaultUrl || '/';
if (referrer.startsWith('https://') || referrer.startsWith('http://')) return '/';
else if (!referrer.startsWith('/')) referrer = req.url + (req.url.endsWith('/') ? '' : '/') + referrer;
return referrer;
}
private readonly viewsPath: string;
@ -40,8 +44,8 @@ export default class NunjucksComponent extends ApplicationComponent<void> {
noCache: !config.get('view.cache'),
throwOnUndefined: true,
})
.addGlobal('route', (route: string, params: { [p: string]: string } | [] = [], absolute: boolean = false) => {
const path = Controller.route(route, params, absolute);
.addGlobal('route', (route: string, params?: { [p: string]: string } | [], query?: ParsedUrlQueryInput, absolute?: boolean) => {
const path = Controller.route(route, params, query, absolute);
if (path === null) throw new ServerError(`Route ${route} not found.`);
return path;
})
@ -58,7 +62,9 @@ export default class NunjucksComponent extends ApplicationComponent<void> {
router.use((req, res, next) => {
req.env = this.env!;
res.locals.url = req.url;
res.locals.params = () => req.params;
res.locals.params = req.params;
res.locals.query = req.query;
res.locals.body = req.body;
res.locals.app = config.get('app');

View File

@ -8,7 +8,11 @@
{% block body %}
<div class="container">
<div class="panel">
{% set action = route('auth') + '?' + querystring.stringify({redirect_uri: req.url}) %}
{% set queryStr = '' %}
{% if query.redirect_uri | length %}
{% set queryStr = '?' + querystring.stringify({redirect_uri: query.redirect_uri}) %}
{% endif %}
{% set action = route('auth') + queryStr %}
{% if register_confirm_email %}
<form action="{{ action }}" method="POST" id="register-form">