64 lines
2.1 KiB
TypeScript
64 lines
2.1 KiB
TypeScript
import type { Handler } from 'hono';
|
|
import { sendMail } from 'emailer';
|
|
import { getUserCount, createUser } from 'officerdb';
|
|
import { sign, verify } from '@@/jwt';
|
|
import argon2 from 'argon2';
|
|
import * as errors from '@@/custom-errors';
|
|
import { syncUserPiConfig } from '@@/api/server-settings/sync-user-pi-config';
|
|
import { validatePassword } from './validate-password';
|
|
|
|
export const bootstrapHandler: Handler = async function (ctx) {
|
|
const body = ctx.get('body');
|
|
const origin = ctx.get('origin');
|
|
const token = body.token as string;
|
|
const email = body.email as string;
|
|
|
|
const userCount = getUserCount();
|
|
if (userCount > 0) throw errors.FORBIDDEN('Registration is closed');
|
|
|
|
if (!token) {
|
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
throw errors.BAD_REQUEST('Invalid email address');
|
|
}
|
|
|
|
const verificationCode = await sign({ email }, '24h');
|
|
const url = `${origin}/auth/verify?verificationCode=${verificationCode}`;
|
|
|
|
await sendMail({
|
|
template: 'VerifyAdmin',
|
|
subject: 'Verify your officer.dev account',
|
|
to: email,
|
|
data: { name: email, url },
|
|
});
|
|
|
|
return ctx.json({ ok: true });
|
|
}
|
|
|
|
const payload = ((await verify(token).catch(() => null)) as { email: string } | null);
|
|
if (!payload?.email) throw errors.BAD_REQUEST('Invalid or expired token');
|
|
|
|
const name = body.name as string;
|
|
const username = body.username as string;
|
|
const password = body.password as string;
|
|
const confirmPassword = body.confirmPassword as string;
|
|
|
|
if (!name || !name.trim()) throw errors.BAD_REQUEST('Name is required');
|
|
if (!username || !username.trim()) throw errors.BAD_REQUEST('Username is required');
|
|
validatePassword(password);
|
|
if (password !== confirmPassword) throw errors.BAD_REQUEST('Passwords do not match');
|
|
|
|
const passwordHash = await argon2.hash(password);
|
|
|
|
await createUser({
|
|
email: payload.email,
|
|
password: passwordHash,
|
|
name: name.trim(),
|
|
username: username.trim(),
|
|
role: 'Super Admin',
|
|
status: 'Active',
|
|
});
|
|
|
|
syncUserPiConfig(payload.email).catch(() => {});
|
|
return ctx.json({ ok: true });
|
|
};
|