From 46e55f10c509f74f21e69d1af6eeb2957846785e Mon Sep 17 00:00:00 2001 From: Eclypsed Date: Mon, 19 Feb 2024 15:03:39 -0500 Subject: [PATCH] Started playing around with typescript generic types --- src/app.d.ts | 5 ++- src/hooks.server.ts | 2 +- src/lib/server/users.db | Bin 24576 -> 24576 bytes src/lib/server/users.ts | 30 +++++-------- src/routes/api/jellyfin/auth/+server.ts | 41 ------------------ src/routes/login/+page.server.ts | 11 ++--- .../settings/connections/+page.server.ts | 1 + 7 files changed, 21 insertions(+), 69 deletions(-) delete mode 100644 src/routes/api/jellyfin/auth/+server.ts diff --git a/src/app.d.ts b/src/app.d.ts index e58412e..e827dee 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -4,7 +4,7 @@ declare global { namespace App { // interface Error {} interface Locals { - user: User + user: Omit } // interface PageData {} // interface PageState {} @@ -21,7 +21,7 @@ declare global { interface User { id: string username: string - password?: string + password: string } type serviceType = 'jellyfin' | 'youtube-music' @@ -39,6 +39,7 @@ declare global { accessToken: string refreshToken?: string expiry?: number + connectionInfo?: ConnectionInfo } interface ConnectionInfo { diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 86ec659..450b6bf 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -17,7 +17,7 @@ export const handle: Handle = async ({ event, resolve }) => { if (!authToken) throw redirect(303, `/login?redirect=${urlpath}`) try { - const tokenData = jwt.verify(authToken, SECRET_JWT_KEY) as User + const tokenData = jwt.verify(authToken, SECRET_JWT_KEY) as Omit event.locals.user = tokenData } catch { throw redirect(303, `/login?redirect=${urlpath}`) diff --git a/src/lib/server/users.db b/src/lib/server/users.db index 7696ed727799db85dba7b8a7f008ce6c1f970add..ab11870db548a0c77167ae4164d931957813423a 100644 GIT binary patch delta 621 zcma)(&5qJg0EHz{7aB67FQ5yy9d3IIZC#jLep;Ztlu|CVxR~6Q{=t-%QYq4MMVn~;OR2cpC?;sUS?@0`LB9hQ+e z`mX^FY;w7o+*m>N4ZnMTq&QU{TR>SZ1PK{IDo zTL`2vE-iE_3=sd@#m|{KUMPOW#t7S$M|#i_~BWD7lB7Pj$t{^ zW%!08GNSMJj4$#$WIeVCfqM?+TZ%v3wlq40-K3OI;n*yK>LN{5-v~OUb-IkTxVM{O zsXK3bo7G|6?n_DryJ2j{Q4nZ@vK2Qu8Y%uLFiEzyq!dTo&Jf*Bw$t@NG0S&Lx;oI0 zQUYyQ=&3i6lOj{*MX?{W3{AiF%bOw|lpdVpEpC$x4?`Oy`9hjBns_T*8?+tRDx!vo z#@LjNo?>x0$|EusRP|ae$cQDaC0?p2MSv9`Td#x0&@vgN%%4~J!Isqo(0`*&zhn|G14b%kGitmOZLK4wVH6n2r{>l T!E7VBm9M{EzdQandXA63CS<^D delta 882 zcma))J#X4j7=?kfQWGhnRmTjC5K=nymTUW7Upt%mGOjTJjBP@P=w5#W12H7nK&YYw zwQHv=-SY#|rdy}%{R18PKf0E=>cDb_S2{X+&v|Yh(>IUluU}sEAtjIzR{~h(U4R70 z1Bp=#e2z*!#k?$tq!GIP@rX|IJk4`3Z{tQXs&F`RBvxJYw6Y0%i=&$9gu&o#TZ_oB zSD{^IN~+El00+ zXzBD=SGuRB(l*R$tEP5&BP@vnoh2H75-+typDT}BD2Q5+BOF&KKx~c#^Fpi*<-}RL zHKna6s$!Xqc@@-U?P89l_Pl2gY!b)rwQhnam_#$Vqj%dH8n%kd1|fE&&lN)bo(QKbXZpMU)PbGNe7&(^CP8>ti(2*k+u01R9oAeWbbga`w2fSD&i z5e9+yzfFvPLHm%4FDHKPFgNf=qst(Sa&L0U)W6WY^^FK3a{V=?k}D!6LxzcneJr|E zk`Tng?W*yzcvvX-?&xwdUF$gU4kajyxvL=XWf#g&448~Wfl)?01lP8JU?g~WCzVQN fviGU%efHP)7yEbF*6S@Ry$NcWgDq=&lmGt$MJWR9 diff --git a/src/lib/server/users.ts b/src/lib/server/users.ts index 58abf85..bdf4f85 100644 --- a/src/lib/server/users.ts +++ b/src/lib/server/users.ts @@ -1,10 +1,14 @@ -import Database from 'better-sqlite3' +import Database, { SqliteError } from 'better-sqlite3' import { generateUUID } from '$lib/utils' import { isValidURL } from '$lib/utils' const db = new Database('./src/lib/server/users.db', { verbose: console.info }) db.pragma('foreign_keys = ON') -const initUsersTable = 'CREATE TABLE IF NOT EXISTS Users(id VARCHAR(36) PRIMARY KEY, username VARCHAR(30) UNIQUE NOT NULL, password VARCHAR(72) NOT NULL)' +const initUsersTable = `CREATE TABLE IF NOT EXISTS Users( + id VARCHAR(36) PRIMARY KEY, + username VARCHAR(30) UNIQUE NOT NULL, + password VARCHAR(72) NOT NULL +)` const initConnectionsTable = `CREATE TABLE IF NOT EXISTS Connections( id VARCHAR(36) PRIMARY KEY, userId VARCHAR(36) NOT NULL, @@ -16,10 +20,6 @@ const initConnectionsTable = `CREATE TABLE IF NOT EXISTS Connections( )` db.exec(initUsersTable), db.exec(initConnectionsTable) -type UserQueryParams = { - includePassword?: boolean -} - interface ConnectionsTableSchema { id: string userId: string @@ -30,25 +30,19 @@ interface ConnectionsTableSchema { } export class Users { - static getUser = (id: string, params: UserQueryParams | null = null): User | undefined => { - const user = db.prepare('SELECT * FROM Users WHERE id = ?').get(id) as User | undefined - if (user && !params?.includePassword) delete user.password + static getUser = (id: string): User | null => { + const user = db.prepare('SELECT * FROM Users WHERE id = ?').get(id) as User | null return user } - static getUsername = (username: string, params: UserQueryParams | null = null): User | undefined => { - const user = db.prepare('SELECT * FROM Users WHERE lower(username) = ?').get(username.toLowerCase()) as User | undefined - if (user && !params?.includePassword) delete user.password + static getUsername = (username: string): User | null => { + const user = db.prepare('SELECT * FROM Users WHERE lower(username) = ?').get(username.toLowerCase()) as User | null return user } - static allUsers = (includePassword: boolean = false): User[] => { - const users = db.prepare('SELECT * FROM Users').all() as User[] - if (!includePassword) users.forEach((user) => delete user.password) - return users - } + static addUser = (username: string, hashedPassword: string): User | null => { + if (this.getUsername(username)) return null - static addUser = (username: string, hashedPassword: string): User => { const userId = generateUUID() db.prepare('INSERT INTO Users(id, username, password) VALUES(?, ?, ?)').run(userId, username, hashedPassword) return this.getUser(userId)! diff --git a/src/routes/api/jellyfin/auth/+server.ts b/src/routes/api/jellyfin/auth/+server.ts deleted file mode 100644 index 5443c82..0000000 --- a/src/routes/api/jellyfin/auth/+server.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { RequestHandler } from '@sveltejs/kit' -import { isValidURL } from '$lib/utils' -import { z } from 'zod' - -export const POST: RequestHandler = async ({ request, fetch }) => { - const jellyfinAuthSchema = z.object({ - serverUrl: z.string().refine((val) => isValidURL(val)), - username: z.string(), - password: z.string(), - deviceId: z.string(), - }) - - const jellyfinAuthData = await request.json() - const jellyfinAuthValidation = jellyfinAuthSchema.safeParse(jellyfinAuthData) - if (!jellyfinAuthValidation.success) return new Response('Invalid data in request body', { status: 400 }) - - const { serverUrl, username, password, deviceId } = jellyfinAuthValidation.data - const authUrl = new URL('/Users/AuthenticateByName', serverUrl).href - try { - const authResponse = await fetch(authUrl, { - method: 'POST', - body: JSON.stringify({ - Username: username, - Pw: password, - }), - headers: { - 'Content-Type': 'application/json; charset=utf-8', - 'X-Emby-Authorization': `MediaBrowser Client="Lazuli", Device="Chrome", DeviceId="${deviceId}", Version="1.0.0.0"`, - }, - }) - if (!authResponse.ok) return new Response('Failed to authenticate', { status: 401 }) - - const authData = await authResponse.json() - return Response.json({ - userId: authData.User.Id, - accessToken: authData.AccessToken, - }) - } catch { - return new Response('Fetch request failed', { status: 404 }) - } -} diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts index 27a56f4..7e3f99f 100644 --- a/src/routes/login/+page.server.ts +++ b/src/routes/login/+page.server.ts @@ -15,10 +15,10 @@ export const actions: Actions = { const formData = await request.formData() const { username, password, redirectLocation } = Object.fromEntries(formData) - const user = Users.getUsername(username.toString(), { includePassword: true }) + const user = Users.getUsername(username.toString()) if (!user) return fail(400, { message: 'Invalid Username' }) - const passwordValid = await compare(password.toString(), user.password!) + const passwordValid = await compare(password.toString(), user.password) if (!passwordValid) return fail(400, { message: 'Invalid Password' }) const authToken = jwt.sign({ id: user.id, username: user.username }, SECRET_JWT_KEY, { expiresIn: '100d' }) @@ -33,14 +33,11 @@ export const actions: Actions = { const formData = await request.formData() const { username, password } = Object.fromEntries(formData) - const existingUsers = Users.allUsers() - const existingUsernames = Array.from(existingUsers, (user) => user.username) - if (username.toString() in existingUsernames) return fail(400, { message: 'Username already in use' }) - const passwordHash = await hash(password.toString(), 10) const newUser = Users.addUser(username.toString(), passwordHash) + if (!newUser) return fail(400, { message: 'Username already in use' }) - const authToken = jwt.sign(newUser, SECRET_JWT_KEY, { expiresIn: '100d' }) + const authToken = jwt.sign({ id: newUser.id, username: newUser.username }, SECRET_JWT_KEY, { expiresIn: '100d' }) cookies.set('lazuli-auth', authToken, { path: '/', httpOnly: true, sameSite: 'strict', secure: false, maxAge: 60 * 60 * 24 * 100 }) diff --git a/src/routes/settings/connections/+page.server.ts b/src/routes/settings/connections/+page.server.ts index 906520e..94de70b 100644 --- a/src/routes/settings/connections/+page.server.ts +++ b/src/routes/settings/connections/+page.server.ts @@ -12,6 +12,7 @@ export const load: PageServerLoad = async ({ fetch, locals }) => { }) const userConnections = await connectionsResponse.json() + return { connections: userConnections.connections } }