Loading backend/package.json +1 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ "body-parser": "^1.20.2", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "cron": "^4.3.0", "express": "^4.19.2", "express-session": "^1.18.0", "ioredis": "^5.6.1", Loading backend/src/controllers/AuthSession.ts 0 → 100644 +86 −0 Original line number Diff line number Diff line import { AuthSession as DBAuthSession } from "@prisma/client"; import { prisma } from "../lib/prisma.js"; export class AuthSession { static generateCode(): string { return "." .repeat(5) .split("") .map(() => Math.floor(Math.random() * 10)) .join(""); } static async create(handle: `${string}@${string}`): Promise<AuthSession> { // day in seconds const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24); const session = await prisma.authSession.create({ data: { one_time_code: this.generateCode(), user_sub: handle, expiresAt, }, }); return new AuthSession(session); } static async getExpired() { const sessions = await prisma.authSession.findMany({ where: { expiresAt: { lte: new Date(), }, }, }); return sessions.map((d) => new AuthSession(d)); } static async getActive( handle: `${string}@${string}` ): Promise<AuthSession | null> { const session = await prisma.authSession.findFirst({ where: { user_sub: handle, expiresAt: { gt: new Date(), }, }, }); if (!session) return null; return new AuthSession(session); } private _id: string; private _user_sub: `${string}@${string}`; private _createdAt: Date; private _expiresAt: Date; private _code: string; private constructor(session: DBAuthSession) { this._id = session.id; this._user_sub = session.user_sub as any; this._createdAt = session.createdAt; this._expiresAt = session.expiresAt; this._code = session.one_time_code; } get id() { return this._id; } get user_sub() { return this._user_sub; } get createdAt() { return this._createdAt; } get expiresAt() { return this._expiresAt; } get code() { return this._code; } } backend/src/index.ts +2 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ import { configure, getConsoleSink } from "@logtape/logtape"; import { app as Express } from "./lib/express.js"; import { oidc } from "./lib/oidc.js"; import { FederationWorker } from "./lib/apub/worker.js"; import { Jobs } from "./jobs/jobs.js"; if (typeof process.env.SESSION_SECRET !== "string") { throw new Error("SESSION_SECRET is not defined"); Loading Loading @@ -54,6 +55,7 @@ await configure({ }); if (process.env.NODE_TYPE === "worker") { Jobs.start(); FederationWorker.create().then(() => { console.log("FederationWorker started"); }); Loading backend/src/jobs/jobs.ts 0 → 100644 +30 −0 Original line number Diff line number Diff line import { CronJob } from "cron"; import { AuthSession } from "../controllers/AuthSession.js"; import { APub } from "../lib/apub/utils.js"; import { prisma } from "../lib/prisma.js"; export class Jobs { private static instance: Jobs; static start() { this.instance = new Jobs(); CronJob.from({ name: "Destroy expired tokens", cronTime: "*/5 * * * *", onTick: this.instance.handleExpiredTokens.bind(this), start: true, }); } async handleExpiredTokens() { const expired = await AuthSession.getExpired(); console.debug(`Deleting ${expired.length} expired tokens`); for (const session of expired) { console.debug(`Deleting ${session.id}`); await APub.deleteDM(session); await prisma.authSession.delete({ where: { id: session.id } }); } } } backend/src/lib/api.ts +19 −12 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ import { IProfile, getUserMeta } from "./instance/userMeta.js"; import { IInstance, getInstanceMeta } from "./instance/instanceMeta.js"; import { ShadowAPI, UserLoginError } from "./shadow.js"; import { APub } from "./apub/utils.js"; import { AuthSession } from "../controllers/AuthSession.js"; const app = express.Router(); Loading Loading @@ -130,20 +131,26 @@ app.post("/login/step/username", async (req, res) => { // this is the prompt for the user req.session.login.prompt = "ENTER_CODE"; const code = "." .repeat(5) .split("") .map(() => Math.floor(Math.random() * 10)) .join(""); // TODO: prevent spam sending codes to someone const session = await prisma.authSession.create({ const existing = await AuthSession.getActive(`${username}@${instance}`); if (existing) { // if there's an active session, don't create another one req.session.login.session_id = existing.id; req.session.save(() => { res.send({ success: true, data: { one_time_code: code, user_sub: [username, instance].join("@"), session_id: session.id, account: APub.accountHandle, }, }); }); return; } const session = await AuthSession.create(`${username}@${instance}`); req.session.login.session_id = session.id; try { Loading Loading
backend/package.json +1 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ "body-parser": "^1.20.2", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "cron": "^4.3.0", "express": "^4.19.2", "express-session": "^1.18.0", "ioredis": "^5.6.1", Loading
backend/src/controllers/AuthSession.ts 0 → 100644 +86 −0 Original line number Diff line number Diff line import { AuthSession as DBAuthSession } from "@prisma/client"; import { prisma } from "../lib/prisma.js"; export class AuthSession { static generateCode(): string { return "." .repeat(5) .split("") .map(() => Math.floor(Math.random() * 10)) .join(""); } static async create(handle: `${string}@${string}`): Promise<AuthSession> { // day in seconds const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24); const session = await prisma.authSession.create({ data: { one_time_code: this.generateCode(), user_sub: handle, expiresAt, }, }); return new AuthSession(session); } static async getExpired() { const sessions = await prisma.authSession.findMany({ where: { expiresAt: { lte: new Date(), }, }, }); return sessions.map((d) => new AuthSession(d)); } static async getActive( handle: `${string}@${string}` ): Promise<AuthSession | null> { const session = await prisma.authSession.findFirst({ where: { user_sub: handle, expiresAt: { gt: new Date(), }, }, }); if (!session) return null; return new AuthSession(session); } private _id: string; private _user_sub: `${string}@${string}`; private _createdAt: Date; private _expiresAt: Date; private _code: string; private constructor(session: DBAuthSession) { this._id = session.id; this._user_sub = session.user_sub as any; this._createdAt = session.createdAt; this._expiresAt = session.expiresAt; this._code = session.one_time_code; } get id() { return this._id; } get user_sub() { return this._user_sub; } get createdAt() { return this._createdAt; } get expiresAt() { return this._expiresAt; } get code() { return this._code; } }
backend/src/index.ts +2 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ import { configure, getConsoleSink } from "@logtape/logtape"; import { app as Express } from "./lib/express.js"; import { oidc } from "./lib/oidc.js"; import { FederationWorker } from "./lib/apub/worker.js"; import { Jobs } from "./jobs/jobs.js"; if (typeof process.env.SESSION_SECRET !== "string") { throw new Error("SESSION_SECRET is not defined"); Loading Loading @@ -54,6 +55,7 @@ await configure({ }); if (process.env.NODE_TYPE === "worker") { Jobs.start(); FederationWorker.create().then(() => { console.log("FederationWorker started"); }); Loading
backend/src/jobs/jobs.ts 0 → 100644 +30 −0 Original line number Diff line number Diff line import { CronJob } from "cron"; import { AuthSession } from "../controllers/AuthSession.js"; import { APub } from "../lib/apub/utils.js"; import { prisma } from "../lib/prisma.js"; export class Jobs { private static instance: Jobs; static start() { this.instance = new Jobs(); CronJob.from({ name: "Destroy expired tokens", cronTime: "*/5 * * * *", onTick: this.instance.handleExpiredTokens.bind(this), start: true, }); } async handleExpiredTokens() { const expired = await AuthSession.getExpired(); console.debug(`Deleting ${expired.length} expired tokens`); for (const session of expired) { console.debug(`Deleting ${session.id}`); await APub.deleteDM(session); await prisma.authSession.delete({ where: { id: session.id } }); } } }
backend/src/lib/api.ts +19 −12 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ import { IProfile, getUserMeta } from "./instance/userMeta.js"; import { IInstance, getInstanceMeta } from "./instance/instanceMeta.js"; import { ShadowAPI, UserLoginError } from "./shadow.js"; import { APub } from "./apub/utils.js"; import { AuthSession } from "../controllers/AuthSession.js"; const app = express.Router(); Loading Loading @@ -130,20 +131,26 @@ app.post("/login/step/username", async (req, res) => { // this is the prompt for the user req.session.login.prompt = "ENTER_CODE"; const code = "." .repeat(5) .split("") .map(() => Math.floor(Math.random() * 10)) .join(""); // TODO: prevent spam sending codes to someone const session = await prisma.authSession.create({ const existing = await AuthSession.getActive(`${username}@${instance}`); if (existing) { // if there's an active session, don't create another one req.session.login.session_id = existing.id; req.session.save(() => { res.send({ success: true, data: { one_time_code: code, user_sub: [username, instance].join("@"), session_id: session.id, account: APub.accountHandle, }, }); }); return; } const session = await AuthSession.create(`${username}@${instance}`); req.session.login.session_id = session.id; try { Loading