Fix auth redirect_uri chain
This commit is contained in:
parent
634edda704
commit
eb935bf52a
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wms-core",
|
"name": "wms-core",
|
||||||
"version": "0.14.0",
|
"version": "0.15.0",
|
||||||
"description": "Node web framework",
|
"description": "Node web framework",
|
||||||
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
|
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
|
||||||
"author": "Alice Gaudon <alice@gaudon.pro>",
|
"author": "Alice Gaudon <alice@gaudon.pro>",
|
||||||
|
@ -4,11 +4,13 @@ import config from "config";
|
|||||||
import Logger from "./Logger";
|
import Logger from "./Logger";
|
||||||
import Validator, {FileError, ValidationBag} from "./db/Validator";
|
import Validator, {FileError, ValidationBag} from "./db/Validator";
|
||||||
import FileUploadMiddleware from "./FileUploadMiddleware";
|
import FileUploadMiddleware from "./FileUploadMiddleware";
|
||||||
|
import * as querystring from "querystring";
|
||||||
|
import {ParsedUrlQueryInput} from "querystring";
|
||||||
|
|
||||||
export default abstract class Controller {
|
export default abstract class Controller {
|
||||||
private static readonly routes: { [p: string]: string } = {};
|
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];
|
let path = this.routes[route];
|
||||||
if (path === undefined) throw new Error(`Unknown route for name ${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();
|
private readonly router: Router = express.Router();
|
||||||
|
@ -5,12 +5,8 @@ import nunjucks from 'nunjucks';
|
|||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import {WrappingError} from "./Utils";
|
import {WrappingError} from "./Utils";
|
||||||
import mjml2html from "mjml";
|
import mjml2html from "mjml";
|
||||||
import * as querystring from "querystring";
|
|
||||||
import Logger from "./Logger";
|
import Logger from "./Logger";
|
||||||
|
import Controller from "./Controller";
|
||||||
export function mailRoute(template: string): string {
|
|
||||||
return `/mail/${template}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Mail {
|
export default class Mail {
|
||||||
private static transporter: Transporter;
|
private static transporter: Transporter;
|
||||||
@ -101,7 +97,7 @@ export default class Mail {
|
|||||||
// Set data
|
// Set data
|
||||||
this.data.mail_subject = this.options.subject;
|
this.data.mail_subject = this.options.subject;
|
||||||
this.data.mail_to = this.options.to;
|
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');
|
this.data.app = config.get('app');
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
|
@ -3,7 +3,6 @@ import {NextFunction, Request, Response, Router} from "express";
|
|||||||
import AuthGuard from "./AuthGuard";
|
import AuthGuard from "./AuthGuard";
|
||||||
import Controller from "../Controller";
|
import Controller from "../Controller";
|
||||||
import {ForbiddenHttpError} from "../HttpError";
|
import {ForbiddenHttpError} from "../HttpError";
|
||||||
import * as querystring from "querystring";
|
|
||||||
|
|
||||||
export default class AuthComponent extends ApplicationComponent<void> {
|
export default class AuthComponent extends ApplicationComponent<void> {
|
||||||
private readonly authGuard: AuthGuard<any>;
|
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> => {
|
export const REQUIRE_REQUEST_AUTH_MIDDLEWARE = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
||||||
if (!await req.authGuard.isAuthenticatedViaRequest(req)) {
|
if (!await req.authGuard.isAuthenticatedViaRequest(req)) {
|
||||||
req.flash('error', `You must be logged in to access ${req.url}.`);
|
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,
|
redirect_uri: req.url,
|
||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
@ -42,7 +41,7 @@ export const REQUIRE_AUTH_MIDDLEWARE = async (req: Request, res: Response, next:
|
|||||||
} else {
|
} else {
|
||||||
if (!await req.authGuard.isAuthenticated(req.session!)) {
|
if (!await req.authGuard.isAuthenticated(req.session!)) {
|
||||||
req.flash('error', `You must be logged in to access ${req.url}.`);
|
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,
|
redirect_uri: req.url,
|
||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
|
@ -28,7 +28,7 @@ export default abstract class AuthController extends Controller {
|
|||||||
protected async postLogout(req: Request, res: Response, next: NextFunction): Promise<void> {
|
protected async postLogout(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
await req.authGuard.logout(req.session!);
|
await req.authGuard.logout(req.session!);
|
||||||
req.flash('success', 'Successfully logged out.');
|
req.flash('success', 'Successfully logged out.');
|
||||||
res.redirectBack('/');
|
res.redirect(req.query.redirect_uri?.toString() || '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ import Mail from "../Mail";
|
|||||||
|
|
||||||
export default class MailController extends Controller {
|
export default class MailController extends Controller {
|
||||||
routes(): void {
|
routes(): void {
|
||||||
this.get("/mail/:template", this.getMail);
|
this.get("/mail/:template", this.getMail, 'mail');
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getMail(request: Request, response: Response) {
|
private async getMail(request: Request, response: Response) {
|
||||||
|
@ -8,6 +8,7 @@ import {MailTemplate} from "../../Mail";
|
|||||||
import {AuthError, PendingApprovalAuthError} from "../AuthGuard";
|
import {AuthError, PendingApprovalAuthError} from "../AuthGuard";
|
||||||
import geoip from "geoip-lite";
|
import geoip from "geoip-lite";
|
||||||
import AuthController from "../AuthController";
|
import AuthController from "../AuthController";
|
||||||
|
import NunjucksComponent from "../../components/NunjucksComponent";
|
||||||
|
|
||||||
|
|
||||||
export default abstract class MagicLinkAuthController extends AuthController {
|
export default abstract class MagicLinkAuthController extends AuthController {
|
||||||
@ -30,7 +31,7 @@ export default abstract class MagicLinkAuthController extends AuthController {
|
|||||||
},
|
},
|
||||||
html: () => {
|
html: () => {
|
||||||
req.flash('warning', `Your account is pending review. You'll receive an email once you're approved.`);
|
req.flash('warning', `Your account is pending review. You'll receive an email once you're approved.`);
|
||||||
res.redirectBack('/');
|
res.redirect('/');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@ -49,14 +50,16 @@ export default abstract class MagicLinkAuthController extends AuthController {
|
|||||||
this.magicLinkMailTemplate = magicLinkMailTemplate;
|
this.magicLinkMailTemplate = magicLinkMailTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getAuth(request: Request, response: Response, next: NextFunction): Promise<void> {
|
protected async getAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
const link = await MagicLink.bySessionID(request.sessionID!, [this.loginMagicLinkActionType, this.registerMagicLinkActionType]);
|
const link = await MagicLink.bySessionID(req.sessionID!, [this.loginMagicLinkActionType, this.registerMagicLinkActionType]);
|
||||||
if (link && await link.isValid()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await super.getAuth(request, response, next);
|
await super.getAuth(req, res, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async postAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
|
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(
|
await MagicLinkController.sendMagicLink(
|
||||||
req.sessionID!,
|
req.sessionID!,
|
||||||
isRegistration ? this.registerMagicLinkActionType : this.loginMagicLinkActionType,
|
isRegistration ? this.registerMagicLinkActionType : this.loginMagicLinkActionType,
|
||||||
Controller.route('auth'),
|
Controller.route('auth', undefined, {
|
||||||
|
redirect_uri: req.query.redirect_uri?.toString() || undefined,
|
||||||
|
}),
|
||||||
email,
|
email,
|
||||||
this.magicLinkMailTemplate,
|
this.magicLinkMailTemplate,
|
||||||
{
|
{
|
||||||
type: isRegistration ? 'register' : 'login',
|
type: isRegistration ? 'register' : 'login',
|
||||||
ip: req.ip,
|
ip: req.ip,
|
||||||
geo: geo ? `${geo.city}, ${geo.country}` : 'Unknown location',
|
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 {
|
} else {
|
||||||
// Confirm registration req
|
// Confirm registration req
|
||||||
req.flash('register_confirm_email', email);
|
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,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,38 +2,33 @@ import Controller from "../../Controller";
|
|||||||
import {Request, Response} from "express";
|
import {Request, Response} from "express";
|
||||||
import MagicLinkWebSocketListener from "./MagicLinkWebSocketListener";
|
import MagicLinkWebSocketListener from "./MagicLinkWebSocketListener";
|
||||||
import {BadRequestError, NotFoundHttpError} from "../../HttpError";
|
import {BadRequestError, NotFoundHttpError} from "../../HttpError";
|
||||||
import querystring from "querystring";
|
|
||||||
import Throttler from "../../Throttler";
|
import Throttler from "../../Throttler";
|
||||||
import Mail, {MailTemplate} from "../../Mail";
|
import Mail, {MailTemplate} from "../../Mail";
|
||||||
import MagicLink from "../models/MagicLink";
|
import MagicLink from "../models/MagicLink";
|
||||||
import config from "config";
|
import config from "config";
|
||||||
|
|
||||||
export default abstract class MagicLinkController extends Controller {
|
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', 2, MagicLink.validityPeriod(), sessionID, 0, 0);
|
||||||
Throttler.throttle('magic_link', 1, MagicLink.validityPeriod(), email, 0, 0);
|
Throttler.throttle('magic_link', 1, MagicLink.validityPeriod(), email, 0, 0);
|
||||||
|
|
||||||
let link = await MagicLink.bySessionID(sessionID, actionType);
|
const link = await MagicLink.bySessionID(sessionID, actionType) ||
|
||||||
if (!link) {
|
new MagicLink({
|
||||||
link = new MagicLink({
|
|
||||||
session_id: sessionID,
|
session_id: sessionID,
|
||||||
action_type: actionType,
|
action_type: actionType,
|
||||||
original_url: original_url,
|
original_url: original_url,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const token = await link.generateToken(email);
|
const token = await link.generateToken(email);
|
||||||
await link.save();
|
await link.save();
|
||||||
|
|
||||||
// Send email
|
// Send email
|
||||||
await new Mail(mailTemplate, Object.assign(data, {
|
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,
|
id: link.id,
|
||||||
token: token,
|
token: token,
|
||||||
})}`,
|
})}`,
|
||||||
})).send(email);
|
})).send(email);
|
||||||
|
|
||||||
res.redirect(Controller.route('magic_link_lobby'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,8 +8,6 @@ export default class FormHelperComponent extends ApplicationComponent<void> {
|
|||||||
throw new Error('Session is unavailable.');
|
throw new Error('Session is unavailable.');
|
||||||
}
|
}
|
||||||
|
|
||||||
res.locals.query = req.query;
|
|
||||||
|
|
||||||
let _validation: any = null;
|
let _validation: any = null;
|
||||||
res.locals.validation = () => {
|
res.locals.validation = () => {
|
||||||
if (!_validation) {
|
if (!_validation) {
|
||||||
|
@ -5,10 +5,14 @@ import ApplicationComponent from "../ApplicationComponent";
|
|||||||
import Controller from "../Controller";
|
import Controller from "../Controller";
|
||||||
import {ServerError} from "../HttpError";
|
import {ServerError} from "../HttpError";
|
||||||
import * as querystring from "querystring";
|
import * as querystring from "querystring";
|
||||||
|
import {ParsedUrlQueryInput} from "querystring";
|
||||||
|
|
||||||
export default class NunjucksComponent extends ApplicationComponent<void> {
|
export default class NunjucksComponent extends ApplicationComponent<void> {
|
||||||
public static getPreviousURL(req: Request, defaultUrl?: string): string {
|
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;
|
private readonly viewsPath: string;
|
||||||
@ -40,8 +44,8 @@ export default class NunjucksComponent extends ApplicationComponent<void> {
|
|||||||
noCache: !config.get('view.cache'),
|
noCache: !config.get('view.cache'),
|
||||||
throwOnUndefined: true,
|
throwOnUndefined: true,
|
||||||
})
|
})
|
||||||
.addGlobal('route', (route: string, params: { [p: string]: string } | [] = [], absolute: boolean = false) => {
|
.addGlobal('route', (route: string, params?: { [p: string]: string } | [], query?: ParsedUrlQueryInput, absolute?: boolean) => {
|
||||||
const path = Controller.route(route, params, absolute);
|
const path = Controller.route(route, params, query, absolute);
|
||||||
if (path === null) throw new ServerError(`Route ${route} not found.`);
|
if (path === null) throw new ServerError(`Route ${route} not found.`);
|
||||||
return path;
|
return path;
|
||||||
})
|
})
|
||||||
@ -58,7 +62,9 @@ export default class NunjucksComponent extends ApplicationComponent<void> {
|
|||||||
router.use((req, res, next) => {
|
router.use((req, res, next) => {
|
||||||
req.env = this.env!;
|
req.env = this.env!;
|
||||||
res.locals.url = req.url;
|
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');
|
res.locals.app = config.get('app');
|
||||||
|
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="panel">
|
<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 %}
|
{% if register_confirm_email %}
|
||||||
<form action="{{ action }}" method="POST" id="register-form">
|
<form action="{{ action }}" method="POST" id="register-form">
|
||||||
|
Loading…
Reference in New Issue
Block a user