implemented login
This commit is contained in:
parent
2feb97ddfe
commit
db44350bc9
|
|
@ -23,3 +23,12 @@ export const enum RegisterResponseCause {
|
||||||
PasswordLength,
|
PasswordLength,
|
||||||
DisplayNameLength
|
DisplayNameLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum LoginResponseCause {
|
||||||
|
Server = 1,
|
||||||
|
MalformedRequest,
|
||||||
|
EmailLength,
|
||||||
|
PasswordLength,
|
||||||
|
NotFound,
|
||||||
|
Timeout
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue