Files
gerbeur/api/services/invite-service.ts

54 lines
1.5 KiB
TypeScript

import { APIErrorCode, APIException } from "../model/interfaces.ts";
import { db, isInviteRow } from "../model/db.ts";
import { createInviteToken, verifyInviteToken } from "../lib/jwt.ts";
export async function createInvite(inviterId: string): Promise<string> {
const token = await createInviteToken(inviterId);
db.prepare(
`INSERT INTO invites (token, inviter_id, created_at) VALUES (?, ?, ?);`,
).run(token, inviterId, new Date().toISOString());
return token;
}
/**
* Verifies the JWT signature + expiry and checks the token exists and has not
* been used. Returns the inviterId on success; throws APIException otherwise.
*/
export async function validateInvite(token: string): Promise<string> {
const payload = await verifyInviteToken(token);
if (!payload) {
throw new APIException(
APIErrorCode.NOT_FOUND,
404,
"Invalid or expired invite",
);
}
const row = db.prepare(
`SELECT token, inviter_id, used_at, created_at FROM invites WHERE token = ?;`,
).get(token);
if (!row || !isInviteRow(row)) {
throw new APIException(APIErrorCode.NOT_FOUND, 404, "Invite not found");
}
if (row.used_at !== null) {
throw new APIException(
APIErrorCode.VALIDATION_ERROR,
409,
"Invite already used",
);
}
return payload.inviterId;
}
/**
* Marks the token as used. Call this only after the user has been created.
*/
export function redeemInvite(token: string): void {
db.prepare(
`UPDATE invites SET used_at = ? WHERE token = ?;`,
).run(new Date().toISOString(), token);
}