104 lines
4.0 KiB
TypeScript
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,
|
|
}));
|
|
}
|
|
}
|
|
}
|