implemented login

This commit is contained in:
Patrick 2025-09-30 10:59:27 +02:00
parent 2feb97ddfe
commit db44350bc9
3 changed files with 129 additions and 6 deletions

View File

@ -23,3 +23,12 @@ export const enum RegisterResponseCause {
PasswordLength, PasswordLength,
DisplayNameLength DisplayNameLength
} }
export const enum LoginResponseCause {
Server = 1,
MalformedRequest,
EmailLength,
PasswordLength,
NotFound,
Timeout
}

View File

@ -1,14 +1,80 @@
import Bun from "bun" import Bun from "bun"
import Crypto from "node:crypto"
import { Prisma } from "@prisma/client"
import { ArgumentError, DuplicateError } from "$lib/errors" import { ArgumentError, DuplicateError } from "$lib/errors"
import Log from "$lib/server/log" import Log from "$lib/server/log"
import db from "$lib/server/database" import db from "$lib/server/database"
import { Prisma } from "@prisma/client"
const SESSION_LIFETIME = 15 * 60 * 1000
export type User = Prisma.UserGetPayload<Prisma.UserDefaultArgs>
export interface SessionData {
user: User
token: string
issued: Date
expires: Date
}
interface CacheUserInfo {
user: User
last_update: Date
}
interface CacheSessionInfo {
user_id: number
issued: Date
expires: Date
}
class Cache {
private _id_map: Map<number, CacheUserInfo> = new Map()
private _session_map: Map<string, CacheSessionInfo> = new Map()
add(user: User, token: string, issued: Date, expires: Date): SessionData {
this._id_map.set(user.id, {
user: user,
last_update: new Date()
})
this._session_map.set(token, {
user_id: user.id,
issued: issued,
expires: expires
})
return {
user: user,
token: token,
issued: issued,
expires: expires
}
}
get_session(token: string): SessionData|null {
const session_info = this._session_map.get(token)
if (!session_info) return null
const user_info = this._id_map.get(session_info.user_id)
if (!user_info) return null
return {
user: user_info.user,
token: token,
issued: session_info.issued,
expires: session_info.expires
}
}
}
class UserMgmt { class UserMgmt {
_cache: Cache = new Cache()
async register(email: string, password: string, display_name: string) { async register(email: string, password: string, display_name: string): Promise<User|null> {
if (email.length == 0 || password.length == 0 || display_name.length == 0) { if (email.length == 0 || password.length == 0 || display_name.length == 0) {
throw new ArgumentError("No field may be empty") throw new ArgumentError("No field may be empty")
} }
@ -23,7 +89,6 @@ class UserMgmt {
}) })
Log.info(Log.module.USER, `Created user with id ${user.id}`) Log.info(Log.module.USER, `Created user with id ${user.id}`)
console.log(user)
return user; return user;
} catch (e) { } catch (e) {
@ -37,16 +102,32 @@ class UserMgmt {
} }
} }
async login(email: string, password: string) { async login(email: string, password: string): Promise<SessionData|null> {
const user = await db.user.findUnique({ const user = await db.user.findUnique({
where: { where: {
email: email email: email
} }
}) })
if (!user) {
// throw of timing attacks
await Bun.password.verify("a", "$argon2id$v=19$m=16,t=2,p=1$ZHB6Zjd4NXV6RXZBZk9wRg$QaYjeAGLon+x3c5I1KB7UQ")
return null
} }
if (!await Bun.password.verify(password, user.password_hash)) {
return null
}
const token = Crypto.randomBytes(32).toBase64()
const session_info = this._cache.add(user, token, new Date(), new Date(Date.now() + SESSION_LIFETIME))
return session_info
}
async session_login(token: string): Promise<SessionData|null> {
return this._cache.get_session(token)
}
} }
const _manager = new UserMgmt() const _manager = new UserMgmt()

View File

@ -0,0 +1,33 @@
import type { RequestHandler } from "./$types"
import { error, json, text } from "@sveltejs/kit"
import UserMgmt from "$lib/server/usermgmt"
import { LoginResponseCause } from "$lib/errors"
export const POST: RequestHandler = async ({ request, cookies }) => {
const data = await request.formData()
const email = data.get("email")
const password = data.get("password")
if (data.keys.length > 2 || !(typeof email === "string" && typeof password === "string")) {
return error(400, { cause: LoginResponseCause.MalformedRequest, message: "Invalid request" })
}
if (email.length == 0) {
return error(400, { cause: LoginResponseCause.EmailLength, message: "Email must be provided" })
}
if (password.length == 0) {
return error(400, { cause: LoginResponseCause.PasswordLength, message: "Password must be provided" })
}
const session = await UserMgmt.login(email, password)
if (!session) {
return error(401, { message: "Invalid username or password" })
}
return json({ token: session.token, expires: session.expires })
}