diff --git a/Dockerfile b/Dockerfile index 9cd94a3..afe2e67 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN npm ci COPY tsconfig.json vitest.config.ts ./ COPY server ./server RUN npm run build:server && npm test +RUN npm prune --omit=dev # ---- runtime stage ---- FROM node:22-slim diff --git a/server/src/routes/authRoutes.ts b/server/src/routes/authRoutes.ts index 2ba047e..ca70abb 100644 --- a/server/src/routes/authRoutes.ts +++ b/server/src/routes/authRoutes.ts @@ -80,18 +80,25 @@ export async function registerAuthRoutes(app: FastifyInstance): Promise { const { username, password } = (request.body ?? {}) as { username?: string; password?: string } const invalid = validateCredentials(username, password) if (invalid) return reply.code(400).send({ error: 'invalid_input', detail: invalid }) - let user - try { - user = createUser(request.server.db, username as string, await hashPassword(password as string), true) - } catch (err: any) { - if (String(err.message).includes('UNIQUE constraint failed')) { - return reply.code(403).send({ error: 'setup_already_done' }) - } - throw err - } - request.server.db.prepare('INSERT INTO settings (user_id) VALUES (?)').run(user.id) - const token = createSession(request.server.db, user.id) - return reply.setCookie(COOKIE_NAME, token, cookieOpts()).code(200).send({ user: toPublicUser(user) }) + const passwordHash = await hashPassword(password as string) + + const insertSetup = request.server.db.transaction((): 'taken' | { user: UserRow } => { + const current = ( + request.server.db.prepare('SELECT COUNT(*) AS n FROM users').get() as { n: number } + ).n + if (current > 0) return 'taken' + const user = createUser(request.server.db, username as string, passwordHash, true) + request.server.db.prepare('INSERT INTO settings (user_id) VALUES (?)').run(user.id) + return { user } + }) + const result = insertSetup.immediate() + if (result === 'taken') return reply.code(403).send({ error: 'setup_already_done' }) + + const token = createSession(request.server.db, result.user.id) + return reply + .setCookie(COOKIE_NAME, token, cookieOpts()) + .code(200) + .send({ user: toPublicUser(result.user) }) }) app.post('/api/login', async (request, reply) => {