98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
import { sql } from 'drizzle-orm';
|
|
import * as z from 'zod';
|
|
import { INSTANCE_SETTINGS_ID, instanceSettings, users } from '~~/drizzle/schema';
|
|
import { auth } from '~~/lib/auth';
|
|
import { db } from '~~/server/lib/db';
|
|
|
|
// any fixed number works, it only has to be the same for every request
|
|
const SETUP_LOCK_ID = 7_261_008;
|
|
|
|
/**
|
|
* Creates the first account of the instance as its admin.
|
|
*
|
|
* Public, because nobody can be signed in yet, and only works while there are
|
|
* no accounts at all. It does not go through sign up, so it also works when
|
|
* registration is turned off.
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const result = await readValidatedBody(event, (body) =>
|
|
z
|
|
.object({
|
|
name: z.string().trim().min(1).max(128),
|
|
email: z.email(),
|
|
password: z.string().min(8).max(128),
|
|
})
|
|
.safeParse(body),
|
|
);
|
|
|
|
if (!result.success) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: result.error.issues[0]!.message,
|
|
data: {
|
|
code: 'INVALID_BODY',
|
|
},
|
|
});
|
|
}
|
|
|
|
const { name, email, password } = result.data;
|
|
|
|
let alreadyDone = false;
|
|
|
|
try {
|
|
await db.transaction(async (tx) => {
|
|
// two people submitting the form at the same moment wait here, and
|
|
// the second one then finds the first one's account
|
|
await tx.execute(sql`select pg_advisory_xact_lock(${SETUP_LOCK_ID})`);
|
|
|
|
const existing = await tx.select({ id: users.id }).from(users).limit(1);
|
|
if (existing.length > 0) {
|
|
alreadyDone = true;
|
|
return;
|
|
}
|
|
|
|
await auth.api.createUser({
|
|
body: {
|
|
name,
|
|
email,
|
|
password,
|
|
role: 'admin',
|
|
},
|
|
});
|
|
|
|
// A new instance starts invite only. The next setup step lets the
|
|
// admin pick, and if they never get there it is not left open.
|
|
await tx.insert(instanceSettings)
|
|
.values({ id: INSTANCE_SETTINGS_ID, registration: 'invite' })
|
|
.onConflictDoNothing();
|
|
});
|
|
} catch (error: any) {
|
|
const code = error?.body?.code as string | undefined;
|
|
|
|
if (code === undefined) {
|
|
console.error('Failed to create the admin account:', error);
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: code === undefined ? 500 : 400,
|
|
statusMessage: 'Failed to set up the instance',
|
|
message: error?.body?.message ?? 'Failed to set up the instance',
|
|
data: {
|
|
code: code ?? 'INTERNAL_ERROR',
|
|
},
|
|
});
|
|
}
|
|
|
|
if (alreadyDone) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
statusMessage: 'This instance is already set up',
|
|
data: {
|
|
code: 'SETUP_ALREADY_DONE',
|
|
},
|
|
});
|
|
}
|
|
|
|
return { ok: true };
|
|
});
|