import { createError, defineEventHandler, readValidatedBody } from 'h3'; import Database from '#server/utils/Database'; import { SERVER_DEBUG, WG_ENV } from '#server/utils/config'; import { useWGSession } from '#server/utils/session'; import { assertUnreachable, validateZod } from '#server/utils/types'; import { UserLoginSchema } from '#db/repositories/user/types'; export default defineEventHandler(async (event) => { if (WG_ENV.DISABLE_PASSWORD_AUTH) { throw createError({ statusCode: 403, statusMessage: 'Password authentication is disabled', }); } const { username, password, remember } = await readValidatedBody( event, validateZod(UserLoginSchema, event) ); const result = await Database.users.login(username, password); const session = await useWGSession(event, remember); // TODO: add localization support if (!result.success) { switch (result.error) { case 'INCORRECT_CREDENTIALS': throw createError({ statusCode: 401, statusMessage: 'Invalid username or password', }); case 'TOTP_REQUIRED': await session.update({ pendingLogin: { type: 'password', userId: result.userId, remember, // 5min expires_at: Date.now() + 5 * 60 * 1000, }, }); return { status: 'TOTP_REQUIRED' as const }; case 'INVALID_TOTP_CODE': return { status: 'INVALID_TOTP_CODE' as const }; case 'USER_DISABLED': throw createError({ statusCode: 401, statusMessage: 'User disabled', }); case 'UNEXPECTED_ERROR': throw createError({ statusCode: 500, statusMessage: 'Unexpected error', }); } assertUnreachable(result); } const user = result.user; const data = await session.update({ userId: user.id, }); // TODO?: create audit log SERVER_DEBUG(`New Session: ${data.id} for ${user.id} (${user.username})`); return { status: 'success' as const }; });