128 lines
4.5 KiB
TypeScript
128 lines
4.5 KiB
TypeScript
import {Request, Response} from "express";
|
|
import {Session} from "express-session";
|
|
import geoip from "geoip-lite";
|
|
|
|
import Application from "../../Application.js";
|
|
import {route} from "../../common/Routing.js";
|
|
import ModelFactory from "../../db/ModelFactory.js";
|
|
import {WhereTest} from "../../db/ModelQuery.js";
|
|
import Validator, {EMAIL_REGEX} from "../../db/Validator.js";
|
|
import MailTemplate from "../../mail/MailTemplate.js";
|
|
import AuthMethod from "../AuthMethod.js";
|
|
import MagicLink from "../models/MagicLink.js";
|
|
import User from "../models/User.js";
|
|
import UserEmail from "../models/UserEmail.js";
|
|
import UserNameComponent from "../models/UserNameComponent.js";
|
|
import AuthMagicLinkActionType from "./AuthMagicLinkActionType.js";
|
|
import MagicLinkController from "./MagicLinkController.js";
|
|
|
|
export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
|
|
public constructor(
|
|
protected readonly app: Application,
|
|
protected readonly magicLinkMailTemplate: MailTemplate,
|
|
) {
|
|
}
|
|
|
|
public getName(): string {
|
|
return 'magic_link';
|
|
}
|
|
|
|
public getWeightForRequest(req: Request): number {
|
|
return !req.body.identifier || !EMAIL_REGEX.test(req.body.identifier) ?
|
|
0 :
|
|
1;
|
|
}
|
|
|
|
public async findUserByIdentifier(identifier: string): Promise<User | null> {
|
|
return (await UserEmail.select()
|
|
.with('user.mainEmail')
|
|
.where('email', identifier)
|
|
.first())?.user.getOrFail() || null;
|
|
}
|
|
|
|
public async getProofsForSession(session: 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('used', 0)
|
|
.first();
|
|
|
|
if (pendingLink) {
|
|
if (await pendingLink.isValid()) {
|
|
res.redirect(route('magic_link_lobby', undefined, {
|
|
redirect_uri: req.getIntendedUrl() || pendingLink.original_url || undefined,
|
|
}));
|
|
return true;
|
|
} else {
|
|
await pendingLink.delete();
|
|
}
|
|
}
|
|
|
|
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> {
|
|
const geo = geoip.lookup(req.ip);
|
|
const actionType = isRegistration ? AuthMagicLinkActionType.REGISTER : AuthMagicLinkActionType.LOGIN;
|
|
|
|
if (isRegistration) {
|
|
const usernameValidator = new Validator();
|
|
if (ModelFactory.get(User).hasComponent(UserNameComponent)) usernameValidator.defined();
|
|
|
|
await Validator.validate({
|
|
email: new Validator().defined().unique(UserEmail, 'email'),
|
|
name: usernameValidator,
|
|
}, {
|
|
email: email,
|
|
name: req.body.name,
|
|
});
|
|
}
|
|
|
|
req.getSession().wantsSessionPersistence = !!req.body.persist_session || isRegistration;
|
|
|
|
await MagicLinkController.sendMagicLink(
|
|
this.app,
|
|
req.getSession().id,
|
|
actionType,
|
|
route('auth', undefined, {
|
|
redirect_uri: req.getIntendedUrl() || undefined,
|
|
}),
|
|
email,
|
|
this.magicLinkMailTemplate,
|
|
{
|
|
type: actionType,
|
|
ip: req.ip,
|
|
geo: geo ? `${geo.city}, ${geo.country}` : 'Unknown location',
|
|
},
|
|
{
|
|
username: req.body.name,
|
|
},
|
|
);
|
|
|
|
res.redirect(route('magic_link_lobby', undefined, {
|
|
redirect_uri: req.getIntendedUrl(),
|
|
}));
|
|
}
|
|
}
|