swaf/src/auth/magic_link/MagicLinkAuthMethod.ts

104 lines
4.0 KiB
TypeScript

import AuthMethod from "../AuthMethod";
import {Request, Response} from "express";
import User from "../models/User";
import UserEmail from "../models/UserEmail";
import MagicLink from "../models/MagicLink";
import {WhereTest} from "../../db/ModelQuery";
import Controller from "../../Controller";
import geoip from "geoip-lite";
import MagicLinkController from "./MagicLinkController";
import RedirectBackComponent from "../../components/RedirectBackComponent";
import Application from "../../Application";
import {MailTemplate} from "../../Mail";
import AuthMagicLinkActionType from "./AuthMagicLinkActionType";
export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
public constructor(
protected readonly app: Application,
protected readonly magicLinkMailTemplate: MailTemplate,
) {
}
public getName(): string {
return 'magic_link';
}
public async findUserByIdentifier(identifier: string): Promise<User | null> {
return (await UserEmail.select()
.with('user')
.where('email', identifier)
.first())?.user.getOrFail() || null;
}
public async getProofsForSession(session: Express.Session): Promise<MagicLink[]> {
return await MagicLink.select()
.where('session_id', session.id)
.where('action_type', [AuthMagicLinkActionType.LOGIN, AuthMagicLinkActionType.REGISTER], WhereTest.IN)
.get();
}
public async interruptAuth(req: Request, res: Response): Promise<boolean> {
const pendingLink = await MagicLink.select()
.where('session_id', req.getSession().id)
.where('action_type', [AuthMagicLinkActionType.LOGIN, AuthMagicLinkActionType.REGISTER], WhereTest.IN)
.where('authorized', false)
.first();
if (pendingLink && await pendingLink.isValid()) {
res.redirect(Controller.route('magic_link_lobby', undefined, {
redirect_uri: req.query.redirect_uri?.toString() || pendingLink.original_url || undefined,
}));
return true;
}
return false;
}
public async attemptLogin(req: Request, res: Response, user: User): Promise<void> {
const userEmail = user.mainEmail.getOrFail();
if (!userEmail) throw new Error('No main email for user ' + user.id);
await this.auth(req, res, false, userEmail.getOrFail('email'));
}
public async attemptRegister(req: Request, res: Response, identifier: string): Promise<void> {
const userEmail = UserEmail.create({
email: identifier,
main: true,
});
await userEmail.validate(true);
await this.auth(req, res, true, identifier);
}
private async auth(req: Request, res: Response, isRegistration: boolean, email: string): Promise<void> {
if (!isRegistration || req.body.confirm_register === 'confirm') {
const geo = geoip.lookup(req.ip);
const actionType = isRegistration ? AuthMagicLinkActionType.REGISTER : AuthMagicLinkActionType.LOGIN;
await MagicLinkController.sendMagicLink(
this.app,
req.getSession().id,
actionType,
Controller.route('auth', undefined, {
redirect_uri: req.query.redirect_uri?.toString() || undefined,
}),
email,
this.magicLinkMailTemplate,
{
type: actionType,
ip: req.ip,
geo: geo ? `${geo.city}, ${geo.country}` : 'Unknown location',
},
);
res.redirect(Controller.route('magic_link_lobby', undefined, {
redirect_uri: req.query.redirect_uri?.toString() || RedirectBackComponent.getPreviousURL(req),
}));
} else {
req.flash('register_identifier', email);
res.redirect(Controller.route('auth', undefined, {
redirect_uri: req.query.redirect_uri?.toString() || undefined,
}));
}
}
}