From 3b338801661f780febd5474f956044a1c9884a0f Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Mon, 9 Sep 2024 19:25:16 -0700 Subject: [PATCH 01/14] Creating APIs for changing user password and calling it in change password. Cleaned up CSS layout styles and got settings nav correct. --- package.json | 2 +- pnpm-lock.yaml | 10 +- src/lib/components/Header.svelte | 119 +++++++++--------- src/lib/components/LeftNav.svelte | 77 ------------ src/lib/components/ui/input/input.svelte | 2 +- src/lib/components/ui/toggle/index.ts | 4 +- src/lib/components/ui/toggle/toggle.svelte | 2 +- src/lib/page_loading_indicator.svelte | 65 +++++----- src/lib/server/api/common/config.ts | 2 - src/lib/server/api/common/exceptions.ts | 28 ++--- .../api/controllers/collection.controller.ts | 2 +- .../server/api/controllers/iam.controller.ts | 31 ++++- .../server/api/controllers/mfa.controller.ts | 2 +- .../server/api/controllers/user.controller.ts | 2 +- .../api/controllers/wishlist.controller.ts | 2 +- .../server/api/dtos/change-password.dto.ts | 17 +++ src/lib/server/api/dtos/update-profile.dto.ts | 23 ++-- src/lib/server/api/jobs/auth-cleanup.job.ts | 6 +- .../server/api/middleware/auth.middleware.ts | 13 -- src/lib/server/api/services/iam.service.ts | 7 ++ src/lib/server/api/services/users.service.ts | 16 +++ .../(app)/(protected)/settings/+layout.svelte | 76 ++++++++++- .../security/change/password/+page.server.ts | 45 ++----- .../security/change/password/+page.svelte | 24 +++- .../security/change/password/schemas.ts | 4 +- .../settings/security/mfa/+page.server.ts | 19 ++- .../settings/security/mfa/+page.svelte | 6 +- src/routes/(app)/+layout.server.ts | 26 ++-- src/routes/(app)/+layout.svelte | 50 ++++---- src/routes/(app)/+page.server.ts | 6 +- src/routes/+layout.svelte | 19 +-- 31 files changed, 350 insertions(+), 357 deletions(-) delete mode 100644 src/lib/components/LeftNav.svelte create mode 100644 src/lib/server/api/dtos/change-password.dto.ts diff --git a/package.json b/package.json index e4997a2..bddfaf3 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ }, "type": "module", "dependencies": { - "@fontsource/fira-mono": "^5.0.14", + "@fontsource/fira-mono": "^5.0.15", "@hono/swagger-ui": "^0.4.1", "@hono/zod-openapi": "^0.15.3", "@hono/zod-validator": "^0.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c00d4b2..94e8b55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@fontsource/fira-mono': - specifier: ^5.0.14 - version: 5.0.14 + specifier: ^5.0.15 + version: 5.0.15 '@hono/swagger-ui': specifier: ^0.4.1 version: 0.4.1(hono@4.5.11) @@ -1245,8 +1245,8 @@ packages: '@floating-ui/utils@0.2.7': resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==} - '@fontsource/fira-mono@5.0.14': - resolution: {integrity: sha512-4IKa+cuHipk/vr2frgZh4pyR2XcoQk/j3zmMlo8uuAGUB3IPLpQlgN6Qm5d3RfRZ7dXGlTn/PWiAJeU8bkmD4w==} + '@fontsource/fira-mono@5.0.15': + resolution: {integrity: sha512-wc3TpF2GBbtFDKNbb444BrC3mEKuoPLITSYCKweNIrqBvAalIfJGloY/MVrmSGaMNgaAKUpdgy4eAWPLkUVzaA==} '@gcornut/valibot-json-schema@0.31.0': resolution: {integrity: sha512-3xGptCurm23e7nuPQkdrE5rEs1FeTPHhAUsBuwwqG4/YeZLwJOoYZv+fmsppUEfo5y9lzUwNQrNqLS/q7HMc7g==} @@ -5489,7 +5489,7 @@ snapshots: '@floating-ui/utils@0.2.7': {} - '@fontsource/fira-mono@5.0.14': {} + '@fontsource/fira-mono@5.0.15': {} '@gcornut/valibot-json-schema@0.31.0': dependencies: diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index f614316..4b3a06b 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -4,15 +4,10 @@ import { invalidateAll } from '$app/navigation' import Logo from '$components/logo.svelte' import * as Avatar from '$components/ui/avatar' import * as DropdownMenu from '$components/ui/dropdown-menu' -import type { Users } from '$db/schema' -import { ListChecks, ListTodo, LogOut, User } from 'lucide-svelte' +import { ListChecks, ListTodo, LogOut, Settings } from 'lucide-svelte' import toast from 'svelte-french-toast' -type HeaderProps = { - user: Users | null -} - -let { user = null }: HeaderProps = $props() +let { user = null } = $props() let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)') @@ -28,39 +23,48 @@ let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)') + + +{#snippet userDropdown()} + + + + + {avatar} + + + + + + Account + + + + + Settings + + + + + + Collections + + + + + + Wishlists + + + + { return async ({ result }) => { console.log(result); if (result.type === 'success' || result.type === 'redirect') { @@ -76,26 +80,21 @@ let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)') await applyAction(result); }; }} - action="/logout" - method="POST" - > - - - - - - - {:else} - Login - Sign Up - {/if} - - + action="/logout" + method="POST" + > + + + + + + +{/snippet} \ No newline at end of file diff --git a/src/lib/components/ui/input/input.svelte b/src/lib/components/ui/input/input.svelte index cab1457..477d325 100644 --- a/src/lib/components/ui/input/input.svelte +++ b/src/lib/components/ui/input/input.svelte @@ -1,7 +1,7 @@
diff --git a/src/lib/server/api/common/config.ts b/src/lib/server/api/common/config.ts index 518c73f..0daf6d4 100644 --- a/src/lib/server/api/common/config.ts +++ b/src/lib/server/api/common/config.ts @@ -34,5 +34,3 @@ export const config: Config = { max: env.DB_MIGRATING || env.DB_SEEDING ? 1 : undefined, }, } - -console.log('config', config) diff --git a/src/lib/server/api/common/exceptions.ts b/src/lib/server/api/common/exceptions.ts index fee54c4..cbda928 100644 --- a/src/lib/server/api/common/exceptions.ts +++ b/src/lib/server/api/common/exceptions.ts @@ -1,26 +1,26 @@ -import { StatusCodes } from '$lib/constants/status-codes'; -import { HTTPException } from 'hono/http-exception'; +import { StatusCodes } from '$lib/constants/status-codes' +import { HTTPException } from 'hono/http-exception' -export function TooManyRequests(message: string = 'Too many requests') { - return new HTTPException(StatusCodes.TOO_MANY_REQUESTS, { message }); +export function TooManyRequests(message = 'Too many requests') { + return new HTTPException(StatusCodes.TOO_MANY_REQUESTS, { message }) } -export function Forbidden(message: string = 'Forbidden') { - return new HTTPException(StatusCodes.FORBIDDEN, { message }); +export function Forbidden(message = 'Forbidden') { + return new HTTPException(StatusCodes.FORBIDDEN, { message }) } -export function Unauthorized(message: string = 'Unauthorized') { - return new HTTPException(StatusCodes.UNAUTHORIZED, { message }); +export function Unauthorized(message = 'Unauthorized') { + return new HTTPException(StatusCodes.UNAUTHORIZED, { message }) } -export function NotFound(message: string = 'Not Found') { - return new HTTPException(StatusCodes.NOT_FOUND, { message }); +export function NotFound(message = 'Not Found') { + return new HTTPException(StatusCodes.NOT_FOUND, { message }) } -export function BadRequest(message: string = 'Bad Request') { - return new HTTPException(StatusCodes.BAD_REQUEST, { message }); +export function BadRequest(message = 'Bad Request') { + return new HTTPException(StatusCodes.BAD_REQUEST, { message }) } -export function InternalError(message: string = 'Internal Error') { - return new HTTPException(StatusCodes.INTERNAL_SERVER_ERROR, { message }); +export function InternalError(message = 'Internal Error') { + return new HTTPException(StatusCodes.INTERNAL_SERVER_ERROR, { message }) } diff --git a/src/lib/server/api/controllers/collection.controller.ts b/src/lib/server/api/controllers/collection.controller.ts index 56a53aa..86dfcc5 100644 --- a/src/lib/server/api/controllers/collection.controller.ts +++ b/src/lib/server/api/controllers/collection.controller.ts @@ -2,7 +2,7 @@ import 'reflect-metadata' import { Controller } from '$lib/server/api/common/types/controller' import { CollectionsService } from '$lib/server/api/services/collections.service' import { inject, injectable } from 'tsyringe' -import { requireAuth } from '../middleware/auth.middleware' +import { requireAuth } from '../middleware/require-auth.middleware' @injectable() export class CollectionController extends Controller { diff --git a/src/lib/server/api/controllers/iam.controller.ts b/src/lib/server/api/controllers/iam.controller.ts index 708b7ef..0d2010c 100644 --- a/src/lib/server/api/controllers/iam.controller.ts +++ b/src/lib/server/api/controllers/iam.controller.ts @@ -1,20 +1,23 @@ import { StatusCodes } from '$lib/constants/status-codes' import { Controller } from '$lib/server/api/common/types/controller' +import { changePasswordDto } from '$lib/server/api/dtos/change-password.dto' import { updateEmailDto } from '$lib/server/api/dtos/update-email.dto' import { updateProfileDto } from '$lib/server/api/dtos/update-profile.dto' import { verifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto' import { limiter } from '$lib/server/api/middleware/rate-limiter.middleware' import { IamService } from '$lib/server/api/services/iam.service' +import { LoginRequestsService } from '$lib/server/api/services/loginrequest.service' import { LuciaService } from '$lib/server/api/services/lucia.service' import { zValidator } from '@hono/zod-validator' import { setCookie } from 'hono/cookie' import { inject, injectable } from 'tsyringe' -import { requireAuth } from '../middleware/auth.middleware' +import { requireAuth } from '../middleware/require-auth.middleware' @injectable() export class IamController extends Controller { constructor( @inject(IamService) private readonly iamService: IamService, + @inject(LoginRequestsService) private readonly loginRequestService: LoginRequestsService, @inject(LuciaService) private luciaService: LuciaService, ) { super() @@ -45,6 +48,32 @@ export class IamController extends Controller { } return c.json({}, StatusCodes.OK) }) + .put('/update/password', requireAuth, zValidator('json', changePasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => { + const user = c.var.user + const { password, confirm_password } = c.req.valid('json') + if (password !== confirm_password) { + return c.json('Passwords do not match', StatusCodes.BAD_REQUEST) + } + try { + await this.iamService.updatePassword(user.id, { password, confirm_password }) + await this.luciaService.lucia.invalidateUserSessions(user.id) + await this.loginRequestService.createUserSession(user.id, c.req, undefined) + const sessionCookie = this.luciaService.lucia.createBlankSessionCookie() + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: sessionCookie.attributes.maxAge, + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires, + }) + return c.json({ status: 'success' }) + } catch (error) { + console.error('Error updating password', error) + return c.json('Error updating password', StatusCodes.BAD_REQUEST) + } + }) .post('/update/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => { const user = c.var.user const { email } = c.req.valid('json') diff --git a/src/lib/server/api/controllers/mfa.controller.ts b/src/lib/server/api/controllers/mfa.controller.ts index f70aa04..b08b8c1 100644 --- a/src/lib/server/api/controllers/mfa.controller.ts +++ b/src/lib/server/api/controllers/mfa.controller.ts @@ -8,7 +8,7 @@ import { UsersService } from '$lib/server/api/services/users.service' import { zValidator } from '@hono/zod-validator' import { inject, injectable } from 'tsyringe' import { CredentialsType } from '../databases/tables' -import { requireAuth } from '../middleware/auth.middleware' +import { requireAuth } from '../middleware/require-auth.middleware' @injectable() export class MfaController extends Controller { diff --git a/src/lib/server/api/controllers/user.controller.ts b/src/lib/server/api/controllers/user.controller.ts index e7c5deb..c05cd2e 100644 --- a/src/lib/server/api/controllers/user.controller.ts +++ b/src/lib/server/api/controllers/user.controller.ts @@ -2,7 +2,7 @@ import 'reflect-metadata' import { Controller } from '$lib/server/api/common/types/controller' import { UsersService } from '$lib/server/api/services/users.service' import { inject, injectable } from 'tsyringe' -import { requireAuth } from '../middleware/auth.middleware' +import { requireAuth } from '../middleware/require-auth.middleware' @injectable() export class UserController extends Controller { diff --git a/src/lib/server/api/controllers/wishlist.controller.ts b/src/lib/server/api/controllers/wishlist.controller.ts index 44a4a11..1a8da1c 100644 --- a/src/lib/server/api/controllers/wishlist.controller.ts +++ b/src/lib/server/api/controllers/wishlist.controller.ts @@ -2,7 +2,7 @@ import 'reflect-metadata' import { Controller } from '$lib/server/api/common/types/controller' import { WishlistsService } from '$lib/server/api/services/wishlists.service' import { inject, injectable } from 'tsyringe' -import { requireAuth } from '../middleware/auth.middleware' +import { requireAuth } from '../middleware/require-auth.middleware' @injectable() export class WishlistController extends Controller { diff --git a/src/lib/server/api/dtos/change-password.dto.ts b/src/lib/server/api/dtos/change-password.dto.ts new file mode 100644 index 0000000..b624722 --- /dev/null +++ b/src/lib/server/api/dtos/change-password.dto.ts @@ -0,0 +1,17 @@ +import { refinePasswords } from '$lib/validations/account' +import { z } from 'zod' + +export const changePasswordDto = z + .object({ + password: z.string({ required_error: 'Password is required' }).trim(), + confirm_password: z + .string({ required_error: 'Confirm Password is required' }) + .trim() + .min(8, { message: 'Must be at least 8 characters' }) + .max(128, { message: 'Must be less than 128 characters' }), + }) + .superRefine(({ confirm_password, password }, ctx) => { + return refinePasswords(confirm_password, password, ctx) + }) + +export type ChangePasswordDto = z.infer diff --git a/src/lib/server/api/dtos/update-profile.dto.ts b/src/lib/server/api/dtos/update-profile.dto.ts index 9ea0c6f..1ce379b 100644 --- a/src/lib/server/api/dtos/update-profile.dto.ts +++ b/src/lib/server/api/dtos/update-profile.dto.ts @@ -1,23 +1,14 @@ -import { z } from "zod"; +import { z } from 'zod' export const updateProfileDto = z.object({ firstName: z .string() .trim() - .min(3, {message: 'Must be at least 3 characters'}) - .max(50, {message: 'Must be less than 50 characters'}) + .min(3, { message: 'Must be at least 3 characters' }) + .max(50, { message: 'Must be less than 50 characters' }) .optional(), - lastName: z - .string() - .trim() - .min(3, {message: 'Must be at least 3 characters'}) - .max(50, {message: 'Must be less than 50 characters'}) - .optional(), - username: z - .string() - .trim() - .min(3, {message: 'Must be at least 3 characters'}) - .max(50, {message: 'Must be less than 50 characters'}) -}); + lastName: z.string().trim().min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }).optional(), + username: z.string().trim().min(3, { message: 'Must be at least 3 characters' }).max(50, { message: 'Must be less than 50 characters' }), +}) -export type UpdateProfileDto = z.infer; +export type UpdateProfileDto = z.infer diff --git a/src/lib/server/api/jobs/auth-cleanup.job.ts b/src/lib/server/api/jobs/auth-cleanup.job.ts index fb2ec6c..efaf1c1 100644 --- a/src/lib/server/api/jobs/auth-cleanup.job.ts +++ b/src/lib/server/api/jobs/auth-cleanup.job.ts @@ -10,11 +10,11 @@ export class AuthCleanupJobs { this.queue = this.jobsService.createQueue('test') /* ---------------------------- Register Workers ---------------------------- */ - this.worker().then((r) => console.log('auth-cleanup job worker started')) + this.worker().then(() => console.log('auth-cleanup job worker started')) } async deleteStaleEmailVerificationRequests() { - await this.queue.add('delete_stale_email_verifiactions', null, { + await this.queue.add('delete_stale_email_verifications', null, { repeat: { pattern: '0 0 * * 0', // Runs once a week at midnight on Sunday }, @@ -31,7 +31,7 @@ export class AuthCleanupJobs { private async worker() { return this.jobsService.createWorker(this.queue.name, async (job) => { - if (job.name === 'delete_stale_email_verifiactions') { + if (job.name === 'delete_stale_email_verifications') { // delete stale email verifications } if (job.name === 'delete_stale_login_requests') { diff --git a/src/lib/server/api/middleware/auth.middleware.ts b/src/lib/server/api/middleware/auth.middleware.ts index 3cc3766..b88418b 100644 --- a/src/lib/server/api/middleware/auth.middleware.ts +++ b/src/lib/server/api/middleware/auth.middleware.ts @@ -1,10 +1,8 @@ import { LuciaService } from '$lib/server/api/services/lucia.service' import type { MiddlewareHandler } from 'hono' import { createMiddleware } from 'hono/factory' -import type { Session, User } from 'lucia' import { verifyRequestOrigin } from 'oslo/request' import { container } from 'tsyringe' -import { Unauthorized } from '../common/exceptions' import type { HonoTypes } from '../common/types/hono' // resolve dependencies from the container @@ -41,14 +39,3 @@ export const validateAuthSession: MiddlewareHandler = createMiddlewar c.set('user', user) return next() }) - -export const requireAuth: MiddlewareHandler<{ - Variables: { - session: Session - user: User - } -}> = createMiddleware(async (c, next) => { - const user = c.var.user - if (!user) throw Unauthorized('You must be logged in to access this resource') - return next() -}) diff --git a/src/lib/server/api/services/iam.service.ts b/src/lib/server/api/services/iam.service.ts index 0c3bc6e..a70e8c3 100644 --- a/src/lib/server/api/services/iam.service.ts +++ b/src/lib/server/api/services/iam.service.ts @@ -1,3 +1,5 @@ +import { CredentialsType } from '$lib/server/api/databases/tables' +import type { ChangePasswordDto } from '$lib/server/api/dtos/change-password.dto' import type { UpdateEmailDto } from '$lib/server/api/dtos/update-email.dto' import type { UpdateProfileDto } from '$lib/server/api/dtos/update-profile.dto' import type { VerifyPasswordDto } from '$lib/server/api/dtos/verify-password.dto' @@ -68,6 +70,11 @@ export class IamService { }) } + async updatePassword(userId: string, data: ChangePasswordDto) { + const { password } = data + await this.usersService.updatePassword(userId, password) + } + async verifyPassword(userId: string, data: VerifyPasswordDto) { const user = await this.usersService.findOneById(userId) if (!user) { diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index 89e6eea..f067809 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -69,6 +69,22 @@ export class UsersService { return this.usersRepository.findOneById(id) } + async updatePassword(userId: string, password: string) { + const hashedPassword = await this.tokenService.createHashedToken(password) + const currentCredentials = await this.credentialsRepository.findPasswordCredentialsByUserId(userId) + if (!currentCredentials) { + await this.credentialsRepository.create({ + user_id: userId, + type: CredentialsType.PASSWORD, + secret_data: hashedPassword, + }) + } else { + await this.credentialsRepository.update(currentCredentials.id, { + secret_data: hashedPassword, + }) + } + } + async verifyPassword(userId: string, data: { password: string }) { const user = await this.usersRepository.findOneById(userId) if (!user) { diff --git a/src/routes/(app)/(protected)/settings/+layout.svelte b/src/routes/(app)/(protected)/settings/+layout.svelte index c4620d3..81483b4 100644 --- a/src/routes/(app)/(protected)/settings/+layout.svelte +++ b/src/routes/(app)/(protected)/settings/+layout.svelte @@ -1,5 +1,5 @@ - - {@render children()} - \ No newline at end of file +
+ +
+ {@render children()} +
+
+ + diff --git a/src/routes/(app)/(protected)/settings/security/change/password/+page.server.ts b/src/routes/(app)/(protected)/settings/security/change/password/+page.server.ts index e708aa5..1c4e775 100644 --- a/src/routes/(app)/(protected)/settings/security/change/password/+page.server.ts +++ b/src/routes/(app)/(protected)/settings/security/change/password/+page.server.ts @@ -48,33 +48,11 @@ export const actions: Actions = { }) } - console.log('updating profile') - if (!event.locals.user) { - redirect(302, '/login', notSignedInMessage, event) - } - - if (!event.locals.session) { - return fail(401) - } - - const dbUser = await db.query.usersTable.findFirst({ - where: eq(usersTable.id, authedUser.id), - }) - - // if (!dbUser?.hashed_password) { - // form.data.password = ''; - // form.data.confirm_password = ''; - // form.data.current_password = ''; - // return setError( - // form, - // 'Error occurred. Please try again or contact support if you need further help.', - // ); - // } - - const currentPasswordVerified = await new Argon2id().verify( - // dbUser.hashed_password, - form.data.current_password, - ) + const currentPasswordVerified = await locals.api.me.verify.password + .$post({ + json: { password: form.data.current_password }, + }) + .then(locals.parseApiResponse) if (!currentPasswordVerified) { return setError(form, 'current_password', 'Your password is incorrect') @@ -85,16 +63,9 @@ export const actions: Actions = { if (form.data.password !== form.data.confirm_password) { return setError(form, 'Password and confirm password do not match') } - const hashedPassword = await new Argon2id().hash(form.data.password) - await lucia.invalidateUserSessions(authedUser.id) - // await db - // .update(usersTable) - // .set({ hashed_password: hashedPassword }) - // .where(eq(usersTable.id, user.id)); - await lucia.createSession(user.id, { - country: event.locals.session?.ipCountry ?? 'unknown', - }) - sessionCookie = lucia.createBlankSessionCookie() + await locals.api.me.change.password.$put({ + json: { password: form.data.password, confirm_password: form.data.confirm_password }, + }).then(locals.parseApiResponse) } catch (e) { console.error(e) form.data.password = '' diff --git a/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte b/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte index 302379a..1a46b42 100644 --- a/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte +++ b/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte @@ -2,7 +2,8 @@ import * as Alert from '$components/ui/alert' import * as Form from '$components/ui/form' import { Input } from '$components/ui/input' -import { AlertTriangle } from 'lucide-svelte' +import { Toggle } from '$components/ui/toggle' +import { AlertTriangle, EyeIcon, EyeOff } from 'lucide-svelte' import { zodClient } from 'sveltekit-superforms/adapters' import { superForm } from 'sveltekit-superforms/client' import { changeUserPasswordSchema } from './schemas' @@ -16,13 +17,17 @@ const form = superForm(data.form, { multipleSubmits: 'prevent', }) +let hiddenCurrentPassword = $state(true) +let hiddenPassword = $state(true) +let hiddenConfirmPassword = $state(true) + const { form: formData, enhance } = form

Change Password


- + Heads up! @@ -32,21 +37,30 @@ const { form: formData, enhance } = form Current Password - + + + hiddenCurrentPassword = !hiddenCurrentPassword}>{#if hiddenCurrentPassword}{:else}{/if} + New Password - + + + hiddenPassword = !hiddenPassword}>{#if hiddenPassword}{:else}{/if} + Confirm New Password - + + + hiddenConfirmPassword = !hiddenConfirmPassword}>{#if hiddenConfirmPassword}{:else}{/if} + diff --git a/src/routes/(app)/(protected)/settings/security/change/password/schemas.ts b/src/routes/(app)/(protected)/settings/security/change/password/schemas.ts index ffb4ed8..a6f3adb 100644 --- a/src/routes/(app)/(protected)/settings/security/change/password/schemas.ts +++ b/src/routes/(app)/(protected)/settings/security/change/password/schemas.ts @@ -13,8 +13,8 @@ export const changeUserPasswordSchema = z export type ChangeUserPasswordSchema = typeof changeUserPasswordSchema const refinePasswords = async (confirm_password: string, password: string, ctx: z.RefinementCtx) => { - comparePasswords(confirm_password, password, ctx) - checkPasswordStrength(password, ctx) + await comparePasswords(confirm_password, password, ctx) + await checkPasswordStrength(password, ctx) } const comparePasswords = async (confirm_password: string, password: string, ctx: z.RefinementCtx) => { diff --git a/src/routes/(app)/(protected)/settings/security/mfa/+page.server.ts b/src/routes/(app)/(protected)/settings/security/mfa/+page.server.ts index e87d84b..d16dcd4 100644 --- a/src/routes/(app)/(protected)/settings/security/mfa/+page.server.ts +++ b/src/routes/(app)/(protected)/settings/security/mfa/+page.server.ts @@ -1,14 +1,6 @@ import { notSignedInMessage } from '$lib/flashMessages' -import env from '$lib/server/api/common/env' -import { addTwoFactorSchema, removeTwoFactorSchema } from '$lib/validations/account' -import { type Actions, fail } from '@sveltejs/kit' -import kebabCase from 'just-kebab-case' -import { base32, decodeHex } from 'oslo/encoding' -import { createTOTPKeyURI } from 'oslo/otp' -import QRCode from 'qrcode' +import type { Actions } from '@sveltejs/kit' import { redirect } from 'sveltekit-flash-message/server' -import { zod } from 'sveltekit-superforms/adapters' -import { setError, superValidate } from 'sveltekit-superforms/server' import type { PageServerLoad } from '../../$types' export const load: PageServerLoad = async (event) => { @@ -19,7 +11,14 @@ export const load: PageServerLoad = async (event) => { throw redirect(302, '/login', notSignedInMessage, event) } - return {} + const { data: totpData, error: totpDataError } = await locals.api.mfa.totp.$get().then(locals.parseApiResponse) + + const totpEnabled = !!totpData + + return { + totpEnabled, + hardwareTokenEnabled: false, + } } export const actions: Actions = {} diff --git a/src/routes/(app)/(protected)/settings/security/mfa/+page.svelte b/src/routes/(app)/(protected)/settings/security/mfa/+page.svelte index 0ed96a6..94b3304 100644 --- a/src/routes/(app)/(protected)/settings/security/mfa/+page.svelte +++ b/src/routes/(app)/(protected)/settings/security/mfa/+page.svelte @@ -5,8 +5,8 @@ import * as Card from '$lib/components/ui/card' const { data } = $props() -const totpEnabled = true -const hardwareTokenEnabled = true +const totpEnabled = data.totpEnabled +const hardwareTokenEnabled = data.hardwareTokenEnabled

Two-factor authentication

@@ -19,7 +19,7 @@ const hardwareTokenEnabled = true
-

Authenticator app {#if hardwareTokenEnabled}Configured{/if}

+

Authenticator app {#if totpEnabled}Configured{/if}

Use an authenticator app or browser extension to get two-factor authentication codes when prompted.

diff --git a/src/routes/(app)/+layout.server.ts b/src/routes/(app)/+layout.server.ts index aa43781..493b195 100644 --- a/src/routes/(app)/+layout.server.ts +++ b/src/routes/(app)/+layout.server.ts @@ -1,24 +1,12 @@ -import { loadFlash } from 'sveltekit-flash-message/server'; -import type { LayoutServerLoad } from '../$types'; -// import { userFullyAuthenticated, userNotFullyAuthenticated } from '$lib/server/auth-utils'; -// import { lucia } from '$lib/server/auth'; +import { loadFlash } from 'sveltekit-flash-message/server' +import type { LayoutServerLoad } from '../$types' export const load: LayoutServerLoad = loadFlash(async (event) => { - const { url, locals, cookies } = event; - const authedUser = await locals.getAuthedUser(); - - // if (userNotFullyAuthenticated(user, session)) { - // await lucia.invalidateSession(locals.session!.id!); - // const sessionCookie = lucia.createBlankSessionCookie(); - // cookies.set(sessionCookie.name, sessionCookie.value, { - // path: '.', - // ...sessionCookie.attributes, - // }); - // } + const { url, locals } = event + const authedUser = await locals.getAuthedUser() return { url: url.pathname, - // user: userFullyAuthenticated(user, session) ? locals.user : null, - user: authedUser, - }; -}); + authedUser, + } +}) diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 49d18d5..aaab874 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,37 +1,39 @@ -
+
+
-
- {@render children()} -
+
+ {@render children()} +
-
+
+
\ No newline at end of file +{@render children()} + \ No newline at end of file From bdbfe7dcf5d743711a6a666d628654218181d56c Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 10 Sep 2024 17:40:26 -0700 Subject: [PATCH 02/14] Updating password change toggle to use derived and fixing server code to correctly check errors on verify password. --- .../security/change/password/+page.server.ts | 22 +++----- .../security/change/password/+page.svelte | 55 +++++++++++++++---- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/routes/(app)/(protected)/settings/security/change/password/+page.server.ts b/src/routes/(app)/(protected)/settings/security/change/password/+page.server.ts index 1c4e775..300b45d 100644 --- a/src/routes/(app)/(protected)/settings/security/change/password/+page.server.ts +++ b/src/routes/(app)/(protected)/settings/security/change/password/+page.server.ts @@ -48,24 +48,26 @@ export const actions: Actions = { }) } - const currentPasswordVerified = await locals.api.me.verify.password + const { error: verifyPasswordError } = await locals.api.me.verify.password .$post({ json: { password: form.data.current_password }, }) .then(locals.parseApiResponse) - if (!currentPasswordVerified) { + console.log('verifyPasswordError', verifyPasswordError) + + if (verifyPasswordError) { + console.error(verifyPasswordError) return setError(form, 'current_password', 'Your password is incorrect') } if (authedUser?.username) { - let sessionCookie: Cookie try { if (form.data.password !== form.data.confirm_password) { return setError(form, 'Password and confirm password do not match') } - await locals.api.me.change.password.$put({ + await locals.api.me.update.password.$put({ json: { password: form.data.password, confirm_password: form.data.confirm_password }, - }).then(locals.parseApiResponse) + }) } catch (e) { console.error(e) form.data.password = '' @@ -73,11 +75,6 @@ export const actions: Actions = { form.data.current_password = '' return setError(form, 'current_password', 'Your password is incorrect.') } - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: '.', - ...sessionCookie.attributes, - }) - const message = { type: 'success', message: 'Password Updated. Please sign in.', @@ -85,10 +82,5 @@ export const actions: Actions = { redirect(302, '/login', message, event) } return setError(form, 'Error occurred. Please try again or contact support if you need further help.') - // TODO: Add toast instead? - // form.data.password = ''; - // form.data.confirm_password = ''; - // form.data.current_password = ''; - // return message(form, 'Profile updated successfully.'); }, } diff --git a/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte b/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte index 1a46b42..fbc8215 100644 --- a/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte +++ b/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte @@ -20,6 +20,11 @@ const form = superForm(data.form, { let hiddenCurrentPassword = $state(true) let hiddenPassword = $state(true) let hiddenConfirmPassword = $state(true) +let currentPasswordInput = $derived(hiddenCurrentPassword ? 'password' : 'text') +let passwordInput = $derived(hiddenPassword ? 'password' : 'text') +let confirmPasswordInput = $derived(hiddenConfirmPassword ? 'password' : 'text') + +// $inspect(hiddenCurrentPassword, hiddenPassword, hiddenConfirmPassword) const { form: formData, enhance } = form @@ -27,19 +32,29 @@ const { form: formData, enhance } = form

Change Password


+ + Heads up! - - Changing your password will log you out of all devices. - + Changing your password will log you out of all devices. Current Password - - hiddenCurrentPassword = !hiddenCurrentPassword}>{#if hiddenCurrentPassword}{:else}{/if} + + (hiddenCurrentPassword = !hiddenCurrentPassword)} + > + {#if hiddenCurrentPassword}{:else}{/if} + @@ -48,8 +63,18 @@ const { form: formData, enhance } = form New Password - - hiddenPassword = !hiddenPassword}>{#if hiddenPassword}{:else}{/if} + + (hiddenPassword = !hiddenPassword)} + > + {#if hiddenPassword}{:else}{/if} + @@ -58,8 +83,18 @@ const { form: formData, enhance } = form Confirm New Password - - hiddenConfirmPassword = !hiddenConfirmPassword}>{#if hiddenConfirmPassword}{:else}{/if} + + (hiddenConfirmPassword = !hiddenConfirmPassword)} + > + {#if hiddenConfirmPassword}{:else}{/if} + @@ -71,4 +106,4 @@ const { form: formData, enhance } = form form { max-width: 20rem; } - \ No newline at end of file + From 68182da4cea8a874480982f1699494648cf13c3f Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Fri, 13 Sep 2024 12:11:41 -0700 Subject: [PATCH 03/14] Fixing iam service tests and formatting other code. --- package.json | 18 +- pnpm-lock.yaml | 334 +++++++++--------- .../repositories/credentials.repository.ts | 1 + .../server/api/tests/hashing.service.test.ts | 40 ++- src/lib/server/api/tests/iam.service.test.ts | 105 ++++++ .../server/api/tests/tokens.service.test.ts | 56 ++- .../server/api/tests/users.service.test.ts | 215 +++++++---- .../collections/[cuid]/+page.server.ts | 5 +- .../collections/[cuid]/+page.svelte | 4 +- .../security/change/password/+page.svelte | 6 +- .../security/mfa/totp/+page.server.ts | 4 +- .../settings/security/mfa/totp/+page.svelte | 4 +- .../settings/security/mfa/totp/schemas.ts | 4 +- .../(app)/(protected)/wishlists/+page.svelte | 32 +- .../(protected)/wishlists/[cuid]/+page.svelte | 39 +- src/routes/(app)/+page.svelte | 4 +- vite.config.ts | 3 +- 17 files changed, 540 insertions(+), 334 deletions(-) create mode 100644 src/lib/server/api/tests/iam.service.test.ts diff --git a/package.json b/package.json index bddfaf3..e04cff2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@sveltejs/vite-plugin-svelte": "^3.1.2", "@types/cookie": "^0.6.0", "@types/node": "^20.16.5", - "@types/pg": "^8.11.8", + "@types/pg": "^8.11.9", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "autoprefixer": "^10.4.20", @@ -65,18 +65,18 @@ "sveltekit-flash-message": "^2.4.4", "sveltekit-rate-limiter": "^0.5.2", "sveltekit-superforms": "^2.17.0", - "tailwindcss": "^3.4.10", + "tailwindcss": "^3.4.11", "ts-node": "^10.9.2", "tslib": "^2.7.0", - "tsx": "^4.19.0", - "typescript": "^5.5.4", - "vite": "^5.4.3", + "tsx": "^4.19.1", + "typescript": "^5.6.2", + "vite": "^5.4.4", "vitest": "^1.6.0", "zod": "^3.23.8" }, "type": "module", "dependencies": { - "@fontsource/fira-mono": "^5.0.15", + "@fontsource/fira-mono": "^5.1.0", "@hono/swagger-ui": "^0.4.1", "@hono/zod-openapi": "^0.15.3", "@hono/zod-validator": "^0.2.2", @@ -95,7 +95,7 @@ "arctic": "^1.9.2", "bits-ui": "^0.21.13", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.12.14", + "bullmq": "^5.13.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", @@ -106,7 +106,7 @@ "feather-icons": "^4.29.2", "formsnap": "^1.0.1", "handlebars": "^4.7.8", - "hono": "^4.5.11", + "hono": "^4.6.1", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", @@ -128,6 +128,6 @@ "tailwind-variants": "^0.2.1", "tailwindcss-animate": "^1.0.7", "tsyringe": "^4.8.0", - "zod-to-json-schema": "^3.23.2" + "zod-to-json-schema": "^3.23.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94e8b55..1bcf7f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,17 +9,17 @@ importers: .: dependencies: '@fontsource/fira-mono': - specifier: ^5.0.15 - version: 5.0.15 + specifier: ^5.1.0 + version: 5.1.0 '@hono/swagger-ui': specifier: ^0.4.1 - version: 0.4.1(hono@4.5.11) + version: 0.4.1(hono@4.6.1) '@hono/zod-openapi': specifier: ^0.15.3 - version: 0.15.3(hono@4.5.11)(zod@3.23.8) + version: 0.15.3(hono@4.6.1)(zod@3.23.8) '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.5.11)(zod@3.23.8) + version: 0.2.2(hono@4.6.1)(zod@3.23.8) '@iconify-icons/line-md': specifier: ^1.2.30 version: 1.2.30 @@ -31,7 +31,7 @@ importers: version: 3.5.5 '@lucia-auth/adapter-drizzle': specifier: ^1.1.0 - version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) + version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) '@lukeed/uuid': specifier: ^2.0.1 version: 2.0.1 @@ -46,10 +46,10 @@ importers: version: 2.6.2 '@sveltejs/adapter-node': specifier: ^5.2.2 - version: 5.2.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))) + version: 5.2.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))) '@sveltejs/adapter-vercel': specifier: ^5.4.3 - version: 5.4.3(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))) + version: 5.4.3(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -66,8 +66,8 @@ importers: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.12.14 - version: 5.12.14 + specifier: ^5.13.0 + version: 5.13.0 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -85,25 +85,25 @@ importers: version: 11.0.6 drizzle-orm: specifier: ^0.32.2 - version: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) + version: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4) drizzle-zod: specifier: ^0.5.1 - version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) + version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) feather-icons: specifier: ^4.29.2 version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 hono: - specifier: ^4.5.11 - version: 4.5.11 + specifier: ^4.6.1 + version: 4.6.1 hono-rate-limiter: specifier: ^0.4.0 - version: 0.4.0(hono@4.5.11) + version: 0.4.0(hono@4.6.1) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -157,16 +157,16 @@ importers: version: 2.5.2 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4))) + version: 0.2.1(tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4))) + version: 1.0.7(tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))) tsyringe: specifier: ^4.8.0 version: 4.8.0 zod-to-json-schema: - specifier: ^3.23.2 - version: 3.23.2(zod@3.23.8) + specifier: ^3.23.3 + version: 3.23.3(zod@3.23.8) devDependencies: '@biomejs/biome': specifier: 1.8.3 @@ -185,16 +185,16 @@ importers: version: 1.47.0 '@sveltejs/adapter-auto': specifier: ^3.2.4 - version: 3.2.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))) + version: 3.2.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))) '@sveltejs/enhanced-img': specifier: ^0.3.4 - version: 0.3.4(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + version: 0.3.4(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) '@sveltejs/kit': specifier: ^2.5.26 - version: 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + version: 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 @@ -202,14 +202,14 @@ importers: specifier: ^20.16.5 version: 20.16.5 '@types/pg': - specifier: ^8.11.8 - version: 8.11.8 + specifier: ^8.11.9 + version: 8.11.9 '@typescript-eslint/eslint-plugin': specifier: ^7.18.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': specifier: ^7.18.0 - version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) + version: 7.18.0(eslint@8.57.0)(typescript@5.6.2) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.45) @@ -224,7 +224,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-plugin-svelte: specifier: 2.36.0-next.13 - version: 2.36.0-next.13(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + version: 2.36.0-next.13(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -248,7 +248,7 @@ importers: version: 16.1.0(postcss@8.4.45) postcss-load-config: specifier: ^5.1.0 - version: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0) + version: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1) postcss-preset-env: specifier: ^9.6.0 version: 9.6.0(postcss@8.4.45) @@ -272,46 +272,46 @@ importers: version: 5.0.0-next.175 svelte-check: specifier: ^3.8.6 - version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175) + version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) svelte-meta-tags: specifier: ^3.1.4 - version: 3.1.4(svelte@5.0.0-next.175)(typescript@5.5.4) + version: 3.1.4(svelte@5.0.0-next.175)(typescript@5.6.2) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.5.4) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))) + version: 0.5.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))) sveltekit-superforms: specifier: ^2.17.0 - version: 2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) + version: 2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) tailwindcss: - specifier: ^3.4.10 - version: 3.4.10(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + specifier: ^3.4.11 + version: 3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.16.5)(typescript@5.5.4) + version: 10.9.2(@types/node@20.16.5)(typescript@5.6.2) tslib: specifier: ^2.7.0 version: 2.7.0 tsx: - specifier: ^4.19.0 - version: 4.19.0 + specifier: ^4.19.1 + version: 4.19.1 typescript: - specifier: ^5.5.4 - version: 5.5.4 + specifier: ^5.6.2 + version: 5.6.2 vite: - specifier: ^5.4.3 - version: 5.4.3(@types/node@20.16.5)(sass@1.78.0) + specifier: ^5.4.4 + version: 5.4.4(@types/node@20.16.5)(sass@1.78.0) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.16.5)(sass@1.78.0) @@ -1245,8 +1245,8 @@ packages: '@floating-ui/utils@0.2.7': resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==} - '@fontsource/fira-mono@5.0.15': - resolution: {integrity: sha512-wc3TpF2GBbtFDKNbb444BrC3mEKuoPLITSYCKweNIrqBvAalIfJGloY/MVrmSGaMNgaAKUpdgy4eAWPLkUVzaA==} + '@fontsource/fira-mono@5.1.0': + resolution: {integrity: sha512-6+nftSKApXyN0I9FC5GJuG5TUCh+in5OehtrXRIsHJvq38Pm//oA1kZZYNdXv99JYzLzJ3lzsTAavmS+xGLGDw==} '@gcornut/valibot-json-schema@0.31.0': resolution: {integrity: sha512-3xGptCurm23e7nuPQkdrE5rEs1FeTPHhAUsBuwwqG4/YeZLwJOoYZv+fmsppUEfo5y9lzUwNQrNqLS/q7HMc7g==} @@ -2022,8 +2022,8 @@ packages: '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} - '@types/pg@8.11.8': - resolution: {integrity: sha512-IqpCf8/569txXN/HoP5i1LjXfKZWL76Yr2R77xgeIICUbAYHeoaEZFhYHo2uDftecLWrTJUq63JvQu8q3lnDyA==} + '@types/pg@8.11.9': + resolution: {integrity: sha512-M4mYeJZRBD9lCBCGa72F44uKSV9eJrAFfjlPJagdA6pgIr2OPJULFB7nqnZzOdqXG0qzHlgtZKzTdIgbmHitSg==} '@types/pug@2.0.10': resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} @@ -2295,8 +2295,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bullmq@5.12.14: - resolution: {integrity: sha512-mcSQHq9EY+DKtAP6XSmkP+0f1ifFithcpLTwo8WmUauArE9dxk45Gae3Fls1Nwf0Er9MoaDhPcglfe6LV/XCOg==} + bullmq@5.13.0: + resolution: {integrity: sha512-rE7v3jMZZGsEhfMhLZwADwuHdqJPTTGHBM8C+SpxF9GzyZ+7pvC80EP5bOZJPPRzbmyhvIPJCVd0bchUZiQF+w==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -3043,8 +3043,8 @@ packages: peerDependencies: hono: ^4.1.1 - hono@4.5.11: - resolution: {integrity: sha512-62FcjLPtjAFwISVBUshryl+vbHOjg8rE4uIK/dxyR8GpLztunZpwFmfEvmJCUI7xoGh/Sr3CGCDPCmYxVw7wUQ==} + hono@4.6.1: + resolution: {integrity: sha512-6NGwvttY1+HAFii08VYiEKI6ETPAFbpLntpm2M/MogEsAFWdZV74UNT+2M4bmqX90cIQhjlpBSP+tO+CfB0uww==} engines: {node: '>=16.0.0'} html-entities@2.5.2: @@ -4476,8 +4476,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.10: - resolution: {integrity: sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==} + tailwindcss@3.4.11: + resolution: {integrity: sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==} engines: {node: '>=14.0.0'} hasBin: true @@ -4575,8 +4575,8 @@ packages: tslib@2.7.0: resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - tsx@4.19.0: - resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==} + tsx@4.19.1: + resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} engines: {node: '>=18.0.0'} hasBin: true @@ -4604,8 +4604,8 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} engines: {node: '>=14.17'} hasBin: true @@ -4679,8 +4679,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.3: - resolution: {integrity: sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==} + vite@5.4.4: + resolution: {integrity: sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4835,8 +4835,8 @@ packages: zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} - zod-to-json-schema@3.23.2: - resolution: {integrity: sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==} + zod-to-json-schema@3.23.3: + resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==} peerDependencies: zod: ^3.23.3 @@ -5489,7 +5489,7 @@ snapshots: '@floating-ui/utils@0.2.7': {} - '@fontsource/fira-mono@5.0.15': {} + '@fontsource/fira-mono@5.1.0': {} '@gcornut/valibot-json-schema@0.31.0': dependencies: @@ -5508,20 +5508,20 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/swagger-ui@0.4.1(hono@4.5.11)': + '@hono/swagger-ui@0.4.1(hono@4.6.1)': dependencies: - hono: 4.5.11 + hono: 4.6.1 - '@hono/zod-openapi@0.15.3(hono@4.5.11)(zod@3.23.8)': + '@hono/zod-openapi@0.15.3(hono@4.6.1)(zod@3.23.8)': dependencies: '@asteasolutions/zod-to-openapi': 7.1.1(zod@3.23.8) - '@hono/zod-validator': 0.2.2(hono@4.5.11)(zod@3.23.8) - hono: 4.5.11 + '@hono/zod-validator': 0.2.2(hono@4.6.1)(zod@3.23.8) + hono: 4.6.1 zod: 3.23.8 - '@hono/zod-validator@0.2.2(hono@4.5.11)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.6.1)(zod@3.23.8)': dependencies: - hono: 4.5.11 + hono: 4.6.1 zod: 3.23.8 '@humanwhocodes/config-array@0.11.14': @@ -5664,9 +5664,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': + '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4) lucia: 3.2.0 '@lukeed/csprng@1.1.0': {} @@ -6063,41 +6063,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))': dependencies: - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.21.2) '@rollup/plugin-json': 6.1.0(rollup@4.21.2) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.21.2) - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) rollup: 4.21.2 - '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))': dependencies: - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) '@vercel/nft': 0.27.4 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.4(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/enhanced-img@0.3.4(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))': dependencies: magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.4.3(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) vite-imagetools: 7.0.4(rollup@4.21.2) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6111,28 +6111,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.4.3(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) debug: 4.3.6 svelte: 5.0.0-next.175 - vite: 5.4.3(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) debug: 4.3.6 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.4.3(@types/node@20.16.5)(sass@1.78.0) - vitefu: 0.2.5(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) + vitefu: 0.2.5(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) transitivePeerDependencies: - supports-color @@ -6172,7 +6172,7 @@ snapshots: pg-protocol: 1.6.1 pg-types: 4.0.2 - '@types/pg@8.11.8': + '@types/pg@8.11.9': dependencies: '@types/node': 20.16.5 pg-protocol: 1.6.1 @@ -6185,34 +6185,34 @@ snapshots: '@types/validator@13.12.1': optional: true - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/visitor-keys': 7.18.0 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.3.6 eslint: 8.57.0 optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color @@ -6221,21 +6221,21 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.0)(typescript@5.6.2)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.6.2) debug: 4.3.6 eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.2)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 @@ -6244,18 +6244,18 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.0)(typescript@5.6.2)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) eslint: 8.57.0 transitivePeerDependencies: - supports-color @@ -6508,7 +6508,7 @@ snapshots: builtin-modules@3.3.0: {} - bullmq@5.12.14: + bullmq@5.13.0: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -6769,16 +6769,16 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4): + drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4): optionalDependencies: '@neondatabase/serverless': 0.9.5 - '@types/pg': 8.11.8 + '@types/pg': 8.11.9 pg: 8.12.0 postgres: 3.4.4 - drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): + drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.8)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4) zod: 3.23.8 eastasianwidth@0.2.0: {} @@ -6936,7 +6936,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-svelte@2.36.0-next.13(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)): + eslint-plugin-svelte@2.36.0-next.13(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@jridgewell/sourcemap-codec': 1.5.0 @@ -6946,7 +6946,7 @@ snapshots: esutils: 2.0.3 known-css-properties: 0.30.0 postcss: 8.4.45 - postcss-load-config: 3.1.4(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + postcss-load-config: 3.1.4(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) postcss-safe-parser: 6.0.0(postcss@8.4.45) postcss-selector-parser: 6.1.2 semver: 7.6.3 @@ -7170,11 +7170,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7307,11 +7307,11 @@ snapshots: hex-rgb@4.3.0: {} - hono-rate-limiter@0.4.0(hono@4.5.11): + hono-rate-limiter@0.4.0(hono@4.6.1): dependencies: - hono: 4.5.11 + hono: 4.6.1 - hono@4.5.11: {} + hono@4.6.1: {} html-entities@2.5.2: {} @@ -8014,30 +8014,30 @@ snapshots: '@csstools/utilities': 1.0.0(postcss@8.4.45) postcss: 8.4.45 - postcss-load-config@3.1.4(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)): + postcss-load-config@3.1.4(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: postcss: 8.4.45 - ts-node: 10.9.2(@types/node@20.16.5)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.16.5)(typescript@5.6.2) - postcss-load-config@4.0.2(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): dependencies: lilconfig: 3.1.2 yaml: 2.5.1 optionalDependencies: postcss: 8.4.45 - ts-node: 10.9.2(@types/node@20.16.5)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@20.16.5)(typescript@5.6.2) - postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0): + postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1): dependencies: lilconfig: 3.1.2 yaml: 2.5.1 optionalDependencies: jiti: 1.21.6 postcss: 8.4.45 - tsx: 4.19.0 + tsx: 4.19.1 postcss-logical@7.0.1(postcss@8.4.45): dependencies: @@ -8386,9 +8386,9 @@ snapshots: postcss-value-parser: 4.2.0 yoga-wasm-web: 0.3.3 - schema-dts@1.1.2(typescript@5.5.4): + schema-dts@1.1.2(typescript@5.6.2): dependencies: - typescript: 5.5.4 + typescript: 5.6.2 semver@6.3.1: {} @@ -8578,15 +8578,15 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175): + svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.1.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.5.4) - typescript: 5.5.4 + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2) + typescript: 5.6.2 transitivePeerDependencies: - '@babel/core' - coffeescript @@ -8630,9 +8630,9 @@ snapshots: svelte-lazy-loader@1.0.0: {} - svelte-meta-tags@3.1.4(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-meta-tags@3.1.4(svelte@5.0.0-next.175)(typescript@5.6.2): dependencies: - schema-dts: 1.1.2(typescript@5.5.4) + schema-dts: 1.1.2(typescript@5.6.2) svelte: 5.0.0-next.175 transitivePeerDependencies: - typescript @@ -8641,7 +8641,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8651,18 +8651,18 @@ snapshots: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.45 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0) + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1) sass: 1.78.0 - typescript: 5.5.4 + typescript: 5.6.2 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.5.4): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2): dependencies: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.45 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.0) + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1) sass: 1.78.0 - typescript: 5.5.4 + typescript: 5.6.2 svelte-render@2.0.1(svelte@5.0.0-next.175): dependencies: @@ -8715,19 +8715,19 @@ snapshots: magic-string: 0.30.11 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) - sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -8746,22 +8746,22 @@ snapshots: valibot: 0.35.0 yup: 1.4.0 zod: 3.23.8 - zod-to-json-schema: 3.23.2(zod@3.23.8) + zod-to-json-schema: 3.23.3(zod@3.23.8) tabbable@6.2.0: {} tailwind-merge@2.5.2: {} - tailwind-variants@0.2.1(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4))): + tailwind-variants@0.2.1(tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))): dependencies: tailwind-merge: 2.5.2 - tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + tailwindcss: 3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))): dependencies: - tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + tailwindcss: 3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) - tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)): + tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -8780,7 +8780,7 @@ snapshots: postcss: 8.4.45 postcss-import: 15.1.0(postcss@8.4.45) postcss-js: 4.0.1(postcss@8.4.45) - postcss-load-config: 4.0.2(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4)) + postcss-load-config: 4.0.2(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) postcss-nested: 6.2.0(postcss@8.4.45) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -8839,15 +8839,15 @@ snapshots: ts-algebra@2.0.0: optional: true - ts-api-utils@1.3.0(typescript@5.5.4): + ts-api-utils@1.3.0(typescript@5.6.2): dependencies: - typescript: 5.5.4 + typescript: 5.6.2 ts-deepmerge@7.0.1: {} ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.16.5)(typescript@5.5.4): + ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -8861,7 +8861,7 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.4 + typescript: 5.6.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -8874,7 +8874,7 @@ snapshots: tslib@2.7.0: {} - tsx@4.19.0: + tsx@4.19.1: dependencies: esbuild: 0.23.1 get-tsconfig: 4.8.0 @@ -8901,7 +8901,7 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - typescript@5.5.4: {} + typescript@5.6.2: {} ufo@1.5.4: {} @@ -8964,7 +8964,7 @@ snapshots: debug: 4.3.6 pathe: 1.1.2 picocolors: 1.1.0 - vite: 5.4.3(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) transitivePeerDependencies: - '@types/node' - less @@ -8976,7 +8976,7 @@ snapshots: - supports-color - terser - vite@5.4.3(@types/node@20.16.5)(sass@1.78.0): + vite@5.4.4(@types/node@20.16.5)(sass@1.78.0): dependencies: esbuild: 0.21.5 postcss: 8.4.45 @@ -8986,9 +8986,9 @@ snapshots: fsevents: 2.3.3 sass: 1.78.0 - vitefu@0.2.5(vite@5.4.3(@types/node@20.16.5)(sass@1.78.0)): + vitefu@0.2.5(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)): optionalDependencies: - vite: 5.4.3(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) vitest@1.6.0(@types/node@20.16.5)(sass@1.78.0): dependencies: @@ -9009,7 +9009,7 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.3(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) vite-node: 1.6.0(@types/node@20.16.5)(sass@1.78.0) why-is-node-running: 2.3.0 optionalDependencies: @@ -9117,7 +9117,7 @@ snapshots: zimmerframe@1.1.2: {} - zod-to-json-schema@3.23.2(zod@3.23.8): + zod-to-json-schema@3.23.3(zod@3.23.8): dependencies: zod: 3.23.8 diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 11f56c5..0671a40 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -7,6 +7,7 @@ import { takeFirstOrThrow } from '../common/utils/repository' export type CreateCredentials = InferInsertModel export type UpdateCredentials = Partial +export type DeleteCredentials = Pick @injectable() export class CredentialsRepository { diff --git a/src/lib/server/api/tests/hashing.service.test.ts b/src/lib/server/api/tests/hashing.service.test.ts index 94c7f89..d7fe080 100644 --- a/src/lib/server/api/tests/hashing.service.test.ts +++ b/src/lib/server/api/tests/hashing.service.test.ts @@ -1,32 +1,38 @@ -import 'reflect-metadata'; -import { container } from 'tsyringe'; -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -import { HashingService } from '../services/hashing.service'; +import 'reflect-metadata' +import { container } from 'tsyringe' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { HashingService } from '../services/hashing.service' describe('HashingService', () => { - let service: HashingService; + let service: HashingService beforeAll(() => { - service = container.resolve(HashingService); - }); + service = container.resolve(HashingService) + }) afterAll(() => { vi.resetAllMocks() - }); + }) describe('Create Hash', () => { it('should create a hash', async () => { - const hash = await service.hash('111'); - expect(hash).not.toBeUndefined(); - expect(hash).not.toBeNull(); - }); + const hash = await service.hash('111') + expect(hash).not.toBeUndefined() + expect(hash).not.toBeNull() + }) }) describe('Verify Hash', () => { it('should verify a hash', async () => { - const hash = await service.hash('111'); - const verifiable = await service.verify(hash, '111'); - expect(verifiable).toBeTruthy(); - }); + const hash = await service.hash('111') + const verifiable = await service.verify(hash, '111') + expect(verifiable).toBeTruthy() + }) + + it('should not verify a hash', async () => { + const hash = await service.hash('111') + const verifiable = await service.verify(hash, '222') + expect(verifiable).toBeFalsy() + }) }) -}) \ No newline at end of file +}) diff --git a/src/lib/server/api/tests/iam.service.test.ts b/src/lib/server/api/tests/iam.service.test.ts new file mode 100644 index 0000000..96d3174 --- /dev/null +++ b/src/lib/server/api/tests/iam.service.test.ts @@ -0,0 +1,105 @@ +import 'reflect-metadata' +import { IamService } from '$lib/server/api/services/iam.service' +import { LuciaService } from '$lib/server/api/services/lucia.service' +import { UsersService } from '$lib/server/api/services/users.service' +import { container } from 'tsyringe' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' + +describe('IamService', () => { + let service: IamService + const luciaService = vi.mocked(LuciaService.prototype) + const userService = vi.mocked(UsersService.prototype) + + beforeAll(() => { + service = container + .register(LuciaService, { useValue: luciaService }) + .register(UsersService, { useValue: userService }) + .resolve(IamService) + }) + + beforeEach(() => { + vi.resetAllMocks() + }) + + afterAll(() => { + vi.resetAllMocks() + }) + + describe('Update Profile', () => { + it('should resolve', async () => { + const timeStampDate = new Date() + userService.findOneById = vi.fn().mockResolvedValueOnce({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + first_name: 'test', + last_name: 'test', + email: 'test@example.com', + username: 'test', + verified: false, + receive_email: false, + mfa_enabled: false, + theme: 'system', + createdAt: timeStampDate, + updatedAt: timeStampDate, + }) + userService.findOneByUsername = vi.fn().mockResolvedValue(undefined) + userService.updateUser = vi.fn().mockResolvedValue({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + first_name: 'test', + last_name: 'test', + email: 'test@example.com', + username: 'test', + verified: false, + receive_email: false, + mfa_enabled: false, + theme: 'system', + createdAt: timeStampDate, + updatedAt: timeStampDate, + }) + + const spy_userService_findOneById = vi.spyOn(userService, 'findOneById') + const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername') + const spy_userService_updateUser = vi.spyOn(userService, 'updateUser') + await expect( + service.updateProfile('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', { + username: 'test', + }), + ).resolves.toEqual({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + first_name: 'test', + last_name: 'test', + email: 'test@example.com', + username: 'test', + verified: false, + receive_email: false, + mfa_enabled: false, + theme: 'system', + createdAt: timeStampDate, + updatedAt: timeStampDate, + }) + expect(spy_userService_findOneById).toBeCalledTimes(1) + expect(spy_userService_findOneByUsername).toBeCalledTimes(1) + expect(spy_userService_updateUser).toBeCalledTimes(1) + }) + + it('should error on no user found', async () => { + userService.findOneById = vi.fn().mockResolvedValueOnce(undefined) + + const spy_userService_findOneById = vi.spyOn(userService, 'findOneById') + const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername') + const spy_userService_updateUser = vi.spyOn(userService, 'updateUser') + await expect( + service.updateProfile('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', { + username: 'test', + }), + ).resolves.toEqual({ + error: 'User not found', + }) + expect(spy_userService_findOneById).toBeCalledTimes(1) + expect(spy_userService_findOneByUsername).toBeCalledTimes(0) + expect(spy_userService_updateUser).toBeCalledTimes(0) + }) + }) +}) diff --git a/src/lib/server/api/tests/tokens.service.test.ts b/src/lib/server/api/tests/tokens.service.test.ts index 5c0e34e..ad0aa3b 100644 --- a/src/lib/server/api/tests/tokens.service.test.ts +++ b/src/lib/server/api/tests/tokens.service.test.ts @@ -1,49 +1,47 @@ -import 'reflect-metadata'; -import { container } from 'tsyringe'; -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -import { TokensService } from '../services/tokens.service'; -import { HashingService } from '../services/hashing.service'; -import { Argon2id } from 'oslo/password'; +import 'reflect-metadata' +import { Argon2id } from 'oslo/password' +import { container } from 'tsyringe' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { HashingService } from '../services/hashing.service' +import { TokensService } from '../services/tokens.service' describe('TokensService', () => { - let service: TokensService; - const hashingService = vi.mocked(HashingService.prototype); + let service: TokensService + const hashingService = vi.mocked(HashingService.prototype) beforeAll(() => { - service = container - .register(HashingService, { useValue: hashingService }) - .resolve(TokensService); - }); + service = container.register(HashingService, { useValue: hashingService }).resolve(TokensService) + }) afterAll(() => { vi.resetAllMocks() - }); + }) describe('Generate Token', () => { - const hashedPassword = new Argon2id().hash('111'); + const hashedPassword = new Argon2id().hash('111') - hashingService.hash = vi.fn().mockResolvedValue(hashedPassword); - hashingService.verify = vi.fn().mockResolvedValue(true); + hashingService.hash = vi.fn().mockResolvedValue(hashedPassword) + hashingService.verify = vi.fn().mockResolvedValue(true) - const spy_hashingService_hash = vi.spyOn(hashingService, 'hash'); - const spy_hashingService_verify = vi.spyOn(hashingService, 'verify'); + const spy_hashingService_hash = vi.spyOn(hashingService, 'hash') + const spy_hashingService_verify = vi.spyOn(hashingService, 'verify') it('should resolve', async () => { - await expect(service.createHashedToken('111')).resolves.string + expect(service.createHashedToken('111')).resolves.string }) it('should generate a token that is verifiable', async () => { - const token = await service.createHashedToken('111'); - expect(token).not.toBeUndefined(); - expect(token).not.toBeNull(); - const verifiable = await service.verifyHashedToken(token, '111'); - expect(verifiable).toBeTruthy(); - }); + const token = await service.createHashedToken('111') + expect(token).not.toBeUndefined() + expect(token).not.toBeNull() + const verifiable = await service.verifyHashedToken(token, '111') + expect(verifiable).toBeTruthy() + }) it('should generate a hashed token', async () => { - expect(spy_hashingService_hash).toHaveBeenCalledTimes(2); + expect(spy_hashingService_hash).toHaveBeenCalledTimes(2) }) it('should verify a hashed token', async () => { - expect(spy_hashingService_verify).toHaveBeenCalledTimes(1); + expect(spy_hashingService_verify).toHaveBeenCalledTimes(1) }) - }); -}); \ No newline at end of file + }) +}) diff --git a/src/lib/server/api/tests/users.service.test.ts b/src/lib/server/api/tests/users.service.test.ts index fb40e5f..ec551a2 100644 --- a/src/lib/server/api/tests/users.service.test.ts +++ b/src/lib/server/api/tests/users.service.test.ts @@ -1,44 +1,47 @@ -import 'reflect-metadata'; -import { container } from 'tsyringe'; -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -import { UsersService } from '../services/users.service'; -import { CredentialsRepository } from '../repositories/credentials.repository'; -import { TokensService } from '../services/tokens.service'; -import { UserRolesService } from '../services/user_roles.service'; -import { UsersRepository } from '../repositories/users.repository'; -import { Argon2id } from 'oslo/password'; -import { WishlistsService } from '../services/wishlists.service'; -import { CollectionsService } from '../services/collections.service'; +import 'reflect-metadata' +import { CredentialsType } from '$lib/server/api/databases/tables' +import { Argon2id } from 'oslo/password' +import { container } from 'tsyringe' +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { CredentialsRepository } from '../repositories/credentials.repository' +import { UsersRepository } from '../repositories/users.repository' +import { CollectionsService } from '../services/collections.service' +import { TokensService } from '../services/tokens.service' +import { UserRolesService } from '../services/user_roles.service' +import { UsersService } from '../services/users.service' +import { WishlistsService } from '../services/wishlists.service' describe('UsersService', () => { - let service: UsersService; - const credentialsRepository = vi.mocked(CredentialsRepository.prototype); - const tokensService = vi.mocked(TokensService.prototype); - const usersRepository = vi.mocked(UsersRepository.prototype); - const userRolesService = vi.mocked(UserRolesService.prototype); - const wishlistsService = vi.mocked(WishlistsService.prototype); - const collectionsService = vi.mocked(CollectionsService.prototype); + let service: UsersService + const credentialsRepository = vi.mocked(CredentialsRepository.prototype) + const tokensService = vi.mocked(TokensService.prototype) + const usersRepository = vi.mocked(UsersRepository.prototype) + const userRolesService = vi.mocked(UserRolesService.prototype) + const wishlistsService = vi.mocked(WishlistsService.prototype) + const collectionsService = vi.mocked(CollectionsService.prototype) - beforeAll(() => { - service = container - .register(CredentialsRepository, { useValue: credentialsRepository }) + beforeAll(() => { + service = container + .register(CredentialsRepository, { useValue: credentialsRepository }) .register(TokensService, { useValue: tokensService }) .register(UsersRepository, { useValue: usersRepository }) .register(UserRolesService, { useValue: userRolesService }) .register(WishlistsService, { useValue: wishlistsService }) .register(CollectionsService, { useValue: collectionsService }) - .resolve(UsersService); - }); + .resolve(UsersService) + }) - afterAll(() => { - vi.resetAllMocks() - }) + afterAll(() => { + vi.resetAllMocks() + }) describe('Create User', () => { - const hashedPassword = new Argon2id().hash('111'); - tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) + const date = new Date() - usersRepository.create = vi.fn().mockResolvedValue({ + const hashedPassword = new Argon2id().hash('111') + tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) + + usersRepository.create = vi.fn().mockResolvedValue({ id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', cuid: 'ciglo1j8q0000t9j4xq8d6p5e', first_name: 'test', @@ -47,17 +50,18 @@ describe('UsersService', () => { username: 'test', verified: false, receive_email: false, + mfa_enabled: false, theme: 'system', - createdAt: new Date(), - updatedAt: new Date() - } satisfies Awaited>) + createdAt: date, + updatedAt: date, + } satisfies Awaited>) credentialsRepository.create = vi.fn().mockResolvedValue({ id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - type: 'PASSWORD', - secret_data: hashedPassword - }) + type: CredentialsType.PASSWORD, + secret_data: hashedPassword, + }) satisfies Awaited> userRolesService.addRoleToUser = vi.fn().mockResolvedValue(undefined) @@ -65,40 +69,121 @@ describe('UsersService', () => { collectionsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined) - const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken'); - const spy_usersRepository_create = vi.spyOn(usersRepository, 'create'); - const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create'); - const spy_userRolesService_addRoleToUser = vi.spyOn(userRolesService, 'addRoleToUser'); - const spy_wishlistsService_createEmptyNoName = vi.spyOn(wishlistsService, 'createEmptyNoName'); - const spy_collectionsService_createEmptyNoName = vi.spyOn(collectionsService, 'createEmptyNoName'); + const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') + const spy_usersRepository_create = vi.spyOn(usersRepository, 'create') + const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create') + const spy_userRolesService_addRoleToUser = vi.spyOn(userRolesService, 'addRoleToUser') + const spy_wishlistsService_createEmptyNoName = vi.spyOn(wishlistsService, 'createEmptyNoName') + const spy_collectionsService_createEmptyNoName = vi.spyOn(collectionsService, 'createEmptyNoName') - it('should resolve', async () => { - await expect(service.create({ - firstName: 'test', - lastName: 'test', + it('should resolve', async () => { + await expect( + service.create({ + firstName: 'test', + lastName: 'test', + email: 'test@example.com', + username: 'test', + password: '111', + confirm_password: '111', + }), + ).resolves.toEqual({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + first_name: 'test', + last_name: 'test', email: 'test@example.com', username: 'test', - password: '111', - confirm_password: '111' - })).resolves.not.toThrow() - }) - it('should generate a hashed token', async () => { - expect(spy_tokensService_createHashToken).toBeCalledTimes(1) - }) - it('should create a new user', async () => { - expect(spy_usersRepository_create).toHaveBeenCalledTimes(1) - }) - it('should create a new credential', async () => { - expect(spy_credentialsRepository_create).toBeCalledTimes(1) + verified: false, + receive_email: false, + mfa_enabled: false, + theme: 'system', + createdAt: date, + updatedAt: date, + }) }) - it('should add role to user', async () => { - expect(spy_userRolesService_addRoleToUser).toBeCalledTimes(1) + // it('should generate a hashed token', async () => { + // expect(spy_tokensService_createHashToken).toBeCalledTimes(1) + // }) + // it('should create a new user', async () => { + // expect(spy_usersRepository_create).toBeCalledTimes(1) + // }) + // it('should create a new credential', async () => { + // expect(spy_credentialsRepository_create).toBeCalledTimes(1) + // }) + // it('should add role to user', async () => { + // expect(spy_userRolesService_addRoleToUser).toBeCalledTimes(1) + // }) + // it('should create a new wishlist', async () => { + // expect(spy_wishlistsService_createEmptyNoName).toBeCalledTimes(1) + // }) + // it('should create a new collection', async () => { + // expect(spy_collectionsService_createEmptyNoName).toBeCalledTimes(1) + // }) + }) + describe('Update User Password Exiting Credentials', () => { + const hashedPassword = new Argon2id().hash('111') + tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) satisfies Awaited> + credentialsRepository.update = vi.fn().mockResolvedValue({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + type: 'PASSWORD', + secret_data: hashedPassword, + }) satisfies Awaited> + credentialsRepository.findPasswordCredentialsByUserId = vi.fn().mockResolvedValue({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + type: 'PASSWORD', + secret_data: hashedPassword, + }) satisfies Awaited> + + const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') + const spy_credentialsRepository_findPasswordCredentialsByUserId = vi.spyOn(credentialsRepository, 'findPasswordCredentialsByUserId') + const spy_credentialsRepository_update = vi.spyOn(credentialsRepository, 'update') + + it('should resolve', async () => { + await expect(service.updatePassword('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', '111')).resolves.toBeUndefined() }) - it('should create a new wishlist', async () => { - expect(spy_wishlistsService_createEmptyNoName).toBeCalledTimes(1) + console.log(spy_tokensService_createHashToken.mock.calls) + it('should generate a hashed token', async () => { + expect(spy_tokensService_createHashToken).toBeCalledTimes(1) }) - it('should create a new collection', async () => { - expect(spy_collectionsService_createEmptyNoName).toBeCalledTimes(1) + console.log(spy_credentialsRepository_findPasswordCredentialsByUserId.mock.calls) + it('should call find password credentials by user id', async () => { + expect(spy_credentialsRepository_findPasswordCredentialsByUserId).toBeCalledTimes(1) }) - }) -}); + console.log(spy_credentialsRepository_update.mock.calls) + it('should update the credential when user has credential', async () => { + expect(spy_credentialsRepository_update).toBeCalledTimes(1) + }) + }) + describe('Update User Password No Existing Credentials', () => { + const hashedPassword = new Argon2id().hash('111') + tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) satisfies Awaited> + credentialsRepository.findPasswordCredentialsByUserId = vi.fn().mockResolvedValue(null) satisfies Awaited< + ReturnType + > + credentialsRepository.create = vi.fn().mockResolvedValue({ + id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', + type: 'PASSWORD', + secret_data: hashedPassword, + }) satisfies Awaited> + + const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') + const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create') + const spy_credentialsRepository_findPasswordCredentialsByUserId = vi.spyOn(credentialsRepository, 'findPasswordCredentialsByUserId') + + it('should resolve with no current credential for user', async () => { + await expect(service.updatePassword('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', '111')).resolves.not.toThrow() + }) + it('should generate a hashed token', async () => { + expect(spy_tokensService_createHashToken).toBeCalledTimes(1) + }) + it('should call find password credentials by user id', async () => { + expect(spy_credentialsRepository_findPasswordCredentialsByUserId).toBeCalledTimes(1) + }) + it('should create a new credential when user has no credential', async () => { + expect(spy_credentialsRepository_create).toHaveBeenCalledTimes(1) + }) + }) +}) diff --git a/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts b/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts index c394fa6..488fbb1 100644 --- a/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts +++ b/src/routes/(app)/(protected)/collections/[cuid]/+page.server.ts @@ -1,13 +1,12 @@ import { notSignedInMessage } from '$lib/flashMessages.js' +import { collection_items, collections, gamesTable } from '$lib/server/api/databases/tables' import { db } from '$lib/server/api/packages/drizzle' -import { userNotAuthenticated } from '$lib/server/auth-utils' import { modifyListGameSchema } from '$lib/validations/zod-schemas' -import { type Actions, error, fail } from '@sveltejs/kit' +import { type Actions, error } from '@sveltejs/kit' import { and, eq } from 'drizzle-orm' import { redirect } from 'sveltekit-flash-message/server' import { zod } from 'sveltekit-superforms/adapters' import { superValidate } from 'sveltekit-superforms/server' -import { collection_items, collections, gamesTable } from '../../../../../lib/server/api/databases/tables' export async function load(event) { const { params, locals } = event diff --git a/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte b/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte index b840239..450233c 100644 --- a/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte +++ b/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte @@ -3,10 +3,10 @@ import Game from '$components/Game.svelte' import type { UICollection } from '$lib/types' -export let data +const { data } = $props() +const { items = [] } = data console.log(`Page data: ${JSON.stringify(data)}`) let collection: UICollection = data?.collection ?? {} -let items = data?.items || [] console.log('items', items) // async function handleNextPageEvent(event: CustomEvent) { diff --git a/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte b/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte index fbc8215..e2f8c0f 100644 --- a/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte +++ b/src/routes/(app)/(protected)/settings/security/change/password/+page.svelte @@ -53,7 +53,7 @@ const { form: formData, enhance } = form aria-label={`${hiddenCurrentPassword ? 'Show' : 'Hide'} Current Password}`} onPressedChange={() => (hiddenCurrentPassword = !hiddenCurrentPassword)} > - {#if hiddenCurrentPassword}{:else}{/if} + {#if hiddenCurrentPassword}{:else}{/if} @@ -73,7 +73,7 @@ const { form: formData, enhance } = form aria-label={`${hiddenPassword ? 'Show' : 'Hide'} Password}`} onPressedChange={() => (hiddenPassword = !hiddenPassword)} > - {#if hiddenPassword}{:else}{/if} + {#if hiddenPassword}{:else}{/if} @@ -93,7 +93,7 @@ const { form: formData, enhance } = form aria-label={`${hiddenConfirmPassword ? 'Show' : 'Hide'} Confirm Password}`} onPressedChange={() => (hiddenConfirmPassword = !hiddenConfirmPassword)} > - {#if hiddenConfirmPassword}{:else}{/if} + {#if hiddenConfirmPassword}{:else}{/if} diff --git a/src/routes/(app)/(protected)/settings/security/mfa/totp/+page.server.ts b/src/routes/(app)/(protected)/settings/security/mfa/totp/+page.server.ts index 4d6577a..1a45df9 100644 --- a/src/routes/(app)/(protected)/settings/security/mfa/totp/+page.server.ts +++ b/src/routes/(app)/(protected)/settings/security/mfa/totp/+page.server.ts @@ -102,7 +102,7 @@ export const actions: Actions = { const { error: verifyPasswordError } = await locals.api.me.verify.password .$post({ - json: { password: addTwoFactorForm.data.current_password }, + json: { password: addTwoFactorForm.data.password }, }) .then(locals.parseApiResponse) @@ -144,7 +144,7 @@ export const actions: Actions = { } const { error: verifyPasswordError } = await locals.api.me.verify.password .$post({ - json: { password: removeTwoFactorForm.data.current_password }, + json: { password: removeTwoFactorForm.data.password }, }) .then(locals.parseApiResponse) diff --git a/src/routes/(app)/(protected)/settings/security/mfa/totp/+page.svelte b/src/routes/(app)/(protected)/settings/security/mfa/totp/+page.svelte index b52ba0b..47672d4 100644 --- a/src/routes/(app)/(protected)/settings/security/mfa/totp/+page.svelte +++ b/src/routes/(app)/(protected)/settings/security/mfa/totp/+page.svelte @@ -42,7 +42,7 @@ const { form: removeTwoFactorFormData, enhance: removeTwoFactorEnhance } = remov Current Password - + Please enter your current password. @@ -64,7 +64,7 @@ const { form: removeTwoFactorFormData, enhance: removeTwoFactorEnhance } = remov Enter Password - + Please enter your current password. diff --git a/src/routes/(app)/(protected)/settings/security/mfa/totp/schemas.ts b/src/routes/(app)/(protected)/settings/security/mfa/totp/schemas.ts index fd14c28..6e05a49 100644 --- a/src/routes/(app)/(protected)/settings/security/mfa/totp/schemas.ts +++ b/src/routes/(app)/(protected)/settings/security/mfa/totp/schemas.ts @@ -1,14 +1,14 @@ import { z } from 'zod' export const addTwoFactorSchema = z.object({ - current_password: z.string({ required_error: 'Current Password is required' }), + password: z.string({ required_error: 'Current Password is required' }), two_factor_code: z.string({ required_error: 'Two Factor Code is required' }).trim(), }) export type AddTwoFactorSchema = typeof addTwoFactorSchema export const removeTwoFactorSchema = addTwoFactorSchema.pick({ - current_password: true, + password: true, }) export type RemoveTwoFactorSchema = typeof removeTwoFactorSchema diff --git a/src/routes/(app)/(protected)/wishlists/+page.svelte b/src/routes/(app)/(protected)/wishlists/+page.svelte index a65577f..ace0846 100644 --- a/src/routes/(app)/(protected)/wishlists/+page.svelte +++ b/src/routes/(app)/(protected)/wishlists/+page.svelte @@ -1,26 +1,28 @@ Your Wishlists | Bored Game -

Your wishlistsTable

+
+

Your wishlistsTable

-
-
- {#if wishlists.length === 0} -

You have no wishlistsTable

- {:else} - {#each wishlists as wishlist} -
-

{wishlist.name}

-

Created at: {new Date(wishlist.created_at).toLocaleString()}

-
- {/each} - {/if} +
+
+ {#if wishlists.length === 0} +

You have no wishlistsTable

+ {:else} + {#each wishlists as wishlist} +
+

{wishlist.name}

+

Created at: {new Date(wishlist.created_at).toLocaleString()}

+
+ {/each} + {/if} +
diff --git a/src/routes/(app)/(protected)/wishlists/[cuid]/+page.svelte b/src/routes/(app)/(protected)/wishlists/[cuid]/+page.svelte index 4eb0cca..784cbd7 100644 --- a/src/routes/(app)/(protected)/wishlists/[cuid]/+page.svelte +++ b/src/routes/(app)/(protected)/wishlists/[cuid]/+page.svelte @@ -1,35 +1,44 @@ {`Your Wishlist | Bored Game`} -

Your wishlist

+
+

Your wishlist

-
- {#if items.length > 0} - {#each items as item (item.id)} - - {/each} - {:else} -

Sorry no gamesTable found!

- {/if} +
+ {#if items.length > 0} + {#each items as item (item.id)} + + {/each} + {:else} +

Sorry no games found!

+ {/if} +
diff --git a/vite.config.ts b/vite.config.ts index 9ccc126..c484cfd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -16,7 +16,8 @@ export default defineConfig({ sveltekit() ], test: { - include: ['src/**/*.{test,spec}.{js,ts}'] + include: ['src/**/*.{test,spec}.{js,ts}'], + mockReset: true, }, css: { devSourcemap: true, From b1527e7782f7b9fa575b6048118bd9106d599f49 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Fri, 13 Sep 2024 17:21:22 -0700 Subject: [PATCH 04/14] Fixing all the tests to pass --- src/lib/server/api/databases/seeds/roles.ts | 2 +- .../api/databases/tables/roles.table.ts | 7 + src/lib/server/api/services/iam.service.ts | 4 +- src/lib/server/api/tests/iam.service.test.ts | 115 ++++++---- .../server/api/tests/tokens.service.test.ts | 32 ++- .../api/tests/user_roles.service.test.ts | 105 +++++---- .../server/api/tests/users.service.test.ts | 211 +++++++----------- 7 files changed, 227 insertions(+), 249 deletions(-) diff --git a/src/lib/server/api/databases/seeds/roles.ts b/src/lib/server/api/databases/seeds/roles.ts index 35cefcd..a299a94 100644 --- a/src/lib/server/api/databases/seeds/roles.ts +++ b/src/lib/server/api/databases/seeds/roles.ts @@ -1,5 +1,5 @@ import * as schema from '$lib/server/api/databases/tables' -import { type db } from '$lib/server/api/packages/drizzle' +import type { db } from '$lib/server/api/packages/drizzle' import roles from './data/roles.json' export default async function seed(db: db) { diff --git a/src/lib/server/api/databases/tables/roles.table.ts b/src/lib/server/api/databases/tables/roles.table.ts index 1dfcfc2..e9f036f 100644 --- a/src/lib/server/api/databases/tables/roles.table.ts +++ b/src/lib/server/api/databases/tables/roles.table.ts @@ -4,6 +4,13 @@ import { pgTable, text, uuid } from 'drizzle-orm/pg-core' import { timestamps } from '../../common/utils/table' import { user_roles } from './userRoles.table' +export enum RoleName { + ADMIN = 'admin', + EDITOR = 'editor', + MODERATOR = 'moderator', + USER = 'user', +} + export const rolesTable = pgTable('roles', { id: uuid('id').primaryKey().defaultRandom(), cuid: text('cuid') diff --git a/src/lib/server/api/services/iam.service.ts b/src/lib/server/api/services/iam.service.ts index a70e8c3..7d93a5f 100644 --- a/src/lib/server/api/services/iam.service.ts +++ b/src/lib/server/api/services/iam.service.ts @@ -44,13 +44,13 @@ export class IamService { } const existingUserForNewUsername = await this.usersService.findOneByUsername(data.username) - if (existingUserForNewUsername && existingUserForNewUsername.id !== userId) { + if (existingUserForNewUsername && existingUserForNewUsername.id !== user.id) { return { error: 'Username already in use', } } - return this.usersService.updateUser(userId, { + return this.usersService.updateUser(user.id, { first_name: data.firstName, last_name: data.lastName, username: data.username !== user.username ? data.username : user.username, diff --git a/src/lib/server/api/tests/iam.service.test.ts b/src/lib/server/api/tests/iam.service.test.ts index 96d3174..40b0bae 100644 --- a/src/lib/server/api/tests/iam.service.test.ts +++ b/src/lib/server/api/tests/iam.service.test.ts @@ -2,6 +2,7 @@ import 'reflect-metadata' import { IamService } from '$lib/server/api/services/iam.service' import { LuciaService } from '$lib/server/api/services/lucia.service' import { UsersService } from '$lib/server/api/services/users.service' +import { faker } from '@faker-js/faker' import { container } from 'tsyringe' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -25,60 +26,36 @@ describe('IamService', () => { vi.resetAllMocks() }) + const timeStampDate = new Date() + const dbUser = { + id: faker.string.uuid(), + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + first_name: faker.person.firstName(), + last_name: faker.person.lastName(), + email: faker.internet.email(), + username: faker.internet.userName(), + verified: false, + receive_email: false, + mfa_enabled: false, + theme: 'system', + createdAt: timeStampDate, + updatedAt: timeStampDate, + } + describe('Update Profile', () => { - it('should resolve', async () => { - const timeStampDate = new Date() - userService.findOneById = vi.fn().mockResolvedValueOnce({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - cuid: 'ciglo1j8q0000t9j4xq8d6p5e', - first_name: 'test', - last_name: 'test', - email: 'test@example.com', - username: 'test', - verified: false, - receive_email: false, - mfa_enabled: false, - theme: 'system', - createdAt: timeStampDate, - updatedAt: timeStampDate, - }) + it('should update user', async () => { + userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser) userService.findOneByUsername = vi.fn().mockResolvedValue(undefined) - userService.updateUser = vi.fn().mockResolvedValue({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - cuid: 'ciglo1j8q0000t9j4xq8d6p5e', - first_name: 'test', - last_name: 'test', - email: 'test@example.com', - username: 'test', - verified: false, - receive_email: false, - mfa_enabled: false, - theme: 'system', - createdAt: timeStampDate, - updatedAt: timeStampDate, - }) + userService.updateUser = vi.fn().mockResolvedValue(dbUser) const spy_userService_findOneById = vi.spyOn(userService, 'findOneById') const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername') const spy_userService_updateUser = vi.spyOn(userService, 'updateUser') await expect( - service.updateProfile('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', { - username: 'test', + service.updateProfile(faker.string.uuid(), { + username: faker.internet.userName(), }), - ).resolves.toEqual({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - cuid: 'ciglo1j8q0000t9j4xq8d6p5e', - first_name: 'test', - last_name: 'test', - email: 'test@example.com', - username: 'test', - verified: false, - receive_email: false, - mfa_enabled: false, - theme: 'system', - createdAt: timeStampDate, - updatedAt: timeStampDate, - }) + ).resolves.toEqual(dbUser) expect(spy_userService_findOneById).toBeCalledTimes(1) expect(spy_userService_findOneByUsername).toBeCalledTimes(1) expect(spy_userService_updateUser).toBeCalledTimes(1) @@ -91,8 +68,8 @@ describe('IamService', () => { const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername') const spy_userService_updateUser = vi.spyOn(userService, 'updateUser') await expect( - service.updateProfile('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', { - username: 'test', + service.updateProfile(faker.string.uuid(), { + username: faker.internet.userName(), }), ).resolves.toEqual({ error: 'User not found', @@ -101,5 +78,47 @@ describe('IamService', () => { expect(spy_userService_findOneByUsername).toBeCalledTimes(0) expect(spy_userService_updateUser).toBeCalledTimes(0) }) + + it('should error on duplicate username', async () => { + userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser) + userService.findOneByUsername = vi.fn().mockResolvedValue({ + id: faker.string.uuid(), + }) + userService.updateUser = vi.fn().mockResolvedValue(dbUser) + + const spy_userService_findOneById = vi.spyOn(userService, 'findOneById') + const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername') + const spy_userService_updateUser = vi.spyOn(userService, 'updateUser') + await expect( + service.updateProfile(faker.string.uuid(), { + username: faker.internet.userName(), + }), + ).resolves.toEqual({ + error: 'Username already in use', + }) + expect(spy_userService_findOneById).toBeCalledTimes(1) + expect(spy_userService_findOneByUsername).toBeCalledTimes(1) + expect(spy_userService_updateUser).toBeCalledTimes(0) + }) + }) + + it('should not error if the user id of new username is the current user id', async () => { + userService.findOneById = vi.fn().mockResolvedValueOnce(dbUser) + userService.findOneByUsername = vi.fn().mockResolvedValue({ + id: dbUser.id, + }) + userService.updateUser = vi.fn().mockResolvedValue(dbUser) + + const spy_userService_findOneById = vi.spyOn(userService, 'findOneById') + const spy_userService_findOneByUsername = vi.spyOn(userService, 'findOneByUsername') + const spy_userService_updateUser = vi.spyOn(userService, 'updateUser') + await expect( + service.updateProfile(dbUser.id, { + username: dbUser.id, + }), + ).resolves.toEqual(dbUser) + expect(spy_userService_findOneById).toBeCalledTimes(1) + expect(spy_userService_findOneByUsername).toBeCalledTimes(1) + expect(spy_userService_updateUser).toBeCalledTimes(1) }) }) diff --git a/src/lib/server/api/tests/tokens.service.test.ts b/src/lib/server/api/tests/tokens.service.test.ts index ad0aa3b..cff2574 100644 --- a/src/lib/server/api/tests/tokens.service.test.ts +++ b/src/lib/server/api/tests/tokens.service.test.ts @@ -1,7 +1,7 @@ import 'reflect-metadata' import { Argon2id } from 'oslo/password' import { container } from 'tsyringe' -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, describe, expect, expectTypeOf, it, vi } from 'vitest' import { HashingService } from '../services/hashing.service' import { TokensService } from '../services/tokens.service' @@ -18,30 +18,28 @@ describe('TokensService', () => { }) describe('Generate Token', () => { - const hashedPassword = new Argon2id().hash('111') - - hashingService.hash = vi.fn().mockResolvedValue(hashedPassword) - hashingService.verify = vi.fn().mockResolvedValue(true) - - const spy_hashingService_hash = vi.spyOn(hashingService, 'hash') - const spy_hashingService_verify = vi.spyOn(hashingService, 'verify') - it('should resolve', async () => { - expect(service.createHashedToken('111')).resolves.string + const hashedPassword = await new Argon2id().hash('111') + hashingService.hash = vi.fn().mockResolvedValue(hashedPassword) + const spy_hashingService_hash = vi.spyOn(hashingService, 'hash') + const spy_hashingService_verify = vi.spyOn(hashingService, 'verify') + await expectTypeOf(service.createHashedToken('111')).resolves.toBeString() + expect(spy_hashingService_hash).toBeCalledTimes(1) + expect(spy_hashingService_verify).toBeCalledTimes(0) }) it('should generate a token that is verifiable', async () => { + hashingService.hash = vi.fn().mockResolvedValue(await new Argon2id().hash('111')) + hashingService.verify = vi.fn().mockResolvedValue(true) + const spy_hashingService_hash = vi.spyOn(hashingService, 'hash') + const spy_hashingService_verify = vi.spyOn(hashingService, 'verify') const token = await service.createHashedToken('111') + expect(token).not.toBeNaN() expect(token).not.toBeUndefined() expect(token).not.toBeNull() const verifiable = await service.verifyHashedToken(token, '111') expect(verifiable).toBeTruthy() - }) - - it('should generate a hashed token', async () => { - expect(spy_hashingService_hash).toHaveBeenCalledTimes(2) - }) - it('should verify a hashed token', async () => { - expect(spy_hashingService_verify).toHaveBeenCalledTimes(1) + expect(spy_hashingService_hash).toBeCalledTimes(1) + expect(spy_hashingService_verify).toBeCalledTimes(1) }) }) }) diff --git a/src/lib/server/api/tests/user_roles.service.test.ts b/src/lib/server/api/tests/user_roles.service.test.ts index dfd47d2..89cde2a 100644 --- a/src/lib/server/api/tests/user_roles.service.test.ts +++ b/src/lib/server/api/tests/user_roles.service.test.ts @@ -1,62 +1,75 @@ -import 'reflect-metadata'; -import { container } from 'tsyringe'; -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -import { UserRolesService } from '../services/user_roles.service'; -import { UserRolesRepository } from '../repositories/user_roles.repository'; -import { RolesService } from '../services/roles.service'; +import 'reflect-metadata' +import { RoleName } from '$lib/server/api/databases/tables' +import { faker } from '@faker-js/faker' +import { container } from 'tsyringe' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { UserRolesRepository } from '../repositories/user_roles.repository' +import { RolesService } from '../services/roles.service' +import { UserRolesService } from '../services/user_roles.service' describe('UserRolesService', () => { - let service: UserRolesService; - const userRolesRepository = vi.mocked(UserRolesRepository.prototype); - const rolesService = vi.mocked(RolesService.prototype); + let service: UserRolesService + const userRolesRepository = vi.mocked(UserRolesRepository.prototype) + const rolesService = vi.mocked(RolesService.prototype) beforeAll(() => { service = container .register(UserRolesRepository, { useValue: userRolesRepository }) .register(RolesService, { useValue: rolesService }) - .resolve(UserRolesService); - }); + .resolve(UserRolesService) + }) afterAll(() => { vi.resetAllMocks() - }); + }) + + const timeStampDate = new Date() + const roleUUID = faker.string.uuid() + const userUUID = faker.string.uuid() + const dbRole = { + id: roleUUID, + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + name: RoleName.ADMIN, + createdAt: timeStampDate, + updatedAt: timeStampDate, + } + + const dbUserRole = { + id: faker.string.uuid(), + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + role_id: roleUUID, + user_id: userUUID, + primary: true, + createdAt: timeStampDate, + updatedAt: timeStampDate, + } describe('Create User Role', () => { - rolesService.findOneByNameOrThrow = vi.fn().mockResolvedValue({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - cuid: 'ciglo1j8q0000t9j4xq8d6p5e', - name: 'user', - createdAt: new Date(), - updatedAt: new Date() - } satisfies Awaited>); - - userRolesRepository.create = vi.fn().mockResolvedValue({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - cuid: 'ciglo1j8q0000t9j4xq8d6p5e', - user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8fff', - role_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - primary: true, - createdAt: new Date(), - updatedAt: new Date() - } satisfies Awaited>); - - const spy_rolesService_findOneByNameOrThrow = vi.spyOn(rolesService, 'findOneByNameOrThrow'); - const spy_userRolesRepository_create = vi.spyOn(userRolesRepository, 'create'); - it('should resolve', async () => { - await expect(service.addRoleToUser('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8fff', 'user', true)).resolves.not.toThrowError(); - }) - it('should call rolesService.findOneByNameOrThrow', async () => { - expect(spy_rolesService_findOneByNameOrThrow).toBeCalledWith('user'); - expect(spy_rolesService_findOneByNameOrThrow).toBeCalledTimes(1); - }) - it('should call userRolesRepository.create', async () => { + rolesService.findOneByNameOrThrow = vi.fn().mockResolvedValue(dbRole satisfies Awaited>) + + userRolesRepository.create = vi.fn().mockResolvedValue(dbUserRole satisfies Awaited>) + + const spy_rolesService_findOneByNameOrThrow = vi.spyOn(rolesService, 'findOneByNameOrThrow') + const spy_userRolesRepository_create = vi.spyOn(userRolesRepository, 'create') + + await expect(service.addRoleToUser(userUUID, RoleName.ADMIN, true)).resolves.not.toThrowError() + expect(spy_rolesService_findOneByNameOrThrow).toBeCalledWith(RoleName.ADMIN) + expect(spy_rolesService_findOneByNameOrThrow).toBeCalledTimes(1) expect(spy_userRolesRepository_create).toBeCalledWith({ - user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8fff', - role_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - primary: true - }); - expect(spy_userRolesRepository_create).toBeCalledTimes(1); + user_id: userUUID, + role_id: dbRole.id, + primary: true, + }) + expect(spy_userRolesRepository_create).toBeCalledTimes(1) + }) + it('should error on no role found', async () => { + rolesService.findOneByNameOrThrow = vi.fn().mockResolvedValue(undefined) + + const spy_rolesService_findOneByNameOrThrow = vi.spyOn(rolesService, 'findOneByNameOrThrow') + await expect(service.addRoleToUser(userUUID, RoleName.ADMIN, true)).rejects.toThrowError(`Role with name ${RoleName.ADMIN} not found`) + expect(spy_rolesService_findOneByNameOrThrow).toBeCalledWith(RoleName.ADMIN) + expect(spy_rolesService_findOneByNameOrThrow).toBeCalledTimes(1) }) }) -}); \ No newline at end of file +}) diff --git a/src/lib/server/api/tests/users.service.test.ts b/src/lib/server/api/tests/users.service.test.ts index ec551a2..ca916e2 100644 --- a/src/lib/server/api/tests/users.service.test.ts +++ b/src/lib/server/api/tests/users.service.test.ts @@ -1,8 +1,9 @@ import 'reflect-metadata' import { CredentialsType } from '$lib/server/api/databases/tables' +import { faker } from '@faker-js/faker' import { Argon2id } from 'oslo/password' import { container } from 'tsyringe' -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' import { CredentialsRepository } from '../repositories/credentials.repository' import { UsersRepository } from '../repositories/users.repository' import { CollectionsService } from '../services/collections.service' @@ -35,154 +36,94 @@ describe('UsersService', () => { vi.resetAllMocks() }) + const timeStampDate = new Date() + const dbUser = { + id: faker.string.uuid(), + cuid: 'ciglo1j8q0000t9j4xq8d6p5e', + first_name: faker.person.firstName(), + last_name: faker.person.lastName(), + email: faker.internet.email(), + username: faker.internet.userName(), + verified: false, + receive_email: false, + mfa_enabled: false, + theme: 'system', + createdAt: timeStampDate, + updatedAt: timeStampDate, + } + const dbCredentials = { + id: faker.string.uuid(), + user_id: dbUser.id, + type: CredentialsType.PASSWORD, + secret_data: 'hashedPassword', + createdAt: timeStampDate, + updatedAt: timeStampDate, + } + describe('Create User', () => { - const date = new Date() - - const hashedPassword = new Argon2id().hash('111') - tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) - - usersRepository.create = vi.fn().mockResolvedValue({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - cuid: 'ciglo1j8q0000t9j4xq8d6p5e', - first_name: 'test', - last_name: 'test', - email: 'test@example.com', - username: 'test', - verified: false, - receive_email: false, - mfa_enabled: false, - theme: 'system', - createdAt: date, - updatedAt: date, - } satisfies Awaited>) - - credentialsRepository.create = vi.fn().mockResolvedValue({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - type: CredentialsType.PASSWORD, - secret_data: hashedPassword, - }) satisfies Awaited> - - userRolesService.addRoleToUser = vi.fn().mockResolvedValue(undefined) - - wishlistsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined) - - collectionsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined) - - const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') - const spy_usersRepository_create = vi.spyOn(usersRepository, 'create') - const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create') - const spy_userRolesService_addRoleToUser = vi.spyOn(userRolesService, 'addRoleToUser') - const spy_wishlistsService_createEmptyNoName = vi.spyOn(wishlistsService, 'createEmptyNoName') - const spy_collectionsService_createEmptyNoName = vi.spyOn(collectionsService, 'createEmptyNoName') - it('should resolve', async () => { + const hashedPassword = new Argon2id().hash('111') + tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) + usersRepository.create = vi.fn().mockResolvedValue(dbUser satisfies Awaited>) + credentialsRepository.create = vi.fn().mockResolvedValue(dbCredentials satisfies Awaited>) + userRolesService.addRoleToUser = vi.fn().mockResolvedValue(undefined) + wishlistsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined) + collectionsService.createEmptyNoName = vi.fn().mockResolvedValue(undefined) + + const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') + const spy_usersRepository_create = vi.spyOn(usersRepository, 'create') + const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create') + const spy_userRolesService_addRoleToUser = vi.spyOn(userRolesService, 'addRoleToUser') + const spy_wishlistsService_createEmptyNoName = vi.spyOn(wishlistsService, 'createEmptyNoName') + const spy_collectionsService_createEmptyNoName = vi.spyOn(collectionsService, 'createEmptyNoName') await expect( service.create({ - firstName: 'test', - lastName: 'test', - email: 'test@example.com', - username: 'test', - password: '111', - confirm_password: '111', + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + email: faker.internet.email(), + username: faker.internet.userName(), + password: faker.string.alphanumeric(10), + confirm_password: faker.string.alphanumeric(10), }), - ).resolves.toEqual({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - cuid: 'ciglo1j8q0000t9j4xq8d6p5e', - first_name: 'test', - last_name: 'test', - email: 'test@example.com', - username: 'test', - verified: false, - receive_email: false, - mfa_enabled: false, - theme: 'system', - createdAt: date, - updatedAt: date, - }) - }) - // it('should generate a hashed token', async () => { - // expect(spy_tokensService_createHashToken).toBeCalledTimes(1) - // }) - // it('should create a new user', async () => { - // expect(spy_usersRepository_create).toBeCalledTimes(1) - // }) - // it('should create a new credential', async () => { - // expect(spy_credentialsRepository_create).toBeCalledTimes(1) - // }) - // it('should add role to user', async () => { - // expect(spy_userRolesService_addRoleToUser).toBeCalledTimes(1) - // }) - // it('should create a new wishlist', async () => { - // expect(spy_wishlistsService_createEmptyNoName).toBeCalledTimes(1) - // }) - // it('should create a new collection', async () => { - // expect(spy_collectionsService_createEmptyNoName).toBeCalledTimes(1) - // }) - }) - describe('Update User Password Exiting Credentials', () => { - const hashedPassword = new Argon2id().hash('111') - tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) satisfies Awaited> - credentialsRepository.update = vi.fn().mockResolvedValue({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - type: 'PASSWORD', - secret_data: hashedPassword, - }) satisfies Awaited> - credentialsRepository.findPasswordCredentialsByUserId = vi.fn().mockResolvedValue({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - type: 'PASSWORD', - secret_data: hashedPassword, - }) satisfies Awaited> - - const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') - const spy_credentialsRepository_findPasswordCredentialsByUserId = vi.spyOn(credentialsRepository, 'findPasswordCredentialsByUserId') - const spy_credentialsRepository_update = vi.spyOn(credentialsRepository, 'update') - - it('should resolve', async () => { - await expect(service.updatePassword('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', '111')).resolves.toBeUndefined() - }) - console.log(spy_tokensService_createHashToken.mock.calls) - it('should generate a hashed token', async () => { + ).resolves.toEqual(dbUser) expect(spy_tokensService_createHashToken).toBeCalledTimes(1) + expect(spy_usersRepository_create).toBeCalledTimes(1) + expect(spy_credentialsRepository_create).toBeCalledTimes(1) + expect(spy_userRolesService_addRoleToUser).toBeCalledTimes(1) + expect(spy_wishlistsService_createEmptyNoName).toBeCalledTimes(1) + expect(spy_collectionsService_createEmptyNoName).toBeCalledTimes(1) }) - console.log(spy_credentialsRepository_findPasswordCredentialsByUserId.mock.calls) - it('should call find password credentials by user id', async () => { + }) + describe('Update User', () => { + it('should resolve Password Exiting Credentials', async () => { + const hashedPassword = new Argon2id().hash('111') + tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) + credentialsRepository.update = vi.fn().mockResolvedValue(dbCredentials satisfies Awaited>) + credentialsRepository.findPasswordCredentialsByUserId = vi + .fn() + .mockResolvedValue(dbCredentials satisfies Awaited>) + + const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') + const spy_credentialsRepository_findPasswordCredentialsByUserId = vi.spyOn(credentialsRepository, 'findPasswordCredentialsByUserId') + const spy_credentialsRepository_update = vi.spyOn(credentialsRepository, 'update') + await expect(service.updatePassword(dbUser.id, faker.string.alphanumeric(10))).resolves.toBeUndefined() + expect(spy_tokensService_createHashToken).toBeCalledTimes(1) expect(spy_credentialsRepository_findPasswordCredentialsByUserId).toBeCalledTimes(1) - }) - console.log(spy_credentialsRepository_update.mock.calls) - it('should update the credential when user has credential', async () => { expect(spy_credentialsRepository_update).toBeCalledTimes(1) }) - }) - describe('Update User Password No Existing Credentials', () => { - const hashedPassword = new Argon2id().hash('111') - tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) satisfies Awaited> - credentialsRepository.findPasswordCredentialsByUserId = vi.fn().mockResolvedValue(null) satisfies Awaited< - ReturnType - > - credentialsRepository.create = vi.fn().mockResolvedValue({ - id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - user_id: '3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', - type: 'PASSWORD', - secret_data: hashedPassword, - }) satisfies Awaited> + it('Should Create User Password No Existing Credentials', async () => { + const hashedPassword = new Argon2id().hash('111') + tokensService.createHashedToken = vi.fn().mockResolvedValue(hashedPassword) + credentialsRepository.findPasswordCredentialsByUserId = vi.fn().mockResolvedValue(null) + credentialsRepository.create = vi.fn().mockResolvedValue(dbCredentials satisfies Awaited>) - const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') - const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create') - const spy_credentialsRepository_findPasswordCredentialsByUserId = vi.spyOn(credentialsRepository, 'findPasswordCredentialsByUserId') + const spy_tokensService_createHashToken = vi.spyOn(tokensService, 'createHashedToken') + const spy_credentialsRepository_create = vi.spyOn(credentialsRepository, 'create') + const spy_credentialsRepository_findPasswordCredentialsByUserId = vi.spyOn(credentialsRepository, 'findPasswordCredentialsByUserId') - it('should resolve with no current credential for user', async () => { - await expect(service.updatePassword('3e0e9f0f-0a0b-4f0b-8f0b-0a0b4f0b8f0b', '111')).resolves.not.toThrow() - }) - it('should generate a hashed token', async () => { + await expect(service.updatePassword(dbUser.id, faker.string.alphanumeric(10))).resolves.not.toThrow() expect(spy_tokensService_createHashToken).toBeCalledTimes(1) - }) - it('should call find password credentials by user id', async () => { expect(spy_credentialsRepository_findPasswordCredentialsByUserId).toBeCalledTimes(1) - }) - it('should create a new credential when user has no credential', async () => { expect(spy_credentialsRepository_create).toHaveBeenCalledTimes(1) }) }) From fbf4d08b07197a3bcbf5bfb6ba9a1c5fef3828f2 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Mon, 16 Sep 2024 09:07:22 -0700 Subject: [PATCH 05/14] Adding OAuth for GitHub. --- .env.example | 4 + package.json | 16 +- pnpm-lock.yaml | 752 ++++++++++-------- src/lib/server/api/common/env.ts | 2 + .../api/controllers/collection.controller.ts | 5 + .../api/controllers/oauth.controller.ts | 55 ++ .../api/databases/tables/collections.table.ts | 4 +- src/lib/server/api/index.ts | 1 + .../repositories/collections.repository.ts | 19 +- .../federated_identity.repository.ts | 25 + .../api/services/collections.service.ts | 4 + src/lib/server/api/services/oauth.service.ts | 54 ++ src/lib/server/auth.ts | 4 + .../(protected)/collections/+page.server.ts | 20 +- .../(protected)/collections/+page.svelte | 26 +- .../(auth)/auth/callback/github/+server.ts | 21 + src/routes/(auth)/login/github/+server.ts | 18 + 17 files changed, 645 insertions(+), 385 deletions(-) create mode 100644 src/lib/server/api/controllers/oauth.controller.ts create mode 100644 src/lib/server/api/repositories/federated_identity.repository.ts create mode 100644 src/lib/server/api/services/oauth.service.ts create mode 100644 src/lib/server/auth.ts create mode 100644 src/routes/(auth)/auth/callback/github/+server.ts create mode 100644 src/routes/(auth)/login/github/+server.ts diff --git a/.env.example b/.env.example index 7b3b671..527dae2 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,10 @@ ADMIN_PASSWORD= TWO_FACTOR_TIMEOUT=300000 +# OAuth +GITHUB_CLIENT_ID="" +GITHUB_CLIENT_SECRET="" + # Public PUBLIC_SITE_NAME='Bored Game' diff --git a/package.json b/package.json index e04cff2..92afa67 100644 --- a/package.json +++ b/package.json @@ -27,16 +27,17 @@ "@faker-js/faker": "^8.4.1", "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", - "@playwright/test": "^1.47.0", + "@playwright/test": "^1.47.1", "@sveltejs/adapter-auto": "^3.2.4", - "@sveltejs/enhanced-img": "^0.3.4", - "@sveltejs/kit": "^2.5.26", + "@sveltejs/enhanced-img": "^0.3.7", + "@sveltejs/kit": "^2.5.27", "@sveltejs/vite-plugin-svelte": "^3.1.2", "@types/cookie": "^0.6.0", "@types/node": "^20.16.5", - "@types/pg": "^8.11.9", + "@types/pg": "^8.11.10", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", + "arctic": "^1.9.2", "autoprefixer": "^10.4.20", "drizzle-kit": "^0.23.2", "eslint": "^8.57.0", @@ -47,7 +48,7 @@ "lucia": "3.2.0", "lucide-svelte": "^0.408.0", "nodemailer": "^6.9.15", - "postcss": "^8.4.45", + "postcss": "^8.4.47", "postcss-import": "^16.1.0", "postcss-load-config": "^5.1.0", "postcss-preset-env": "^9.6.0", @@ -64,13 +65,13 @@ "svelte-sequential-preprocessor": "^2.0.1", "sveltekit-flash-message": "^2.4.4", "sveltekit-rate-limiter": "^0.5.2", - "sveltekit-superforms": "^2.17.0", + "sveltekit-superforms": "^2.18.0", "tailwindcss": "^3.4.11", "ts-node": "^10.9.2", "tslib": "^2.7.0", "tsx": "^4.19.1", "typescript": "^5.6.2", - "vite": "^5.4.4", + "vite": "^5.4.5", "vitest": "^1.6.0", "zod": "^3.23.8" }, @@ -92,7 +93,6 @@ "@sveltejs/adapter-vercel": "^5.4.3", "@types/feather-icons": "^4.29.4", "@vercel/og": "^0.5.20", - "arctic": "^1.9.2", "bits-ui": "^0.21.13", "boardgamegeekclient": "^1.9.1", "bullmq": "^5.13.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bcf7f5..3368666 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 3.5.5 '@lucia-auth/adapter-drizzle': specifier: ^1.1.0 - version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) + version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) '@lukeed/uuid': specifier: ^2.0.1 version: 2.0.1 @@ -46,19 +46,16 @@ importers: version: 2.6.2 '@sveltejs/adapter-node': specifier: ^5.2.2 - version: 5.2.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))) + version: 5.2.2(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))) '@sveltejs/adapter-vercel': specifier: ^5.4.3 - version: 5.4.3(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))) + version: 5.4.3(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 '@vercel/og': specifier: ^0.5.20 version: 0.5.20 - arctic: - specifier: ^1.9.2 - version: 1.9.2 bits-ui: specifier: ^0.21.13 version: 0.21.13(svelte@5.0.0-next.175) @@ -85,16 +82,16 @@ importers: version: 11.0.6 drizzle-orm: specifier: ^0.32.2 - version: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4) + version: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4) drizzle-zod: specifier: ^0.5.1 - version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) + version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) feather-icons: specifier: ^4.29.2 version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 @@ -181,20 +178,20 @@ importers: specifier: ^0.83.0 version: 0.83.0(svelte@5.0.0-next.175) '@playwright/test': - specifier: ^1.47.0 - version: 1.47.0 + specifier: ^1.47.1 + version: 1.47.1 '@sveltejs/adapter-auto': specifier: ^3.2.4 - version: 3.2.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))) + version: 3.2.4(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))) '@sveltejs/enhanced-img': - specifier: ^0.3.4 - version: 0.3.4(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + specifier: ^0.3.7 + version: 0.3.7(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) '@sveltejs/kit': - specifier: ^2.5.26 - version: 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + specifier: ^2.5.27 + version: 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 @@ -202,17 +199,20 @@ importers: specifier: ^20.16.5 version: 20.16.5 '@types/pg': - specifier: ^8.11.9 - version: 8.11.9 + specifier: ^8.11.10 + version: 8.11.10 '@typescript-eslint/eslint-plugin': specifier: ^7.18.0 version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) '@typescript-eslint/parser': specifier: ^7.18.0 version: 7.18.0(eslint@8.57.0)(typescript@5.6.2) + arctic: + specifier: ^1.9.2 + version: 1.9.2 autoprefixer: specifier: ^10.4.20 - version: 10.4.20(postcss@8.4.45) + version: 10.4.20(postcss@8.4.47) drizzle-kit: specifier: ^0.23.2 version: 0.23.2 @@ -241,17 +241,17 @@ importers: specifier: ^6.9.15 version: 6.9.15 postcss: - specifier: ^8.4.45 - version: 8.4.45 + specifier: ^8.4.47 + version: 8.4.47 postcss-import: specifier: ^16.1.0 - version: 16.1.0(postcss@8.4.45) + version: 16.1.0(postcss@8.4.47) postcss-load-config: specifier: ^5.1.0 - version: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1) + version: 5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1) postcss-preset-env: specifier: ^9.6.0 - version: 9.6.0(postcss@8.4.45) + version: 9.6.0(postcss@8.4.47) prettier: specifier: ^3.3.3 version: 3.3.3 @@ -272,7 +272,7 @@ importers: version: 5.0.0-next.175 svelte-check: specifier: ^3.8.6 - version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175) + version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) @@ -281,19 +281,19 @@ importers: version: 3.1.4(svelte@5.0.0-next.175)(typescript@5.6.2) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))) + version: 0.5.2(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))) sveltekit-superforms: - specifier: ^2.17.0 - version: 2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) + specifier: ^2.18.0 + version: 2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) tailwindcss: specifier: ^3.4.11 version: 3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) @@ -310,8 +310,8 @@ importers: specifier: ^5.6.2 version: 5.6.2 vite: - specifier: ^5.4.4 - version: 5.4.4(@types/node@20.16.5)(sass@1.78.0) + specifier: ^5.4.5 + version: 5.4.5(@types/node@20.16.5)(sass@1.78.0) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.16.5)(sass@1.78.0) @@ -1706,8 +1706,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.47.0': - resolution: {integrity: sha512-SgAdlSwYVpToI4e/IH19IHHWvoijAYH5hu2MWSXptRypLSnzj51PcGD+rsOXFayde4P9ZLi+loXVwArg6IUkCA==} + '@playwright/test@1.47.1': + resolution: {integrity: sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==} engines: {node: '>=18'} hasBin: true @@ -1956,14 +1956,14 @@ packages: peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/enhanced-img@0.3.4': - resolution: {integrity: sha512-eX+ob5uWr0bTLMKeG9nhhM84aR88hqiLiyEfWZPX7ijhk/wlmYSUX9nOiaVHh2ct1U+Ju9Hhb90Copw+ZNOB8w==} + '@sveltejs/enhanced-img@0.3.7': + resolution: {integrity: sha512-Rb692ralH4Vs8bx52b403Xs2ZmZJ3XctYiXGNXLay9ex1G56jD31jqPFnwoWKyUqiD9JTi6CxVp/1Xy2Dus86Q==} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 vite: '>= 5.0.0' - '@sveltejs/kit@2.5.26': - resolution: {integrity: sha512-8l1JTIM2L+bS8ebq1E+nGjv/YSKSnD9Q19bYIUkc41vaEG2JjVUx6ikvPIJv2hkQAuqJLzoPrXlKk4KcyWOv3Q==} + '@sveltejs/kit@2.5.27': + resolution: {integrity: sha512-CcbRTzl+65oWljAASL6UlxM4x3NWwd0fjq5fQOfP243vs50myFQ8lil0fr3Im6HeeQqYUCtnv8HjO8REWVPjTw==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2019,12 +2019,12 @@ packages: '@types/node@20.16.5': resolution: {integrity: sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==} + '@types/pg@8.11.10': + resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} + '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} - '@types/pg@8.11.9': - resolution: {integrity: sha512-M4mYeJZRBD9lCBCGa72F44uKSV9eJrAFfjlPJagdA6pgIr2OPJULFB7nqnZzOdqXG0qzHlgtZKzTdIgbmHitSg==} - '@types/pug@2.0.10': resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} @@ -2034,6 +2034,22 @@ packages: '@types/validator@13.12.1': resolution: {integrity: sha512-w0URwf7BQb0rD/EuiG12KP0bailHKHP5YVviJG9zw3ykAokL0TuxU2TUqMB7EwZ59bDHYdeTIvjI5m0S7qHfOA==} + '@typeschema/class-validator@0.2.0': + resolution: {integrity: sha512-zq0qeflVu1Z6D0ttkqAWZMtxJeNEQ70yo/025sV0jujiOOgQx38JXrky77nSWWPp2E1KIMtgkToQbkzkXyW5yg==} + peerDependencies: + class-validator: ^0.14.1 + peerDependenciesMeta: + class-validator: + optional: true + + '@typeschema/core@0.14.0': + resolution: {integrity: sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==} + peerDependencies: + '@types/json-schema': ^7.0.15 + peerDependenciesMeta: + '@types/json-schema': + optional: true + '@typescript-eslint/eslint-plugin@7.18.0': resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} engines: {node: ^18.18.0 || >=20.0.0} @@ -2351,6 +2367,9 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + class-variance-authority@0.7.0: resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} @@ -3219,6 +3238,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libphonenumber-js@1.11.8: + resolution: {integrity: sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg==} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -3673,13 +3695,13 @@ packages: pkg-types@1.2.0: resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} - playwright-core@1.47.0: - resolution: {integrity: sha512-1DyHT8OqkcfCkYUD9zzUTfg7EfTd+6a8MkD/NWOvjo0u/SCNd5YmY/lJwFvUZOxJbWNds+ei7ic2+R/cRz/PDg==} + playwright-core@1.47.1: + resolution: {integrity: sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==} engines: {node: '>=18'} hasBin: true - playwright@1.47.0: - resolution: {integrity: sha512-jOWiRq2pdNAX/mwLiwFYnPHpEZ4rM+fRSQpRHwEwZlP2PUANvL3+aJOF/bvISMhFD30rqMxUB4RJx9aQbfh4Ww==} + playwright@1.47.1: + resolution: {integrity: sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==} engines: {node: '>=18'} hasBin: true @@ -3922,8 +3944,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.45: - resolution: {integrity: sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==} + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -4211,6 +4233,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -4453,8 +4479,8 @@ packages: peerDependencies: '@sveltejs/kit': 1.x || 2.x - sveltekit-superforms@2.17.0: - resolution: {integrity: sha512-QrX8pkcmE0XoeVU42zMhsah4FoDrgtPc/4cZEr38rDlgU+DE0xNc5J0E7z1456sUJNbFjaB0+HZwwAkX0vYqaA==} + sveltekit-superforms@2.18.0: + resolution: {integrity: sha512-INx2EwWk82WcUdG5iEMtcJ+NLvBpYOdiHDRa68Q9p9W+YRAN3mxwiqj5gpfVrvBPlGfFZypeAze54pBZ/wlspw==} peerDependencies: '@sveltejs/kit': 1.x || 2.x svelte: 3.x || 4.x || >=5.0.0-next.51 @@ -4679,8 +4705,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.4: - resolution: {integrity: sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==} + vite@5.4.5: + resolution: {integrity: sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4939,201 +4965,201 @@ snapshots: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-cascade-layers@4.0.6(postcss@8.4.45)': + '@csstools/postcss-cascade-layers@4.0.6(postcss@8.4.47)': dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - '@csstools/postcss-color-function@3.0.19(postcss@8.4.45)': + '@csstools/postcss-color-function@3.0.19(postcss@8.4.47)': dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-color-mix-function@2.0.19(postcss@8.4.45)': + '@csstools/postcss-color-mix-function@2.0.19(postcss@8.4.47)': dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-content-alt-text@1.0.0(postcss@8.4.45)': + '@csstools/postcss-content-alt-text@1.0.0(postcss@8.4.47)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-exponential-functions@1.0.9(postcss@8.4.45)': + '@csstools/postcss-exponential-functions@1.0.9(postcss@8.4.47)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.4.45)': + '@csstools/postcss-font-format-keywords@3.0.2(postcss@8.4.47)': dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.4.45)': + '@csstools/postcss-gamut-mapping@1.0.11(postcss@8.4.47)': dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.4.45)': + '@csstools/postcss-gradients-interpolation-method@4.0.20(postcss@8.4.47)': dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-hwb-function@3.0.18(postcss@8.4.45)': + '@csstools/postcss-hwb-function@3.0.18(postcss@8.4.47)': dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-ic-unit@3.0.7(postcss@8.4.45)': + '@csstools/postcss-ic-unit@3.0.7(postcss@8.4.47)': dependencies: - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-initial@1.0.1(postcss@8.4.45)': + '@csstools/postcss-initial@1.0.1(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.4.45)': + '@csstools/postcss-is-pseudo-class@4.0.8(postcss@8.4.47)': dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - '@csstools/postcss-light-dark-function@1.0.8(postcss@8.4.45)': + '@csstools/postcss-light-dark-function@1.0.8(postcss@8.4.47)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.4.45)': + '@csstools/postcss-logical-float-and-clear@2.0.1(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-logical-overflow@1.0.1(postcss@8.4.45)': + '@csstools/postcss-logical-overflow@1.0.1(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.4.45)': + '@csstools/postcss-logical-overscroll-behavior@1.0.1(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-logical-resize@2.0.1(postcss@8.4.45)': + '@csstools/postcss-logical-resize@2.0.1(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.4.45)': + '@csstools/postcss-logical-viewport-units@2.0.11(postcss@8.4.47)': dependencies: '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-media-minmax@1.1.8(postcss@8.4.45)': + '@csstools/postcss-media-minmax@1.1.8(postcss@8.4.47)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.4.45)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.11(postcss@8.4.47)': dependencies: '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-nested-calc@3.0.2(postcss@8.4.45)': + '@csstools/postcss-nested-calc@3.0.2(postcss@8.4.47)': dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.4.45)': + '@csstools/postcss-normalize-display-values@3.0.2(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@3.0.19(postcss@8.4.45)': + '@csstools/postcss-oklab-function@3.0.19(postcss@8.4.47)': dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.4.45)': + '@csstools/postcss-progressive-custom-properties@3.3.0(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.4.45)': + '@csstools/postcss-relative-color-syntax@2.0.19(postcss@8.4.47)': dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.4.45)': + '@csstools/postcss-scope-pseudo-class@3.0.1(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.4.45)': + '@csstools/postcss-stepped-value-functions@3.0.10(postcss@8.4.47)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.4.45)': + '@csstools/postcss-text-decoration-shorthand@3.0.7(postcss@8.4.47)': dependencies: '@csstools/color-helpers': 4.2.1 - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.4.45)': + '@csstools/postcss-trigonometric-functions@3.0.10(postcss@8.4.47)': dependencies: '@csstools/css-calc': 1.2.4(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.45 + postcss: 8.4.47 - '@csstools/postcss-unset-value@3.0.1(postcss@8.4.45)': + '@csstools/postcss-unset-value@3.0.1(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 '@csstools/selector-resolve-nested@1.1.0(postcss-selector-parser@6.1.2)': dependencies: @@ -5143,9 +5169,9 @@ snapshots: dependencies: postcss-selector-parser: 6.1.2 - '@csstools/utilities@1.0.0(postcss@8.4.45)': + '@csstools/utilities@1.0.0(postcss@8.4.47)': dependencies: - postcss: 8.4.45 + postcss: 8.4.47 '@drizzle-team/brocli@0.8.2': {} @@ -5664,9 +5690,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': + '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4) lucia: 3.2.0 '@lukeed/csprng@1.1.0': {} @@ -5888,9 +5914,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.47.0': + '@playwright/test@1.47.1': dependencies: - playwright: 1.47.0 + playwright: 1.47.1 '@polka/url@1.0.0-next.25': {} @@ -6063,41 +6089,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))': dependencies: - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.21.2) '@rollup/plugin-json': 6.1.0(rollup@4.21.2) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.21.2) - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) rollup: 4.21.2 - '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))': dependencies: - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) '@vercel/nft': 0.27.4 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.4(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/enhanced-img@0.3.7(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))': dependencies: magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) vite-imagetools: 7.0.4(rollup@4.21.2) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6111,28 +6137,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) debug: 4.3.6 svelte: 5.0.0-next.175 - vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) debug: 4.3.6 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) - vitefu: 0.2.5(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) + vitefu: 0.2.5(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) transitivePeerDependencies: - supports-color @@ -6166,13 +6192,13 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/pg@8.11.6': + '@types/pg@8.11.10': dependencies: '@types/node': 20.16.5 pg-protocol: 1.6.1 pg-types: 4.0.2 - '@types/pg@8.11.9': + '@types/pg@8.11.6': dependencies: '@types/node': 20.16.5 pg-protocol: 1.6.1 @@ -6185,6 +6211,20 @@ snapshots: '@types/validator@13.12.1': optional: true + '@typeschema/class-validator@0.2.0(@types/json-schema@7.0.15)(class-validator@0.14.1)': + dependencies: + '@typeschema/core': 0.14.0(@types/json-schema@7.0.15) + optionalDependencies: + class-validator: 0.14.1 + transitivePeerDependencies: + - '@types/json-schema' + optional: true + + '@typeschema/core@0.14.0(@types/json-schema@7.0.15)': + optionalDependencies: + '@types/json-schema': 7.0.15 + optional: true + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.0 @@ -6428,14 +6468,14 @@ snapshots: async-sema@3.1.1: {} - autoprefixer@10.4.20(postcss@8.4.45): + autoprefixer@10.4.20(postcss@8.4.47): dependencies: browserslist: 4.23.3 caniuse-lite: 1.0.30001655 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.0 - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 axobject-query@4.1.0: {} @@ -6578,6 +6618,13 @@ snapshots: chownr@2.0.0: {} + class-validator@0.14.1: + dependencies: + '@types/validator': 13.12.1 + libphonenumber-js: 1.11.8 + validator: 13.12.0 + optional: true + class-variance-authority@0.7.0: dependencies: clsx: 2.0.0 @@ -6658,25 +6705,25 @@ snapshots: css-background-parser@0.1.0: {} - css-blank-pseudo@6.0.2(postcss@8.4.45): + css-blank-pseudo@6.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 css-box-shadow@1.0.0-3: {} css-color-keywords@1.0.0: {} - css-has-pseudo@6.0.5(postcss@8.4.45): + css-has-pseudo@6.0.5(postcss@8.4.47): dependencies: '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - css-prefers-color-scheme@9.0.1(postcss@8.4.45): + css-prefers-color-scheme@9.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 css-to-react-native@3.2.0: dependencies: @@ -6687,7 +6734,7 @@ snapshots: css-tree@2.3.1: dependencies: mdn-data: 2.0.30 - source-map-js: 1.2.0 + source-map-js: 1.2.1 cssdb@8.1.0: {} @@ -6769,16 +6816,16 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4): + drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4): optionalDependencies: '@neondatabase/serverless': 0.9.5 - '@types/pg': 8.11.9 + '@types/pg': 8.11.10 pg: 8.12.0 postgres: 3.4.4 - drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): + drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.9)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4) zod: 3.23.8 eastasianwidth@0.2.0: {} @@ -6945,9 +6992,9 @@ snapshots: eslint-compat-utils: 0.5.1(eslint@8.57.0) esutils: 2.0.3 known-css-properties: 0.30.0 - postcss: 8.4.45 - postcss-load-config: 3.1.4(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) - postcss-safe-parser: 6.0.0(postcss@8.4.45) + postcss: 8.4.47 + postcss-load-config: 3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) + postcss-safe-parser: 6.0.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 semver: 7.6.3 svelte-eslint-parser: 0.41.0(svelte@5.0.0-next.175) @@ -7170,11 +7217,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) + sveltekit-superforms: 2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7488,6 +7535,9 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libphonenumber-js@1.11.8: + optional: true + lilconfig@2.1.0: {} lilconfig@3.1.2: {} @@ -7884,281 +7934,281 @@ snapshots: mlly: 1.7.1 pathe: 1.1.2 - playwright-core@1.47.0: {} + playwright-core@1.47.1: {} - playwright@1.47.0: + playwright@1.47.1: dependencies: - playwright-core: 1.47.0 + playwright-core: 1.47.1 optionalDependencies: fsevents: 2.3.2 pngjs@5.0.0: {} - postcss-attribute-case-insensitive@6.0.3(postcss@8.4.45): + postcss-attribute-case-insensitive@6.0.3(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-clamp@4.1.0(postcss@8.4.45): + postcss-clamp@4.1.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@6.0.14(postcss@8.4.45): + postcss-color-functional-notation@6.0.14(postcss@8.4.47): dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - postcss-color-hex-alpha@9.0.4(postcss@8.4.45): + postcss-color-hex-alpha@9.0.4(postcss@8.4.47): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-color-rebeccapurple@9.0.3(postcss@8.4.45): + postcss-color-rebeccapurple@9.0.3(postcss@8.4.47): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-custom-media@10.0.8(postcss@8.4.45): + postcss-custom-media@10.0.8(postcss@8.4.47): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - postcss: 8.4.45 + postcss: 8.4.47 - postcss-custom-properties@13.3.12(postcss@8.4.45): + postcss-custom-properties@13.3.12(postcss@8.4.47): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-custom-selectors@7.1.12(postcss@8.4.45): + postcss-custom-selectors@7.1.12(postcss@8.4.47): dependencies: '@csstools/cascade-layer-name-parser': 1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-dir-pseudo-class@8.0.1(postcss@8.4.45): + postcss-dir-pseudo-class@8.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-double-position-gradients@5.0.7(postcss@8.4.45): + postcss-double-position-gradients@5.0.7(postcss@8.4.47): dependencies: - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-focus-visible@9.0.1(postcss@8.4.45): + postcss-focus-visible@9.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-focus-within@8.0.1(postcss@8.4.45): + postcss-focus-within@8.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-font-variant@5.0.0(postcss@8.4.45): + postcss-font-variant@5.0.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - postcss-gap-properties@5.0.1(postcss@8.4.45): + postcss-gap-properties@5.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - postcss-image-set-function@6.0.3(postcss@8.4.45): + postcss-image-set-function@6.0.3(postcss@8.4.47): dependencies: - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-import@15.1.0(postcss@8.4.45): + postcss-import@15.1.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-import@16.1.0(postcss@8.4.45): + postcss-import@16.1.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.45): + postcss-js@4.0.1(postcss@8.4.47): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.45 + postcss: 8.4.47 - postcss-lab-function@6.0.19(postcss@8.4.45): + postcss-lab-function@6.0.19(postcss@8.4.47): dependencies: '@csstools/css-color-parser': 2.0.5(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) '@csstools/css-tokenizer': 2.4.1 - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/utilities': 1.0.0(postcss@8.4.45) - postcss: 8.4.45 + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/utilities': 1.0.0(postcss@8.4.47) + postcss: 8.4.47 - postcss-load-config@3.1.4(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): + postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.45 + postcss: 8.4.47 ts-node: 10.9.2(@types/node@20.16.5)(typescript@5.6.2) - postcss-load-config@4.0.2(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): + postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): dependencies: lilconfig: 3.1.2 yaml: 2.5.1 optionalDependencies: - postcss: 8.4.45 + postcss: 8.4.47 ts-node: 10.9.2(@types/node@20.16.5)(typescript@5.6.2) - postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1): + postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1): dependencies: lilconfig: 3.1.2 yaml: 2.5.1 optionalDependencies: jiti: 1.21.6 - postcss: 8.4.45 + postcss: 8.4.47 tsx: 4.19.1 - postcss-logical@7.0.1(postcss@8.4.45): + postcss-logical@7.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-nested@6.2.0(postcss@8.4.45): + postcss-nested@6.2.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-nesting@12.1.5(postcss@8.4.45): + postcss-nesting@12.1.5(postcss@8.4.47): dependencies: '@csstools/selector-resolve-nested': 1.1.0(postcss-selector-parser@6.1.2) '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.2) - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-opacity-percentage@2.0.0(postcss@8.4.45): + postcss-opacity-percentage@2.0.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - postcss-overflow-shorthand@5.0.1(postcss@8.4.45): + postcss-overflow-shorthand@5.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-page-break@3.0.4(postcss@8.4.45): + postcss-page-break@3.0.4(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - postcss-place@9.0.1(postcss@8.4.45): + postcss-place@9.0.1(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-preset-env@9.6.0(postcss@8.4.45): + postcss-preset-env@9.6.0(postcss@8.4.47): dependencies: - '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.4.45) - '@csstools/postcss-color-function': 3.0.19(postcss@8.4.45) - '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.4.45) - '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.4.45) - '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.4.45) - '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.45) - '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.4.45) - '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.4.45) - '@csstools/postcss-hwb-function': 3.0.18(postcss@8.4.45) - '@csstools/postcss-ic-unit': 3.0.7(postcss@8.4.45) - '@csstools/postcss-initial': 1.0.1(postcss@8.4.45) - '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.4.45) - '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.4.45) - '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.45) - '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.45) - '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.45) - '@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.45) - '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.4.45) - '@csstools/postcss-media-minmax': 1.1.8(postcss@8.4.45) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.4.45) - '@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.45) - '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.45) - '@csstools/postcss-oklab-function': 3.0.19(postcss@8.4.45) - '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.45) - '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.4.45) - '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.45) - '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.4.45) - '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.4.45) - '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.4.45) - '@csstools/postcss-unset-value': 3.0.1(postcss@8.4.45) - autoprefixer: 10.4.20(postcss@8.4.45) + '@csstools/postcss-cascade-layers': 4.0.6(postcss@8.4.47) + '@csstools/postcss-color-function': 3.0.19(postcss@8.4.47) + '@csstools/postcss-color-mix-function': 2.0.19(postcss@8.4.47) + '@csstools/postcss-content-alt-text': 1.0.0(postcss@8.4.47) + '@csstools/postcss-exponential-functions': 1.0.9(postcss@8.4.47) + '@csstools/postcss-font-format-keywords': 3.0.2(postcss@8.4.47) + '@csstools/postcss-gamut-mapping': 1.0.11(postcss@8.4.47) + '@csstools/postcss-gradients-interpolation-method': 4.0.20(postcss@8.4.47) + '@csstools/postcss-hwb-function': 3.0.18(postcss@8.4.47) + '@csstools/postcss-ic-unit': 3.0.7(postcss@8.4.47) + '@csstools/postcss-initial': 1.0.1(postcss@8.4.47) + '@csstools/postcss-is-pseudo-class': 4.0.8(postcss@8.4.47) + '@csstools/postcss-light-dark-function': 1.0.8(postcss@8.4.47) + '@csstools/postcss-logical-float-and-clear': 2.0.1(postcss@8.4.47) + '@csstools/postcss-logical-overflow': 1.0.1(postcss@8.4.47) + '@csstools/postcss-logical-overscroll-behavior': 1.0.1(postcss@8.4.47) + '@csstools/postcss-logical-resize': 2.0.1(postcss@8.4.47) + '@csstools/postcss-logical-viewport-units': 2.0.11(postcss@8.4.47) + '@csstools/postcss-media-minmax': 1.1.8(postcss@8.4.47) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.11(postcss@8.4.47) + '@csstools/postcss-nested-calc': 3.0.2(postcss@8.4.47) + '@csstools/postcss-normalize-display-values': 3.0.2(postcss@8.4.47) + '@csstools/postcss-oklab-function': 3.0.19(postcss@8.4.47) + '@csstools/postcss-progressive-custom-properties': 3.3.0(postcss@8.4.47) + '@csstools/postcss-relative-color-syntax': 2.0.19(postcss@8.4.47) + '@csstools/postcss-scope-pseudo-class': 3.0.1(postcss@8.4.47) + '@csstools/postcss-stepped-value-functions': 3.0.10(postcss@8.4.47) + '@csstools/postcss-text-decoration-shorthand': 3.0.7(postcss@8.4.47) + '@csstools/postcss-trigonometric-functions': 3.0.10(postcss@8.4.47) + '@csstools/postcss-unset-value': 3.0.1(postcss@8.4.47) + autoprefixer: 10.4.20(postcss@8.4.47) browserslist: 4.23.3 - css-blank-pseudo: 6.0.2(postcss@8.4.45) - css-has-pseudo: 6.0.5(postcss@8.4.45) - css-prefers-color-scheme: 9.0.1(postcss@8.4.45) + css-blank-pseudo: 6.0.2(postcss@8.4.47) + css-has-pseudo: 6.0.5(postcss@8.4.47) + css-prefers-color-scheme: 9.0.1(postcss@8.4.47) cssdb: 8.1.0 - postcss: 8.4.45 - postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.45) - postcss-clamp: 4.1.0(postcss@8.4.45) - postcss-color-functional-notation: 6.0.14(postcss@8.4.45) - postcss-color-hex-alpha: 9.0.4(postcss@8.4.45) - postcss-color-rebeccapurple: 9.0.3(postcss@8.4.45) - postcss-custom-media: 10.0.8(postcss@8.4.45) - postcss-custom-properties: 13.3.12(postcss@8.4.45) - postcss-custom-selectors: 7.1.12(postcss@8.4.45) - postcss-dir-pseudo-class: 8.0.1(postcss@8.4.45) - postcss-double-position-gradients: 5.0.7(postcss@8.4.45) - postcss-focus-visible: 9.0.1(postcss@8.4.45) - postcss-focus-within: 8.0.1(postcss@8.4.45) - postcss-font-variant: 5.0.0(postcss@8.4.45) - postcss-gap-properties: 5.0.1(postcss@8.4.45) - postcss-image-set-function: 6.0.3(postcss@8.4.45) - postcss-lab-function: 6.0.19(postcss@8.4.45) - postcss-logical: 7.0.1(postcss@8.4.45) - postcss-nesting: 12.1.5(postcss@8.4.45) - postcss-opacity-percentage: 2.0.0(postcss@8.4.45) - postcss-overflow-shorthand: 5.0.1(postcss@8.4.45) - postcss-page-break: 3.0.4(postcss@8.4.45) - postcss-place: 9.0.1(postcss@8.4.45) - postcss-pseudo-class-any-link: 9.0.2(postcss@8.4.45) - postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.45) - postcss-selector-not: 7.0.2(postcss@8.4.45) + postcss: 8.4.47 + postcss-attribute-case-insensitive: 6.0.3(postcss@8.4.47) + postcss-clamp: 4.1.0(postcss@8.4.47) + postcss-color-functional-notation: 6.0.14(postcss@8.4.47) + postcss-color-hex-alpha: 9.0.4(postcss@8.4.47) + postcss-color-rebeccapurple: 9.0.3(postcss@8.4.47) + postcss-custom-media: 10.0.8(postcss@8.4.47) + postcss-custom-properties: 13.3.12(postcss@8.4.47) + postcss-custom-selectors: 7.1.12(postcss@8.4.47) + postcss-dir-pseudo-class: 8.0.1(postcss@8.4.47) + postcss-double-position-gradients: 5.0.7(postcss@8.4.47) + postcss-focus-visible: 9.0.1(postcss@8.4.47) + postcss-focus-within: 8.0.1(postcss@8.4.47) + postcss-font-variant: 5.0.0(postcss@8.4.47) + postcss-gap-properties: 5.0.1(postcss@8.4.47) + postcss-image-set-function: 6.0.3(postcss@8.4.47) + postcss-lab-function: 6.0.19(postcss@8.4.47) + postcss-logical: 7.0.1(postcss@8.4.47) + postcss-nesting: 12.1.5(postcss@8.4.47) + postcss-opacity-percentage: 2.0.0(postcss@8.4.47) + postcss-overflow-shorthand: 5.0.1(postcss@8.4.47) + postcss-page-break: 3.0.4(postcss@8.4.47) + postcss-place: 9.0.1(postcss@8.4.47) + postcss-pseudo-class-any-link: 9.0.2(postcss@8.4.47) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.47) + postcss-selector-not: 7.0.2(postcss@8.4.47) - postcss-pseudo-class-any-link@9.0.2(postcss@8.4.45): + postcss-pseudo-class-any-link@9.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-replace-overflow-wrap@4.0.0(postcss@8.4.45): + postcss-replace-overflow-wrap@4.0.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - postcss-safe-parser@6.0.0(postcss@8.4.45): + postcss-safe-parser@6.0.0(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - postcss-scss@4.0.9(postcss@8.4.45): + postcss-scss@4.0.9(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 - postcss-selector-not@7.0.2(postcss@8.4.45): + postcss-selector-not@7.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.45 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 postcss-selector-parser@6.1.2: @@ -8168,11 +8218,11 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.4.45: + postcss@8.4.47: dependencies: nanoid: 3.3.7 picocolors: 1.1.0 - source-map-js: 1.2.0 + source-map-js: 1.2.1 postgres-array@2.0.0: {} @@ -8502,6 +8552,8 @@ snapshots: source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -8578,14 +8630,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175): + svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.1.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2) + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2) typescript: 5.6.2 transitivePeerDependencies: - '@babel/core' @@ -8603,8 +8655,8 @@ snapshots: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - postcss: 8.4.45 - postcss-scss: 4.0.9(postcss@8.4.45) + postcss: 8.4.47 + postcss-scss: 4.0.9(postcss@8.4.47) optionalDependencies: svelte: 5.0.0-next.175 @@ -8641,7 +8693,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8650,17 +8702,17 @@ snapshots: strip-indent: 3.0.0 svelte: 5.0.0-next.175 optionalDependencies: - postcss: 8.4.45 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1) + postcss: 8.4.47 + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1) sass: 1.78.0 typescript: 5.6.2 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1))(postcss@8.4.45)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2): dependencies: svelte: 5.0.0-next.175 optionalDependencies: - postcss: 8.4.45 - postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.45)(tsx@4.19.1) + postcss: 8.4.47 + postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1) sass: 1.78.0 typescript: 5.6.2 @@ -8715,19 +8767,19 @@ snapshots: magic-string: 0.30.11 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) - sveltekit-superforms@2.17.0(@sveltejs/kit@2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175): + sveltekit-superforms@2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.26(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -8738,8 +8790,10 @@ snapshots: '@gcornut/valibot-json-schema': 0.31.0 '@sinclair/typebox': 0.32.35 '@sodaru/yup-to-json-schema': 2.0.1 + '@typeschema/class-validator': 0.2.0(@types/json-schema@7.0.15)(class-validator@0.14.1) '@vinejs/vine': 1.8.0 arktype: 2.0.0-beta.0 + class-validator: 0.14.1 joi: 17.13.3 json-schema-to-ts: 3.1.1 superstruct: 2.0.2 @@ -8747,6 +8801,8 @@ snapshots: yup: 1.4.0 zod: 3.23.8 zod-to-json-schema: 3.23.3(zod@3.23.8) + transitivePeerDependencies: + - '@types/json-schema' tabbable@6.2.0: {} @@ -8777,11 +8833,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.0 - postcss: 8.4.45 - postcss-import: 15.1.0(postcss@8.4.45) - postcss-js: 4.0.1(postcss@8.4.45) - postcss-load-config: 4.0.2(postcss@8.4.45)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) - postcss-nested: 6.2.0(postcss@8.4.45) + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) + postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 resolve: 1.22.8 sucrase: 3.35.0 @@ -8964,7 +9020,7 @@ snapshots: debug: 4.3.6 pathe: 1.1.2 picocolors: 1.1.0 - vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) transitivePeerDependencies: - '@types/node' - less @@ -8976,19 +9032,19 @@ snapshots: - supports-color - terser - vite@5.4.4(@types/node@20.16.5)(sass@1.78.0): + vite@5.4.5(@types/node@20.16.5)(sass@1.78.0): dependencies: esbuild: 0.21.5 - postcss: 8.4.45 + postcss: 8.4.47 rollup: 4.21.2 optionalDependencies: '@types/node': 20.16.5 fsevents: 2.3.3 sass: 1.78.0 - vitefu@0.2.5(vite@5.4.4(@types/node@20.16.5)(sass@1.78.0)): + vitefu@0.2.5(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)): optionalDependencies: - vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) vitest@1.6.0(@types/node@20.16.5)(sass@1.78.0): dependencies: @@ -9009,7 +9065,7 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.4(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) vite-node: 1.6.0(@types/node@20.16.5)(sass@1.78.0) why-is-node-running: 2.3.0 optionalDependencies: diff --git a/src/lib/server/api/common/env.ts b/src/lib/server/api/common/env.ts index 17796e8..95b35c4 100644 --- a/src/lib/server/api/common/env.ts +++ b/src/lib/server/api/common/env.ts @@ -17,6 +17,8 @@ const EnvSchema = z.object({ DATABASE_DB: z.string(), DB_MIGRATING: stringBoolean, DB_SEEDING: stringBoolean, + GITHUB_CLIENT_ID: z.string(), + GITHUB_CLIENT_SECRET: z.string(), NODE_ENV: z.string().default('development'), ORIGIN: z.string(), PUBLIC_SITE_NAME: z.string(), diff --git a/src/lib/server/api/controllers/collection.controller.ts b/src/lib/server/api/controllers/collection.controller.ts index 86dfcc5..5cc94cd 100644 --- a/src/lib/server/api/controllers/collection.controller.ts +++ b/src/lib/server/api/controllers/collection.controller.ts @@ -18,6 +18,11 @@ export class CollectionController extends Controller { console.log('collections service', collections) return c.json({ collections }) }) + .get('/count', requireAuth, async (c) => { + const user = c.var.user + const collections = await this.collectionsService.findAllByUserIdWithDetails(user.id) + return c.json({ collections }) + }) .get('/:cuid', requireAuth, async (c) => { const cuid = c.req.param('cuid') const collection = await this.collectionsService.findOneByCuid(cuid) diff --git a/src/lib/server/api/controllers/oauth.controller.ts b/src/lib/server/api/controllers/oauth.controller.ts new file mode 100644 index 0000000..4b1bcb4 --- /dev/null +++ b/src/lib/server/api/controllers/oauth.controller.ts @@ -0,0 +1,55 @@ +import 'reflect-metadata' +import { Controller } from '$lib/server/api/common/types/controller' +import { LuciaService } from '$lib/server/api/services/lucia.service' +import { OAuthService } from '$lib/server/api/services/oauth.service' +import { github } from '$lib/server/auth' +import { OAuth2RequestError } from 'arctic' +import { getCookie } from 'hono/cookie' +import { inject, injectable } from 'tsyringe' + +@injectable() +export class OAuthController extends Controller { + constructor( + @inject(LuciaService) private luciaService: LuciaService, + @inject(OAuthService) private readonly oauthService: OAuthService) { + super() + } + + routes() { + return this.controller + .get('/github', async (c) => { + try { + const code = c.req.query('code')?.toString() ?? null + const state = c.req.query('state')?.toString() ?? null + const storedState = getCookie(c).github_oauth_state ?? null + + if (!code || !state || !storedState || state !== storedState) { + return c.body(null, 400) + } + + const tokens = await github.validateAuthorizationCode(code) + const githubUserResponse = await fetch("https://api.github.com/user", { + headers: { + Authorization: `Bearer ${tokens.accessToken}` + } + }); + const githubUser: GitHubUser = await githubUserResponse.json(); + + const token = await this.oauthService.handleOAuthUser(githubUser.id, githubUser.login, 'github') + return c.json({ token }) + } catch (error) { + // the specific error message depends on the provider + if (error instanceof OAuth2RequestError) { + // invalid code + return c.body(null, 400) + } + return c.body(null, 500) + } + }) + } +} + +interface GitHubUser { + id: number; + login: string; +} \ No newline at end of file diff --git a/src/lib/server/api/databases/tables/collections.table.ts b/src/lib/server/api/databases/tables/collections.table.ts index 715bf4d..4b483df 100644 --- a/src/lib/server/api/databases/tables/collections.table.ts +++ b/src/lib/server/api/databases/tables/collections.table.ts @@ -3,6 +3,7 @@ import { type InferSelectModel, relations } from 'drizzle-orm' import { pgTable, text, uuid } from 'drizzle-orm/pg-core' import { timestamps } from '../../common/utils/table' import { usersTable } from './users.table' +import { collection_items } from './collectionItems.table' export const collections = pgTable('collections', { id: uuid('id').primaryKey().defaultRandom(), @@ -16,11 +17,12 @@ export const collections = pgTable('collections', { ...timestamps, }) -export const collection_relations = relations(collections, ({ one }) => ({ +export const collection_relations = relations(collections, ({ one, many }) => ({ user: one(usersTable, { fields: [collections.user_id], references: [usersTable.id], }), + collection_items: many(collection_items), })) export type Collections = InferSelectModel diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index 8dd5adb..f39f32a 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -44,6 +44,7 @@ const routes = app .route('/me', container.resolve(IamController).routes()) .route('/user', container.resolve(UserController).routes()) .route('/login', container.resolve(LoginController).routes()) + .route('/oauth', container.resolve(OAuthController).routes()) .route('/signup', container.resolve(SignupController).routes()) .route('/wishlists', container.resolve(WishlistController).routes()) .route('/collections', container.resolve(CollectionController).routes()) diff --git a/src/lib/server/api/repositories/collections.repository.ts b/src/lib/server/api/repositories/collections.repository.ts index c6f097b..bea63ed 100644 --- a/src/lib/server/api/repositories/collections.repository.ts +++ b/src/lib/server/api/repositories/collections.repository.ts @@ -2,7 +2,7 @@ import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository' import { DrizzleService } from '$lib/server/api/services/drizzle.service' import { type InferInsertModel, eq } from 'drizzle-orm' import { inject, injectable } from 'tsyringe' -import { collections } from '../databases/tables' +import { collection_items, collections } from '../databases/tables' export type CreateCollection = InferInsertModel export type UpdateCollection = Partial @@ -51,6 +51,23 @@ export class CollectionsRepository { }) } + async findAllByUserIdWithDetails(userId: string, db = this.drizzle.db) { + return db.query.collections.findMany({ + where: eq(collections.user_id, userId), + columns: { + cuid: true, + name: true, + }, + with: { + collection_items: { + columns: { + cuid: true + } + }, + } + }) + } + async create(data: CreateCollection, db = this.drizzle.db) { return db.insert(collections).values(data).returning().then(takeFirstOrThrow) } diff --git a/src/lib/server/api/repositories/federated_identity.repository.ts b/src/lib/server/api/repositories/federated_identity.repository.ts new file mode 100644 index 0000000..a67009e --- /dev/null +++ b/src/lib/server/api/repositories/federated_identity.repository.ts @@ -0,0 +1,25 @@ +import { inject, injectable } from "tsyringe"; +import { DrizzleService } from "../services/drizzle.service"; +import { and, eq, type InferInsertModel } from "drizzle-orm"; +import { federatedIdentityTable } from "../databases/tables"; +import { takeFirstOrThrow } from "../common/utils/repository"; + +export type CreateFederatedIdentity = InferInsertModel + +@injectable() +export class FederatedIdentityRepository { + + constructor( + @inject(DrizzleService) private readonly drizzle: DrizzleService + ) { } + + async findOneByUserIdAndProvider(userId: string, provider: string) { + return this.drizzle.db.query.federatedIdentityTable.findFirst({ + where: and(eq(federatedIdentityTable.user_id, userId), eq(federatedIdentityTable.identity_provider, provider)) + }) + } + + async create(data: CreateFederatedIdentity, db = this.drizzle.db) { + return db.insert(federatedIdentityTable).values(data).returning().then(takeFirstOrThrow) + } +} \ No newline at end of file diff --git a/src/lib/server/api/services/collections.service.ts b/src/lib/server/api/services/collections.service.ts index 4e0a009..107ccdf 100644 --- a/src/lib/server/api/services/collections.service.ts +++ b/src/lib/server/api/services/collections.service.ts @@ -16,6 +16,10 @@ export class CollectionsService { return this.collectionsRepository.findAllByUserId(userId); } + async findAllByUserIdWithDetails(userId: string) { + return this.collectionsRepository.findAllByUserIdWithDetails(userId); + } + async findOneById(id: string) { return this.collectionsRepository.findOneById(id); } diff --git a/src/lib/server/api/services/oauth.service.ts b/src/lib/server/api/services/oauth.service.ts new file mode 100644 index 0000000..4432792 --- /dev/null +++ b/src/lib/server/api/services/oauth.service.ts @@ -0,0 +1,54 @@ +import { github } from "$lib/server/auth"; +import { db } from "$lib/server/db"; +import { lucia } from "$lib/server/lucia"; +import type { RequestEvent } from "@sveltejs/kit"; +import { OAuth2RequestError } from "arctic"; +import { generateIdFromEntropySize } from "lucia"; +import { inject, injectable } from "tsyringe"; +import { UsersService } from "./users.service"; +import { FederatedIdentityRepository } from "../repositories/federated_identity.repository"; + +@inejectable() +export class OAuthService { + constructor( + @inject(FederatedIdentityRepository) private readonly federatedIdentityRepository: FederatedIdentityRepository, + @inject(UsersService) private readonly usersService: UsersService + ) {} + + async handleOAuthUser(oauthUserId: number, oauthUsername: string, oauthProvider: string) { + const existingUser = await this.federatedIdentityRepository.findOneByUserIdAndProvider(`${oauthUserId}`, oauthProvider) + + if (existingUser) { + return existingUser.id + } else { + const userId = generateIdFromEntropySize(10); // 16 characters long + + const user = await this.drizzleService.db.transaction(async (trx) => { + const user = await this.usersService.create({ + id: userId, + username: oauthUsername + }, trx); + + await this.federatedIdentityRepository.create({ + identity_provider: oauthProvider, + user_id: userId, + identity_id: `${oauthUserId}` + }, trx); + return user; + }) + + const session = await lucia.createSession(userId, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + event.cookies.set(sessionCookie.name, sessionCookie.value, { + path: ".", + ...sessionCookie.attributes + }); + } + return new Response(null, { + status: 302, + headers: { + Location: "/" + } + }); + } +} \ No newline at end of file diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts new file mode 100644 index 0000000..bf0d9aa --- /dev/null +++ b/src/lib/server/auth.ts @@ -0,0 +1,4 @@ +import env from "$lib/server/api/common/env"; +import { GitHub } from "arctic"; + +export const github = new GitHub(env.GITHUB_CLIENT_ID, env.GITHUB_CLIENT_SECRET); diff --git a/src/routes/(app)/(protected)/collections/+page.server.ts b/src/routes/(app)/(protected)/collections/+page.server.ts index 7dc3e2e..881f050 100644 --- a/src/routes/(app)/(protected)/collections/+page.server.ts +++ b/src/routes/(app)/(protected)/collections/+page.server.ts @@ -1,13 +1,12 @@ import { notSignedInMessage } from '$lib/flashMessages' +import { collection_items, collections, gamesTable } from '$lib/server/api/databases/tables' import { db } from '$lib/server/api/packages/drizzle' -import { userNotAuthenticated } from '$lib/server/auth-utils' import { modifyListGameSchema } from '$lib/validations/zod-schemas' import { type Actions, error, fail } from '@sveltejs/kit' import { and, eq } from 'drizzle-orm' import { redirect } from 'sveltekit-flash-message/server' import { zod } from 'sveltekit-superforms/adapters' import { superValidate } from 'sveltekit-superforms/server' -import { collection_items, collections, gamesTable } from '../../../../lib/server/api/databases/tables' export async function load(event) { const { locals } = event @@ -18,23 +17,10 @@ export async function load(event) { } try { - const userCollections = await db.query.collections.findMany({ - columns: { - cuid: true, - name: true, - created_at: true, - }, - where: eq(collections.user_id, authedUser.id), - }) - console.log('collections', userCollections) - - if (userCollections?.length === 0) { - console.log('Collection was not found') - return fail(404, {}) - } + const { data, error } = await locals.api.collections.$get().then(locals.parseApiResponse) return { - collections: userCollections, + collections: data?.collections || [], } } catch (e) { console.error(e) diff --git a/src/routes/(app)/(protected)/collections/+page.svelte b/src/routes/(app)/(protected)/collections/+page.svelte index 81f993f..6d600b9 100644 --- a/src/routes/(app)/(protected)/collections/+page.svelte +++ b/src/routes/(app)/(protected)/collections/+page.svelte @@ -1,24 +1,34 @@ Your Collections | Bored Game +

Your Collections

-
{#if collections.length === 0}

You have no collections

{:else} {#each collections as collection} -
+ + + {collection.name} + + +

Number of items:

+

Created at: {new Date(collection.createdAt).toLocaleString()}

+
+
+ {/each} {/if}
@@ -30,10 +40,6 @@ width: 100%; } - .collections { - margin: 2rem 0; - } - .collection-list { display: grid; grid-template-columns: repeat(3, minmax(200px, 1fr)); diff --git a/src/routes/(auth)/auth/callback/github/+server.ts b/src/routes/(auth)/auth/callback/github/+server.ts new file mode 100644 index 0000000..c58847d --- /dev/null +++ b/src/routes/(auth)/auth/callback/github/+server.ts @@ -0,0 +1,21 @@ +import { github } from "$lib/server/auth"; +import { OAuth2RequestError } from "arctic"; +import { generateIdFromEntropySize } from "lucia"; + +import type { RequestEvent } from "@sveltejs/kit"; + +export async function GET(event: RequestEvent): Promise { + const code = event.url.searchParams.get('code') + const state = event.url.searchParams.get('state') + const { data, error } = await locals.api.oauth.$get({ + params: { + code, + state + } + }) +} + +interface GitHubUser { + id: number; + login: string; +} \ No newline at end of file diff --git a/src/routes/(auth)/login/github/+server.ts b/src/routes/(auth)/login/github/+server.ts new file mode 100644 index 0000000..a93f2f8 --- /dev/null +++ b/src/routes/(auth)/login/github/+server.ts @@ -0,0 +1,18 @@ +import { github } from '$lib/server/auth' +import { redirect } from '@sveltejs/kit' +import { generateState } from 'arctic' + +export async function GET(event) { + const state = generateState(); + const url = await github.createAuthorizationURL(state) + + event.cookies.set('github_state', state, { + path: '/', + secure: import.meta.env.PROD, + httpOnly: true, + maxAge: 60 * 10, + sameSite: 'lax' + }); + + redirect(302, url.toString()) +} \ No newline at end of file From a0b01e5adebad55156bea962431af40fdea434eb Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 17 Sep 2024 17:32:26 -0700 Subject: [PATCH 06/14] Fixing OAuth flows, passing code and state correctly to hono, and signing in with GitHub. --- .../api/controllers/oauth.controller.ts | 85 ++++++++++------- src/lib/server/api/index.ts | 1 + .../repositories/collections.repository.ts | 8 +- .../repositories/credentials.repository.ts | 2 +- .../federated_identity.repository.ts | 25 ++--- .../api/repositories/roles.repository.ts | 10 +- .../api/repositories/user_roles.repository.ts | 4 +- .../api/services/collections.service.ts | 47 ++++++---- src/lib/server/api/services/oauth.service.ts | 60 ++++-------- .../server/api/services/user_roles.service.ts | 48 ++++++---- src/lib/server/api/services/users.service.ts | 91 ++++++++++++++----- .../server/api/services/wishlists.service.ts | 43 +++++---- .../(auth)/auth/callback/github/+server.ts | 42 +++++---- src/routes/(auth)/login/+page.server.ts | 3 + src/routes/(auth)/login/+page.svelte | 69 +++++++------- src/routes/(auth)/login/github/+server.ts | 14 +-- 16 files changed, 319 insertions(+), 233 deletions(-) diff --git a/src/lib/server/api/controllers/oauth.controller.ts b/src/lib/server/api/controllers/oauth.controller.ts index 4b1bcb4..570f42b 100644 --- a/src/lib/server/api/controllers/oauth.controller.ts +++ b/src/lib/server/api/controllers/oauth.controller.ts @@ -4,52 +4,73 @@ import { LuciaService } from '$lib/server/api/services/lucia.service' import { OAuthService } from '$lib/server/api/services/oauth.service' import { github } from '$lib/server/auth' import { OAuth2RequestError } from 'arctic' -import { getCookie } from 'hono/cookie' +import { getCookie, setCookie } from 'hono/cookie' +import { TimeSpan } from 'oslo' import { inject, injectable } from 'tsyringe' @injectable() export class OAuthController extends Controller { constructor( @inject(LuciaService) private luciaService: LuciaService, - @inject(OAuthService) private readonly oauthService: OAuthService) { + @inject(OAuthService) private oauthService: OAuthService, + ) { super() } routes() { - return this.controller - .get('/github', async (c) => { - try { - const code = c.req.query('code')?.toString() ?? null - const state = c.req.query('state')?.toString() ?? null - const storedState = getCookie(c).github_oauth_state ?? null + return this.controller.get('/github', async (c) => { + try { + const code = c.req.query('code')?.toString() ?? null + const state = c.req.query('state')?.toString() ?? null + const storedState = getCookie(c).github_oauth_state ?? null - if (!code || !state || !storedState || state !== storedState) { - return c.body(null, 400) - } + console.log('code', code, 'state', state, 'storedState', storedState) - const tokens = await github.validateAuthorizationCode(code) - const githubUserResponse = await fetch("https://api.github.com/user", { - headers: { - Authorization: `Bearer ${tokens.accessToken}` - } - }); - const githubUser: GitHubUser = await githubUserResponse.json(); - - const token = await this.oauthService.handleOAuthUser(githubUser.id, githubUser.login, 'github') - return c.json({ token }) - } catch (error) { - // the specific error message depends on the provider - if (error instanceof OAuth2RequestError) { - // invalid code - return c.body(null, 400) - } - return c.body(null, 500) + if (!code || !state || !storedState || state !== storedState) { + return c.body(null, 400) } - }) + + const tokens = await github.validateAuthorizationCode(code) + const githubUserResponse = await fetch('https://api.github.com/user', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + const githubUser: GitHubUser = await githubUserResponse.json() + + const userId = await this.oauthService.handleOAuthUser(githubUser.id, githubUser.login, 'github') + + const session = await this.luciaService.lucia.createSession(userId, {}) + const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id) + + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: + sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() + ? sessionCookie.attributes.maxAge + : new TimeSpan(2, 'w').seconds(), + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires, + }) + + return c.json({ message: 'ok' }) + } catch (error) { + console.error(error) + // the specific error message depends on the provider + if (error instanceof OAuth2RequestError) { + // invalid code + return c.body(null, 400) + } + return c.body(null, 500) + } + }) } } interface GitHubUser { - id: number; - login: string; -} \ No newline at end of file + id: number + login: string +} diff --git a/src/lib/server/api/index.ts b/src/lib/server/api/index.ts index f39f32a..3106d86 100644 --- a/src/lib/server/api/index.ts +++ b/src/lib/server/api/index.ts @@ -1,6 +1,7 @@ import 'reflect-metadata' import { CollectionController } from '$lib/server/api/controllers/collection.controller' import { MfaController } from '$lib/server/api/controllers/mfa.controller' +import { OAuthController } from '$lib/server/api/controllers/oauth.controller' import { SignupController } from '$lib/server/api/controllers/signup.controller' import { UserController } from '$lib/server/api/controllers/user.controller' import { WishlistController } from '$lib/server/api/controllers/wishlist.controller' diff --git a/src/lib/server/api/repositories/collections.repository.ts b/src/lib/server/api/repositories/collections.repository.ts index bea63ed..c351714 100644 --- a/src/lib/server/api/repositories/collections.repository.ts +++ b/src/lib/server/api/repositories/collections.repository.ts @@ -2,7 +2,7 @@ import { takeFirstOrThrow } from '$lib/server/api/common/utils/repository' import { DrizzleService } from '$lib/server/api/services/drizzle.service' import { type InferInsertModel, eq } from 'drizzle-orm' import { inject, injectable } from 'tsyringe' -import { collection_items, collections } from '../databases/tables' +import { collections } from '../databases/tables' export type CreateCollection = InferInsertModel export type UpdateCollection = Partial @@ -61,10 +61,10 @@ export class CollectionsRepository { with: { collection_items: { columns: { - cuid: true - } + cuid: true, + }, }, - } + }, }) } diff --git a/src/lib/server/api/repositories/credentials.repository.ts b/src/lib/server/api/repositories/credentials.repository.ts index 0671a40..bdd8de5 100644 --- a/src/lib/server/api/repositories/credentials.repository.ts +++ b/src/lib/server/api/repositories/credentials.repository.ts @@ -44,7 +44,7 @@ export class CredentialsRepository { } async findOneByIdOrThrow(id: string, db = this.drizzle.db) { - const credentials = await this.findOneById(id) + const credentials = await this.findOneById(id, db) if (!credentials) throw Error('Credentials not found') return credentials } diff --git a/src/lib/server/api/repositories/federated_identity.repository.ts b/src/lib/server/api/repositories/federated_identity.repository.ts index a67009e..2180590 100644 --- a/src/lib/server/api/repositories/federated_identity.repository.ts +++ b/src/lib/server/api/repositories/federated_identity.repository.ts @@ -1,25 +1,28 @@ -import { inject, injectable } from "tsyringe"; -import { DrizzleService } from "../services/drizzle.service"; -import { and, eq, type InferInsertModel } from "drizzle-orm"; -import { federatedIdentityTable } from "../databases/tables"; -import { takeFirstOrThrow } from "../common/utils/repository"; +import { type InferInsertModel, and, eq } from 'drizzle-orm' +import { inject, injectable } from 'tsyringe' +import { takeFirstOrThrow } from '../common/utils/repository' +import { federatedIdentityTable } from '../databases/tables' +import { DrizzleService } from '../services/drizzle.service' export type CreateFederatedIdentity = InferInsertModel @injectable() export class FederatedIdentityRepository { - - constructor( - @inject(DrizzleService) private readonly drizzle: DrizzleService - ) { } + constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {} async findOneByUserIdAndProvider(userId: string, provider: string) { return this.drizzle.db.query.federatedIdentityTable.findFirst({ - where: and(eq(federatedIdentityTable.user_id, userId), eq(federatedIdentityTable.identity_provider, provider)) + where: and(eq(federatedIdentityTable.user_id, userId), eq(federatedIdentityTable.identity_provider, provider)), + }) + } + + async findOneByFederatedUserIdAndProvider(federatedUserId: string, provider: string) { + return this.drizzle.db.query.federatedIdentityTable.findFirst({ + where: and(eq(federatedIdentityTable.federated_user_id, federatedUserId), eq(federatedIdentityTable.identity_provider, provider)), }) } async create(data: CreateFederatedIdentity, db = this.drizzle.db) { return db.insert(federatedIdentityTable).values(data).returning().then(takeFirstOrThrow) } -} \ No newline at end of file +} diff --git a/src/lib/server/api/repositories/roles.repository.ts b/src/lib/server/api/repositories/roles.repository.ts index 2750d5c..4d9f283 100644 --- a/src/lib/server/api/repositories/roles.repository.ts +++ b/src/lib/server/api/repositories/roles.repository.ts @@ -28,29 +28,29 @@ export class RolesRepository { constructor(@inject(DrizzleService) private readonly drizzle: DrizzleService) {} async findOneById(id: string, db = this.drizzle.db) { - return db.query.roles.findFirst({ + return db.query.rolesTable.findFirst({ where: eq(rolesTable.id, id), }) } async findOneByIdOrThrow(id: string, db = this.drizzle.db) { - const role = await this.findOneById(id) + const role = await this.findOneById(id, db) if (!role) throw Error('Role not found') return role } async findAll(db = this.drizzle.db) { - return db.query.roles.findMany() + return db.query.rolesTable.findMany() } async findOneByName(name: string, db = this.drizzle.db) { - return db.query.roles.findFirst({ + return db.query.rolesTable.findFirst({ where: eq(rolesTable.name, name), }) } async findOneByNameOrThrow(name: string, db = this.drizzle.db) { - const role = await this.findOneByName(name) + const role = await this.findOneByName(name, db) if (!role) throw Error('Role not found') return role } diff --git a/src/lib/server/api/repositories/user_roles.repository.ts b/src/lib/server/api/repositories/user_roles.repository.ts index 737bc0c..ef95b74 100644 --- a/src/lib/server/api/repositories/user_roles.repository.ts +++ b/src/lib/server/api/repositories/user_roles.repository.ts @@ -33,8 +33,8 @@ export class UserRolesRepository { }) } - async findOneByIdOrThrow(id: string) { - const userRole = await this.findOneById(id) + async findOneByIdOrThrow(id: string, db = this.drizzle.db) { + const userRole = await this.findOneById(id, db) if (!userRole) throw Error('User not found') return userRole } diff --git a/src/lib/server/api/services/collections.service.ts b/src/lib/server/api/services/collections.service.ts index 107ccdf..ccdbcbc 100644 --- a/src/lib/server/api/services/collections.service.ts +++ b/src/lib/server/api/services/collections.service.ts @@ -1,41 +1,50 @@ -import { inject, injectable } from "tsyringe"; -import { generateRandomAnimalName } from "$lib/utils/randomDataUtil"; -import { CollectionsRepository } from "../repositories/collections.repository"; +import type { db } from '$lib/server/api/packages/drizzle' +import { generateRandomAnimalName } from '$lib/utils/randomDataUtil' +import { inject, injectable } from 'tsyringe' +import { CollectionsRepository } from '../repositories/collections.repository' @injectable() export class CollectionsService { - constructor( - @inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository - ) { } + constructor(@inject(CollectionsRepository) private readonly collectionsRepository: CollectionsRepository) {} async findOneByUserId(userId: string) { - return this.collectionsRepository.findOneByUserId(userId); + return this.collectionsRepository.findOneByUserId(userId) } async findAllByUserId(userId: string) { - return this.collectionsRepository.findAllByUserId(userId); + return this.collectionsRepository.findAllByUserId(userId) } async findAllByUserIdWithDetails(userId: string) { - return this.collectionsRepository.findAllByUserIdWithDetails(userId); + return this.collectionsRepository.findAllByUserIdWithDetails(userId) } async findOneById(id: string) { - return this.collectionsRepository.findOneById(id); + return this.collectionsRepository.findOneById(id) } async findOneByCuid(cuid: string) { - return this.collectionsRepository.findOneByCuid(cuid); + return this.collectionsRepository.findOneByCuid(cuid) } - async createEmptyNoName(userId: string) { - return this.createEmpty(userId, null); + async createEmptyNoName(userId: string, trx: Parameters[0]>[0] | null = null) { + return this.createEmpty(userId, null, trx) } - async createEmpty(userId: string, name: string | null) { - return this.collectionsRepository.create({ - user_id: userId, - name: name ?? generateRandomAnimalName(), - }); + async createEmpty(userId: string, name: string | null, trx: Parameters[0]>[0] | null = null) { + if (!trx) { + return this.collectionsRepository.create({ + user_id: userId, + name: name ?? generateRandomAnimalName(), + }) + } + + return this.collectionsRepository.create( + { + user_id: userId, + name: name ?? generateRandomAnimalName(), + }, + trx, + ) } -} \ No newline at end of file +} diff --git a/src/lib/server/api/services/oauth.service.ts b/src/lib/server/api/services/oauth.service.ts index 4432792..0e62baa 100644 --- a/src/lib/server/api/services/oauth.service.ts +++ b/src/lib/server/api/services/oauth.service.ts @@ -1,54 +1,26 @@ -import { github } from "$lib/server/auth"; -import { db } from "$lib/server/db"; -import { lucia } from "$lib/server/lucia"; -import type { RequestEvent } from "@sveltejs/kit"; -import { OAuth2RequestError } from "arctic"; -import { generateIdFromEntropySize } from "lucia"; -import { inject, injectable } from "tsyringe"; -import { UsersService } from "./users.service"; -import { FederatedIdentityRepository } from "../repositories/federated_identity.repository"; +import { inject, injectable } from 'tsyringe' +import { FederatedIdentityRepository } from '../repositories/federated_identity.repository' +import { UsersService } from './users.service' -@inejectable() +@injectable() export class OAuthService { constructor( @inject(FederatedIdentityRepository) private readonly federatedIdentityRepository: FederatedIdentityRepository, - @inject(UsersService) private readonly usersService: UsersService + @inject(UsersService) private readonly usersService: UsersService, ) {} async handleOAuthUser(oauthUserId: number, oauthUsername: string, oauthProvider: string) { - const existingUser = await this.federatedIdentityRepository.findOneByUserIdAndProvider(`${oauthUserId}`, oauthProvider) + const federatedUser = await this.federatedIdentityRepository.findOneByFederatedUserIdAndProvider(`${oauthUserId}`, oauthProvider) - if (existingUser) { - return existingUser.id - } else { - const userId = generateIdFromEntropySize(10); // 16 characters long - - const user = await this.drizzleService.db.transaction(async (trx) => { - const user = await this.usersService.create({ - id: userId, - username: oauthUsername - }, trx); - - await this.federatedIdentityRepository.create({ - identity_provider: oauthProvider, - user_id: userId, - identity_id: `${oauthUserId}` - }, trx); - return user; - }) - - const session = await lucia.createSession(userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - event.cookies.set(sessionCookie.name, sessionCookie.value, { - path: ".", - ...sessionCookie.attributes - }); + if (federatedUser) { + return federatedUser.user_id } - return new Response(null, { - status: 302, - headers: { - Location: "/" - } - }); + + const user = await this.usersService.createOAuthUser(oauthUserId, oauthUsername, oauthProvider) + + if (!user) { + throw new Error('Failed to create user') + } + return user.id } -} \ No newline at end of file +} diff --git a/src/lib/server/api/services/user_roles.service.ts b/src/lib/server/api/services/user_roles.service.ts index 04ad1d0..ea80eff 100644 --- a/src/lib/server/api/services/user_roles.service.ts +++ b/src/lib/server/api/services/user_roles.service.ts @@ -1,39 +1,51 @@ -import {inject, injectable} from "tsyringe"; -import {type CreateUserRole, UserRolesRepository} from "$lib/server/api/repositories/user_roles.repository"; -import {RolesService} from "$lib/server/api/services/roles.service"; +import type { db } from '$lib/server/api/packages/drizzle' +import { type CreateUserRole, UserRolesRepository } from '$lib/server/api/repositories/user_roles.repository' +import { RolesService } from '$lib/server/api/services/roles.service' +import { inject, injectable } from 'tsyringe' @injectable() export class UserRolesService { constructor( - @inject(UserRolesRepository) private readonly userRolesRepository: UserRolesRepository, - @inject(RolesService) private readonly rolesService: RolesService - ) { } + @inject(UserRolesRepository) private readonly userRolesRepository: UserRolesRepository, + @inject(RolesService) private readonly rolesService: RolesService, + ) {} async findOneById(id: string) { - return this.userRolesRepository.findOneById(id); + return this.userRolesRepository.findOneById(id) } async findAllByUserId(userId: string) { - return this.userRolesRepository.findAllByUserId(userId); + return this.userRolesRepository.findAllByUserId(userId) } async create(data: CreateUserRole) { - return this.userRolesRepository.create(data); + return this.userRolesRepository.create(data) } - async addRoleToUser(userId: string, roleName: string, primary = false) { + async addRoleToUser(userId: string, roleName: string, primary = false, trx: Parameters[0]>[0] | null = null) { // Find the role by its name - const role = await this.rolesService.findOneByNameOrThrow(roleName); + const role = await this.rolesService.findOneByNameOrThrow(roleName) if (!role || !role.id) { - throw new Error(`Role with name ${roleName} not found`); + throw new Error(`Role with name ${roleName} not found`) + } + + if (!trx) { + return this.userRolesRepository.create({ + user_id: userId, + role_id: role.id, + primary, + }) } // Create a UserRole entry linking the user and the role - return this.userRolesRepository.create({ - user_id: userId, - role_id: role.id, - primary, - }); + return this.userRolesRepository.create( + { + user_id: userId, + role_id: role.id, + primary, + }, + trx, + ) } -} \ No newline at end of file +} diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index f067809..008a41a 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -1,11 +1,14 @@ import type { SignupUsernameEmailDto } from '$lib/server/api/dtos/signup-username-email.dto' import { CredentialsRepository } from '$lib/server/api/repositories/credentials.repository' +import { FederatedIdentityRepository } from '$lib/server/api/repositories/federated_identity.repository' +import { WishlistsRepository } from '$lib/server/api/repositories/wishlists.repository' import { TokensService } from '$lib/server/api/services/tokens.service' import { UserRolesService } from '$lib/server/api/services/user_roles.service' import { inject, injectable } from 'tsyringe' import { CredentialsType } from '../databases/tables' import { type UpdateUser, UsersRepository } from '../repositories/users.repository' import { CollectionsService } from './collections.service' +import { DrizzleService } from './drizzle.service' import { WishlistsService } from './wishlists.service' @injectable() @@ -13,9 +16,12 @@ export class UsersService { constructor( @inject(CollectionsService) private readonly collectionsService: CollectionsService, @inject(CredentialsRepository) private readonly credentialsRepository: CredentialsRepository, + @inject(DrizzleService) private readonly drizzleService: DrizzleService, + @inject(FederatedIdentityRepository) private readonly federatedIdentityRepository: FederatedIdentityRepository, @inject(TokensService) private readonly tokenService: TokensService, @inject(UsersRepository) private readonly usersRepository: UsersRepository, @inject(UserRolesService) private readonly userRolesService: UserRolesService, + @inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository, @inject(WishlistsService) private readonly wishlistsService: WishlistsService, ) {} @@ -23,34 +29,71 @@ export class UsersService { const { firstName, lastName, email, username, password } = data const hashedPassword = await this.tokenService.createHashedToken(password) - const user = await this.usersRepository.create({ - first_name: firstName, - last_name: lastName, - email, - username, + return await this.drizzleService.db.transaction(async (trx) => { + const createdUser = await this.usersRepository.create( + { + first_name: firstName, + last_name: lastName, + email, + username, + }, + trx, + ) + + if (!createdUser) { + return null + } + + const credentials = await this.credentialsRepository.create( + { + user_id: createdUser.id, + type: CredentialsType.PASSWORD, + secret_data: hashedPassword, + }, + trx, + ) + + if (!credentials) { + await this.usersRepository.delete(createdUser.id) + return null + } + + await this.userRolesService.addRoleToUser(createdUser.id, 'user', true, trx) + + await this.wishlistsService.createEmptyNoName(createdUser.id, trx) + await this.collectionsService.createEmptyNoName(createdUser.id, trx) }) + } - if (!user) { - return null - } + async createOAuthUser(oauthUserId: number, oauthUsername: string, oauthProvider: string) { + return await this.drizzleService.db.transaction(async (trx) => { + const createdUser = await this.usersRepository.create( + { + username: oauthUsername, + }, + trx, + ) - const credentials = await this.credentialsRepository.create({ - user_id: user.id, - type: CredentialsType.PASSWORD, - secret_data: hashedPassword, + if (!createdUser) { + return null + } + + await this.federatedIdentityRepository.create( + { + identity_provider: oauthProvider, + user_id: createdUser.id, + federated_user_id: `${oauthUserId}`, + federated_username: oauthUsername, + }, + trx, + ) + + await this.userRolesService.addRoleToUser(createdUser.id, 'user', true, trx) + + await this.wishlistsService.createEmptyNoName(createdUser.id, trx) + await this.collectionsService.createEmptyNoName(createdUser.id, trx) + return createdUser }) - - if (!credentials) { - await this.usersRepository.delete(user.id) - return null - } - - await this.userRolesService.addRoleToUser(user.id, 'user', true) - - await this.wishlistsService.createEmptyNoName(user.id) - await this.collectionsService.createEmptyNoName(user.id) - - return user } async updateUser(userId: string, data: UpdateUser) { diff --git a/src/lib/server/api/services/wishlists.service.ts b/src/lib/server/api/services/wishlists.service.ts index f7326a1..b06b174 100644 --- a/src/lib/server/api/services/wishlists.service.ts +++ b/src/lib/server/api/services/wishlists.service.ts @@ -1,34 +1,41 @@ -import { inject, injectable } from "tsyringe"; -import { WishlistsRepository } from "../repositories/wishlists.repository"; -import { generateRandomAnimalName } from "$lib/utils/randomDataUtil"; +import type { db } from '$lib/server/api/packages/drizzle' +import { generateRandomAnimalName } from '$lib/utils/randomDataUtil' +import { inject, injectable } from 'tsyringe' +import { WishlistsRepository } from '../repositories/wishlists.repository' @injectable() export class WishlistsService { - - constructor( - @inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository - ) { } + constructor(@inject(WishlistsRepository) private readonly wishlistsRepository: WishlistsRepository) {} async findAllByUserId(userId: string) { - return this.wishlistsRepository.findAllByUserId(userId); + return this.wishlistsRepository.findAllByUserId(userId) } async findOneById(id: string) { - return this.wishlistsRepository.findOneById(id); + return this.wishlistsRepository.findOneById(id) } async findOneByCuid(cuid: string) { - return this.wishlistsRepository.findOneByCuid(cuid); + return this.wishlistsRepository.findOneByCuid(cuid) } - async createEmptyNoName(userId: string) { - return this.createEmpty(userId, null); + async createEmptyNoName(userId: string, trx: Parameters[0]>[0] | null = null) { + return this.createEmpty(userId, null, trx) } - async createEmpty(userId: string, name: string | null) { - return this.wishlistsRepository.create({ - user_id: userId, - name: name ?? generateRandomAnimalName(), - }); + async createEmpty(userId: string, name: string | null, trx: Parameters[0]>[0] | null = null) { + if (!trx) { + return this.wishlistsRepository.create({ + user_id: userId, + name: name ?? generateRandomAnimalName(), + }) + } + return this.wishlistsRepository.create( + { + user_id: userId, + name: name ?? generateRandomAnimalName(), + }, + trx, + ) } -} \ No newline at end of file +} diff --git a/src/routes/(auth)/auth/callback/github/+server.ts b/src/routes/(auth)/auth/callback/github/+server.ts index c58847d..f4c0f73 100644 --- a/src/routes/(auth)/auth/callback/github/+server.ts +++ b/src/routes/(auth)/auth/callback/github/+server.ts @@ -1,21 +1,31 @@ -import { github } from "$lib/server/auth"; -import { OAuth2RequestError } from "arctic"; -import { generateIdFromEntropySize } from "lucia"; - -import type { RequestEvent } from "@sveltejs/kit"; +import { StatusCodes } from '$lib/constants/status-codes' +import type { RequestEvent } from '@sveltejs/kit' +import { redirect } from 'sveltekit-flash-message/server' export async function GET(event: RequestEvent): Promise { - const code = event.url.searchParams.get('code') - const state = event.url.searchParams.get('state') - const { data, error } = await locals.api.oauth.$get({ - params: { - code, - state - } - }) + const { locals, url } = event + const code = url.searchParams.get('code') + const state = url.searchParams.get('state') + console.log('code', code, 'state', state) + + const { data, error } = await locals.api.oauth.github.$get({ query: { code, state } }).then(locals.parseApiResponse) + + if (error) { + return new Response(JSON.stringify(error), { + status: 400, + }) + } + + if (!data) { + return new Response(JSON.stringify({ message: 'Invalid request' }), { + status: 400, + }) + } + + redirect(StatusCodes.TEMPORARY_REDIRECT, '/') } interface GitHubUser { - id: number; - login: string; -} \ No newline at end of file + id: number + login: string +} diff --git a/src/routes/(auth)/login/+page.server.ts b/src/routes/(auth)/login/+page.server.ts index a2b90e9..a08e5d1 100644 --- a/src/routes/(auth)/login/+page.server.ts +++ b/src/routes/(auth)/login/+page.server.ts @@ -1,3 +1,4 @@ +import { StatusCodes } from '$lib/constants/status-codes' import { signinUsernameDto } from '$lib/dtos/signin-username.dto' import { type Actions, fail } from '@sveltejs/kit' import { redirect } from 'sveltekit-flash-message/server' @@ -136,6 +137,8 @@ export const actions: Actions = { form.data.username = '' form.data.password = '' + redirect(StatusCodes.TEMPORARY_REDIRECT, '/') + // if ( // twoFactorDetails?.enabled && // twoFactorDetails?.secret !== null && diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 195f233..090ac27 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -1,41 +1,41 @@ @@ -63,6 +63,9 @@
+

Sign in

+Sign in with GitHub + {#snippet usernamePasswordForm()} diff --git a/src/routes/(auth)/login/github/+server.ts b/src/routes/(auth)/login/github/+server.ts index a93f2f8..d9e5c33 100644 --- a/src/routes/(auth)/login/github/+server.ts +++ b/src/routes/(auth)/login/github/+server.ts @@ -2,17 +2,19 @@ import { github } from '$lib/server/auth' import { redirect } from '@sveltejs/kit' import { generateState } from 'arctic' -export async function GET(event) { - const state = generateState(); +import type { RequestEvent } from '@sveltejs/kit' + +export async function GET(event: RequestEvent): Promise { + const state = generateState() const url = await github.createAuthorizationURL(state) - event.cookies.set('github_state', state, { + event.cookies.set('github_oauth_state', state, { path: '/', secure: import.meta.env.PROD, httpOnly: true, maxAge: 60 * 10, - sameSite: 'lax' - }); + sameSite: 'lax', + }) redirect(302, url.toString()) -} \ No newline at end of file +} From 0e33a85e3796d83db1c6f531e97b0dfea4ca2e66 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 17 Sep 2024 22:13:07 -0700 Subject: [PATCH 07/14] Design sign in with GitHub. --- package.json | 26 +- pnpm-lock.yaml | 383 ++++++++++++++------------- src/routes/(auth)/login/+page.svelte | 14 +- 3 files changed, 224 insertions(+), 199 deletions(-) diff --git a/package.json b/package.json index 92afa67..55ba0af 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,9 @@ "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", "@playwright/test": "^1.47.1", - "@sveltejs/adapter-auto": "^3.2.4", - "@sveltejs/enhanced-img": "^0.3.7", - "@sveltejs/kit": "^2.5.27", + "@sveltejs/adapter-auto": "^3.2.5", + "@sveltejs/enhanced-img": "^0.3.8", + "@sveltejs/kit": "^2.5.28", "@sveltejs/vite-plugin-svelte": "^3.1.2", "@types/cookie": "^0.6.0", "@types/node": "^20.16.5", @@ -40,7 +40,7 @@ "arctic": "^1.9.2", "autoprefixer": "^10.4.20", "drizzle-kit": "^0.23.2", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "2.36.0-next.13", "just-clone": "^6.2.0", @@ -54,7 +54,7 @@ "postcss-preset-env": "^9.6.0", "prettier": "^3.3.3", "prettier-plugin-svelte": "^3.2.6", - "sass": "^1.78.0", + "sass": "^1.79.1", "satori": "^0.10.14", "satori-html": "^0.3.2", "svelte": "5.0.0-next.175", @@ -65,13 +65,13 @@ "svelte-sequential-preprocessor": "^2.0.1", "sveltekit-flash-message": "^2.4.4", "sveltekit-rate-limiter": "^0.5.2", - "sveltekit-superforms": "^2.18.0", - "tailwindcss": "^3.4.11", + "sveltekit-superforms": "^2.18.1", + "tailwindcss": "^3.4.12", "ts-node": "^10.9.2", "tslib": "^2.7.0", "tsx": "^4.19.1", "typescript": "^5.6.2", - "vite": "^5.4.5", + "vite": "^5.4.6", "vitest": "^1.6.0", "zod": "^3.23.8" }, @@ -89,13 +89,13 @@ "@neondatabase/serverless": "^0.9.5", "@paralleldrive/cuid2": "^2.2.2", "@resvg/resvg-js": "^2.6.2", - "@sveltejs/adapter-node": "^5.2.2", - "@sveltejs/adapter-vercel": "^5.4.3", + "@sveltejs/adapter-node": "^5.2.3", + "@sveltejs/adapter-vercel": "^5.4.4", "@types/feather-icons": "^4.29.4", "@vercel/og": "^0.5.20", "bits-ui": "^0.21.13", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.13.0", + "bullmq": "^5.13.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", @@ -106,7 +106,7 @@ "feather-icons": "^4.29.2", "formsnap": "^1.0.1", "handlebars": "^4.7.8", - "hono": "^4.6.1", + "hono": "^4.6.2", "hono-rate-limiter": "^0.4.0", "html-entities": "^2.5.2", "iconify-icon": "^2.1.0", @@ -116,7 +116,7 @@ "loader": "^2.1.1", "open-props": "^1.7.6", "oslo": "^1.2.1", - "pg": "^8.12.0", + "pg": "^8.13.0", "postgres": "^3.4.4", "qrcode": "^1.5.4", "radix-svelte": "^0.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3368666..84f41fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,13 +13,13 @@ importers: version: 5.1.0 '@hono/swagger-ui': specifier: ^0.4.1 - version: 0.4.1(hono@4.6.1) + version: 0.4.1(hono@4.6.2) '@hono/zod-openapi': specifier: ^0.15.3 - version: 0.15.3(hono@4.6.1)(zod@3.23.8) + version: 0.15.3(hono@4.6.2)(zod@3.23.8) '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.6.1)(zod@3.23.8) + version: 0.2.2(hono@4.6.2)(zod@3.23.8) '@iconify-icons/line-md': specifier: ^1.2.30 version: 1.2.30 @@ -31,7 +31,7 @@ importers: version: 3.5.5 '@lucia-auth/adapter-drizzle': specifier: ^1.1.0 - version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0) + version: 1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0)(postgres@3.4.4))(lucia@3.2.0) '@lukeed/uuid': specifier: ^2.0.1 version: 2.0.1 @@ -45,11 +45,11 @@ importers: specifier: ^2.6.2 version: 2.6.2 '@sveltejs/adapter-node': - specifier: ^5.2.2 - version: 5.2.2(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))) + specifier: ^5.2.3 + version: 5.2.3(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))) '@sveltejs/adapter-vercel': - specifier: ^5.4.3 - version: 5.4.3(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))) + specifier: ^5.4.4 + version: 5.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -63,8 +63,8 @@ importers: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.13.0 - version: 5.13.0 + specifier: ^5.13.1 + version: 5.13.1 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -82,25 +82,25 @@ importers: version: 11.0.6 drizzle-orm: specifier: ^0.32.2 - version: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4) + version: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0)(postgres@3.4.4) drizzle-zod: specifier: ^0.5.1 - version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8) + version: 0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0)(postgres@3.4.4))(zod@3.23.8) feather-icons: specifier: ^4.29.2 version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 hono: - specifier: ^4.6.1 - version: 4.6.1 + specifier: ^4.6.2 + version: 4.6.2 hono-rate-limiter: specifier: ^0.4.0 - version: 0.4.0(hono@4.6.1) + version: 0.4.0(hono@4.6.2) html-entities: specifier: ^2.5.2 version: 2.5.2 @@ -126,8 +126,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 pg: - specifier: ^8.12.0 - version: 8.12.0 + specifier: ^8.13.0 + version: 8.13.0 postgres: specifier: ^3.4.4 version: 3.4.4 @@ -154,10 +154,10 @@ importers: version: 2.5.2 tailwind-variants: specifier: ^0.2.1 - version: 0.2.1(tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))) + version: 0.2.1(tailwindcss@3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))) + version: 1.0.7(tailwindcss@3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))) tsyringe: specifier: ^4.8.0 version: 4.8.0 @@ -181,17 +181,17 @@ importers: specifier: ^1.47.1 version: 1.47.1 '@sveltejs/adapter-auto': - specifier: ^3.2.4 - version: 3.2.4(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))) + specifier: ^3.2.5 + version: 3.2.5(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))) '@sveltejs/enhanced-img': - specifier: ^0.3.7 - version: 0.3.7(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + specifier: ^0.3.8 + version: 0.3.8(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) '@sveltejs/kit': - specifier: ^2.5.27 - version: 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + specifier: ^2.5.28 + version: 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 @@ -203,10 +203,10 @@ importers: version: 8.11.10 '@typescript-eslint/eslint-plugin': specifier: ^7.18.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/parser': specifier: ^7.18.0 - version: 7.18.0(eslint@8.57.0)(typescript@5.6.2) + version: 7.18.0(eslint@8.57.1)(typescript@5.6.2) arctic: specifier: ^1.9.2 version: 1.9.2 @@ -217,14 +217,14 @@ importers: specifier: ^0.23.2 version: 0.23.2 eslint: - specifier: ^8.57.0 - version: 8.57.0 + specifier: ^8.57.1 + version: 8.57.1 eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.0) + version: 9.1.0(eslint@8.57.1) eslint-plugin-svelte: specifier: 2.36.0-next.13 - version: 2.36.0-next.13(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) + version: 2.36.0-next.13(eslint@8.57.1)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) just-clone: specifier: ^6.2.0 version: 6.2.0 @@ -259,8 +259,8 @@ importers: specifier: ^3.2.6 version: 3.2.6(prettier@3.3.3)(svelte@5.0.0-next.175) sass: - specifier: ^1.78.0 - version: 1.78.0 + specifier: ^1.79.1 + version: 1.79.1 satori: specifier: ^0.10.14 version: 0.10.14 @@ -272,7 +272,7 @@ importers: version: 5.0.0-next.175 svelte-check: specifier: ^3.8.6 - version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175) + version: 3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.79.1)(svelte@5.0.0-next.175) svelte-headless-table: specifier: ^0.18.2 version: 0.18.2(svelte@5.0.0-next.175) @@ -281,22 +281,22 @@ importers: version: 3.1.4(svelte@5.0.0-next.175)(typescript@5.6.2) svelte-preprocess: specifier: ^6.0.2 - version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2) + version: 6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.79.1)(svelte@5.0.0-next.175)(typescript@5.6.2) svelte-sequential-preprocessor: specifier: ^2.0.1 version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))) + version: 0.5.2(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))) sveltekit-superforms: - specifier: ^2.18.0 - version: 2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) + specifier: ^2.18.1 + version: 2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) tailwindcss: - specifier: ^3.4.11 - version: 3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) + specifier: ^3.4.12 + version: 3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.16.5)(typescript@5.6.2) @@ -310,11 +310,11 @@ importers: specifier: ^5.6.2 version: 5.6.2 vite: - specifier: ^5.4.5 - version: 5.4.5(@types/node@20.16.5)(sass@1.78.0) + specifier: ^5.4.6 + version: 5.4.6(@types/node@20.16.5)(sass@1.79.1) vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.16.5)(sass@1.78.0) + version: 1.6.0(@types/node@20.16.5)(sass@1.79.1) zod: specifier: ^3.23.8 version: 3.23.8 @@ -1225,8 +1225,8 @@ packages: resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} '@exodus/schemasafe@1.3.0': @@ -1276,8 +1276,8 @@ packages: hono: '>=3.9.0' zod: ^3.19.1 - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} deprecated: Use @eslint/config-array instead @@ -1941,29 +1941,29 @@ packages: '@sodaru/yup-to-json-schema@2.0.1': resolution: {integrity: sha512-lWb0Wiz8KZ9ip/dY1eUqt7fhTPmL24p6Hmv5Fd9pzlzAdw/YNcWZr+tiCT4oZ4Zyxzi9+1X4zv82o7jYvcFxYA==} - '@sveltejs/adapter-auto@3.2.4': - resolution: {integrity: sha512-a64AKYbfTUrVwU0xslzv1Jf3M8bj0IwhptaXmhgIkjXspBXhD0od9JiItQHchijpLMGdEDcYBlvqySkEawv6mQ==} + '@sveltejs/adapter-auto@3.2.5': + resolution: {integrity: sha512-27LR+uKccZ62lgq4N/hvyU2G+hTP9fxWEAfnZcl70HnyfAjMSsGk1z/SjAPXNCD1mVJIE7IFu3TQ8cQ/UH3c0A==} peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/adapter-node@5.2.2': - resolution: {integrity: sha512-BCX4zP0cf86TXpmvLQTnnT/tp7P12UMezf+5LwljP1MJC1fFzn9XOXpAHQCyP+pyHGy2K7p5gY0LyLcZFAL02w==} + '@sveltejs/adapter-node@5.2.3': + resolution: {integrity: sha512-0KNrTc9NiEhB1vyVL0HiqZaW2P5JWNJgTYT5PnUZCLO9Oydx8G+6PNtJPJ/NNPyeGrn+6LwR5L8GNRvA4b5Bpw==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/adapter-vercel@5.4.3': - resolution: {integrity: sha512-srZBkMpeaa7lflO1ZGdKTW3jWHscE9rdAkyxgRlVMVyugjcPOZ3dcpEfpoM4wtVCbpEOdHniWqQR9yL+zs4ybA==} + '@sveltejs/adapter-vercel@5.4.4': + resolution: {integrity: sha512-KORoxxqB2H5DrxpCHc9Yfijcgvmoaaz6G6eKHEg9fRlTsujJkxN26C0x4YlcgxqDU4dLIi1wfSLHpuZD0E4Irg==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/enhanced-img@0.3.7': - resolution: {integrity: sha512-Rb692ralH4Vs8bx52b403Xs2ZmZJ3XctYiXGNXLay9ex1G56jD31jqPFnwoWKyUqiD9JTi6CxVp/1Xy2Dus86Q==} + '@sveltejs/enhanced-img@0.3.8': + resolution: {integrity: sha512-n66u46ZeqHltiTm0BEjWptYmCrCY0EltEEvakmC7d5o5ZejDbOvOWm914mebbRKaP2Bezv65TNCod/wqvw/0KA==} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 vite: '>= 5.0.0' - '@sveltejs/kit@2.5.27': - resolution: {integrity: sha512-CcbRTzl+65oWljAASL6UlxM4x3NWwd0fjq5fQOfP243vs50myFQ8lil0fr3Im6HeeQqYUCtnv8HjO8REWVPjTw==} + '@sveltejs/kit@2.5.28': + resolution: {integrity: sha512-/O7pvFGBsQPcFa9UrW8eUC5uHTOXLsUp3SN0dY6YmRAL9nfPSrJsSJk//j5vMpinSshzUjteAFcfQTU+04Ka1w==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -2311,8 +2311,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bullmq@5.13.0: - resolution: {integrity: sha512-rE7v3jMZZGsEhfMhLZwADwuHdqJPTTGHBM8C+SpxF9GzyZ+7pvC80EP5bOZJPPRzbmyhvIPJCVd0bchUZiQF+w==} + bullmq@5.13.1: + resolution: {integrity: sha512-9Ss2GzV+VVA2Q/+qUxQ7zxAwfLWBwc7DIUNboq0EdXGRf2Ia+Qi7BwT51rnxglu4b/0SRsSElevRT8IZc7HvtQ==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -2363,6 +2363,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.0: + resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==} + engines: {node: '>= 14.16.0'} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -2806,8 +2810,8 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true @@ -3062,9 +3066,9 @@ packages: peerDependencies: hono: ^4.1.1 - hono@4.6.1: - resolution: {integrity: sha512-6NGwvttY1+HAFii08VYiEKI6ETPAFbpLntpm2M/MogEsAFWdZV74UNT+2M4bmqX90cIQhjlpBSP+tO+CfB0uww==} - engines: {node: '>=16.0.0'} + hono@4.6.2: + resolution: {integrity: sha512-v+39817TgAhetmHUEli8O0uHDmxp2Up3DnhS4oUZXOl5IQ9np9tYtldd42e5zgdLVS0wsOoXQNZ6mx+BGmEvCA==} + engines: {node: '>=16.9.0'} html-entities@2.5.2: resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} @@ -3638,8 +3642,8 @@ packages: pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} - pg-connection-string@2.6.4: - resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} @@ -3649,14 +3653,17 @@ packages: resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} engines: {node: '>=4'} - pg-pool@3.6.2: - resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} peerDependencies: pg: '>=8.0' pg-protocol@1.6.1: resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -3665,8 +3672,8 @@ packages: resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} engines: {node: '>=10'} - pg@8.12.0: - resolution: {integrity: sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==} + pg@8.13.0: + resolution: {integrity: sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==} engines: {node: '>= 8.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -4063,6 +4070,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.0.1: + resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==} + engines: {node: '>= 14.16.0'} + redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -4137,8 +4148,8 @@ packages: sander@0.5.1: resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} - sass@1.78.0: - resolution: {integrity: sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ==} + sass@1.79.1: + resolution: {integrity: sha512-+mA7svoNKeL0DiJqZGeR/ZGUu8he4I8o3jyUcOFyo4eBJrwNgIMmAEwCMo/N2Y3wdjOBcRzoNxZIOtrtMX8EXg==} engines: {node: '>=14.0.0'} hasBin: true @@ -4479,8 +4490,8 @@ packages: peerDependencies: '@sveltejs/kit': 1.x || 2.x - sveltekit-superforms@2.18.0: - resolution: {integrity: sha512-INx2EwWk82WcUdG5iEMtcJ+NLvBpYOdiHDRa68Q9p9W+YRAN3mxwiqj5gpfVrvBPlGfFZypeAze54pBZ/wlspw==} + sveltekit-superforms@2.18.1: + resolution: {integrity: sha512-bdWXlWfvG0AauTj8KGZ00dZGix4jxzGM5ywXFZuodJjwGyEpnfNbui4n1KX5KdIqsVoTU1cKrxt0gZnuJivJsw==} peerDependencies: '@sveltejs/kit': 1.x || 2.x svelte: 3.x || 4.x || >=5.0.0-next.51 @@ -4502,8 +4513,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.11: - resolution: {integrity: sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==} + tailwindcss@3.4.12: + resolution: {integrity: sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==} engines: {node: '>=14.0.0'} hasBin: true @@ -4705,8 +4716,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.5: - resolution: {integrity: sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==} + vite@5.4.6: + resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5476,9 +5487,9 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': dependencies: - eslint: 8.57.0 + eslint: 8.57.1 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.0': {} @@ -5497,7 +5508,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.0': {} + '@eslint/js@8.57.1': {} '@exodus/schemasafe@1.3.0': optional: true @@ -5534,23 +5545,23 @@ snapshots: '@hapi/hoek': 9.3.0 optional: true - '@hono/swagger-ui@0.4.1(hono@4.6.1)': + '@hono/swagger-ui@0.4.1(hono@4.6.2)': dependencies: - hono: 4.6.1 + hono: 4.6.2 - '@hono/zod-openapi@0.15.3(hono@4.6.1)(zod@3.23.8)': + '@hono/zod-openapi@0.15.3(hono@4.6.2)(zod@3.23.8)': dependencies: '@asteasolutions/zod-to-openapi': 7.1.1(zod@3.23.8) - '@hono/zod-validator': 0.2.2(hono@4.6.1)(zod@3.23.8) - hono: 4.6.1 + '@hono/zod-validator': 0.2.2(hono@4.6.2)(zod@3.23.8) + hono: 4.6.2 zod: 3.23.8 - '@hono/zod-validator@0.2.2(hono@4.6.1)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.6.2)(zod@3.23.8)': dependencies: - hono: 4.6.1 + hono: 4.6.2 zod: 3.23.8 - '@humanwhocodes/config-array@0.11.14': + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 debug: 4.3.6 @@ -5690,9 +5701,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4))(lucia@3.2.0)': + '@lucia-auth/adapter-drizzle@1.1.0(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0)(postgres@3.4.4))(lucia@3.2.0)': dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0)(postgres@3.4.4) lucia: 3.2.0 '@lukeed/csprng@1.1.0': {} @@ -6089,41 +6100,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.4(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-auto@3.2.5(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))': dependencies: - '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.2(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-node@5.2.3(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.21.2) '@rollup/plugin-json': 6.1.0(rollup@4.21.2) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.21.2) - '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) rollup: 4.21.2 - '@sveltejs/adapter-vercel@5.4.3(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))': + '@sveltejs/adapter-vercel@5.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))': dependencies: - '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) '@vercel/nft': 0.27.4 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.7(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/enhanced-img@0.3.8(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))': dependencies: magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) vite-imagetools: 7.0.4(rollup@4.21.2) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6137,28 +6148,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) debug: 4.3.6 svelte: 5.0.0-next.175 - vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) debug: 4.3.6 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) - vitefu: 0.2.5(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + vitefu: 0.2.5(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) transitivePeerDependencies: - supports-color @@ -6225,15 +6236,15 @@ snapshots: '@types/json-schema': 7.0.15 optional: true - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.6.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.2) '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 8.57.0 + eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -6243,14 +6254,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.2)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.3.6 - eslint: 8.57.0 + eslint: 8.57.1 optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -6261,12 +6272,12 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.6.2)': dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.2) debug: 4.3.6 - eslint: 8.57.0 + eslint: 8.57.1 ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 @@ -6290,13 +6301,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.0)(typescript@5.6.2)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.6.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2) - eslint: 8.57.0 + eslint: 8.57.1 transitivePeerDependencies: - supports-color - typescript @@ -6548,7 +6559,7 @@ snapshots: builtin-modules@3.3.0: {} - bullmq@5.13.0: + bullmq@5.13.1: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -6616,6 +6627,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.0: + dependencies: + readdirp: 4.0.1 + chownr@2.0.0: {} class-validator@0.14.1: @@ -6816,16 +6831,16 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4): + drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0)(postgres@3.4.4): optionalDependencies: '@neondatabase/serverless': 0.9.5 '@types/pg': 8.11.10 - pg: 8.12.0 + pg: 8.13.0 postgres: 3.4.4 - drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4))(zod@3.23.8): + drizzle-zod@0.5.1(drizzle-orm@0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0)(postgres@3.4.4))(zod@3.23.8): dependencies: - drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.12.0)(postgres@3.4.4) + drizzle-orm: 0.32.2(@neondatabase/serverless@0.9.5)(@types/pg@8.11.10)(pg@8.13.0)(postgres@3.4.4) zod: 3.23.8 eastasianwidth@0.2.0: {} @@ -6974,22 +6989,22 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@8.57.0): + eslint-compat-utils@0.5.1(eslint@8.57.1): dependencies: - eslint: 8.57.0 + eslint: 8.57.1 semver: 7.6.3 - eslint-config-prettier@9.1.0(eslint@8.57.0): + eslint-config-prettier@9.1.0(eslint@8.57.1): dependencies: - eslint: 8.57.0 + eslint: 8.57.1 - eslint-plugin-svelte@2.36.0-next.13(eslint@8.57.0)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): + eslint-plugin-svelte@2.36.0-next.13(eslint@8.57.1)(svelte@5.0.0-next.175)(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) '@jridgewell/sourcemap-codec': 1.5.0 debug: 4.3.6 - eslint: 8.57.0 - eslint-compat-utils: 0.5.1(eslint@8.57.0) + eslint: 8.57.1 + eslint-compat-utils: 0.5.1(eslint@8.57.1) esutils: 2.0.3 known-css-properties: 0.30.0 postcss: 8.4.47 @@ -7011,13 +7026,13 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint@8.57.0: + eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) '@eslint-community/regexpp': 4.11.0 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.2.0 @@ -7217,11 +7232,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) + sveltekit-superforms: 2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7354,11 +7369,11 @@ snapshots: hex-rgb@4.3.0: {} - hono-rate-limiter@0.4.0(hono@4.6.1): + hono-rate-limiter@0.4.0(hono@4.6.2): dependencies: - hono: 4.6.1 + hono: 4.6.2 - hono@4.6.1: {} + hono@4.6.2: {} html-entities@2.5.2: {} @@ -7876,18 +7891,20 @@ snapshots: pg-cloudflare@1.1.1: optional: true - pg-connection-string@2.6.4: {} + pg-connection-string@2.7.0: {} pg-int8@1.0.1: {} pg-numeric@1.0.2: {} - pg-pool@3.6.2(pg@8.12.0): + pg-pool@3.7.0(pg@8.13.0): dependencies: - pg: 8.12.0 + pg: 8.13.0 pg-protocol@1.6.1: {} + pg-protocol@1.7.0: {} + pg-types@2.2.0: dependencies: pg-int8: 1.0.1 @@ -7906,11 +7923,11 @@ snapshots: postgres-interval: 3.0.0 postgres-range: 1.1.4 - pg@8.12.0: + pg@8.13.0: dependencies: - pg-connection-string: 2.6.4 - pg-pool: 3.6.2(pg@8.12.0) - pg-protocol: 1.6.1 + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.0) + pg-protocol: 1.7.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: @@ -8320,6 +8337,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.0.1: {} + redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -8400,9 +8419,9 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.7.1 - sass@1.78.0: + sass@1.79.1: dependencies: - chokidar: 3.6.0 + chokidar: 4.0.0 immutable: 4.3.7 source-map-js: 1.2.0 @@ -8630,14 +8649,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175): + svelte-check@3.8.6(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.79.1)(svelte@5.0.0-next.175): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.1.0 sade: 1.8.1 svelte: 5.0.0-next.175 - svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2) + svelte-preprocess: 5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.79.1)(svelte@5.0.0-next.175)(typescript@5.6.2) typescript: 5.6.2 transitivePeerDependencies: - '@babel/core' @@ -8693,7 +8712,7 @@ snapshots: dependencies: svelte: 5.0.0-next.175 - svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2): + svelte-preprocess@5.1.4(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.79.1)(svelte@5.0.0-next.175)(typescript@5.6.2): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 @@ -8704,16 +8723,16 @@ snapshots: optionalDependencies: postcss: 8.4.47 postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1) - sass: 1.78.0 + sass: 1.79.1 typescript: 5.6.2 - svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.78.0)(svelte@5.0.0-next.175)(typescript@5.6.2): + svelte-preprocess@6.0.2(postcss-load-config@5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1))(postcss@8.4.47)(sass@1.79.1)(svelte@5.0.0-next.175)(typescript@5.6.2): dependencies: svelte: 5.0.0-next.175 optionalDependencies: postcss: 8.4.47 postcss-load-config: 5.1.0(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.1) - sass: 1.78.0 + sass: 1.79.1 typescript: 5.6.2 svelte-render@2.0.1(svelte@5.0.0-next.175): @@ -8767,19 +8786,19 @@ snapshots: magic-string: 0.30.11 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) - sveltekit-superforms@2.18.0(@sveltejs/kit@2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175): + sveltekit-superforms@2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.27(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)))(svelte@5.0.0-next.175)(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -8808,16 +8827,16 @@ snapshots: tailwind-merge@2.5.2: {} - tailwind-variants@0.2.1(tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))): + tailwind-variants@0.2.1(tailwindcss@3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))): dependencies: tailwind-merge: 2.5.2 - tailwindcss: 3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) + tailwindcss: 3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) - tailwindcss-animate@1.0.7(tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2))): dependencies: - tailwindcss: 3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) + tailwindcss: 3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) - tailwindcss@3.4.11(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): + tailwindcss@3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -9014,13 +9033,13 @@ snapshots: transitivePeerDependencies: - rollup - vite-node@1.6.0(@types/node@20.16.5)(sass@1.78.0): + vite-node@1.6.0(@types/node@20.16.5)(sass@1.79.1): dependencies: cac: 6.7.14 debug: 4.3.6 pathe: 1.1.2 picocolors: 1.1.0 - vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) transitivePeerDependencies: - '@types/node' - less @@ -9032,7 +9051,7 @@ snapshots: - supports-color - terser - vite@5.4.5(@types/node@20.16.5)(sass@1.78.0): + vite@5.4.6(@types/node@20.16.5)(sass@1.79.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 @@ -9040,13 +9059,13 @@ snapshots: optionalDependencies: '@types/node': 20.16.5 fsevents: 2.3.3 - sass: 1.78.0 + sass: 1.79.1 - vitefu@0.2.5(vite@5.4.5(@types/node@20.16.5)(sass@1.78.0)): + vitefu@0.2.5(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)): optionalDependencies: - vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) - vitest@1.6.0(@types/node@20.16.5)(sass@1.78.0): + vitest@1.6.0(@types/node@20.16.5)(sass@1.79.1): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -9065,8 +9084,8 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.5(@types/node@20.16.5)(sass@1.78.0) - vite-node: 1.6.0(@types/node@20.16.5)(sass@1.78.0) + vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + vite-node: 1.6.0(@types/node@20.16.5)(sass@1.79.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.16.5 diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 090ac27..3cfe70c 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -12,6 +12,7 @@ import { AlertCircle } from 'lucide-svelte' import * as flashModule from 'sveltekit-flash-message/client' import { zodClient } from 'sveltekit-superforms/adapters' import { superForm } from 'sveltekit-superforms/client' +import { Github } from 'lucide-svelte' let { data } = $props() @@ -47,8 +48,10 @@ const { form: loginForm, enhance } = superLoginForm Log into your account - + {@render usernamePasswordForm()} + or sign in with + {@render oAuthButtons()}

By clicking continue, you agree to our @@ -63,9 +66,6 @@ const { form: loginForm, enhance } = superLoginForm

-

Sign in

-
Sign in with GitHub - {#snippet usernamePasswordForm()} @@ -89,5 +89,11 @@ const { form: loginForm, enhance } = superLoginForm {/snippet} +{#snippet oAuthButtons()} +
+ +
+{/snippet} + \ No newline at end of file From dd4bbb01dcff649b0ab5a24c0c1023b5c94aaa6b Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 17 Sep 2024 22:58:24 -0700 Subject: [PATCH 08/14] Remove sass --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 55ba0af..59cf53a 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "postcss-preset-env": "^9.6.0", "prettier": "^3.3.3", "prettier-plugin-svelte": "^3.2.6", - "sass": "^1.79.1", "satori": "^0.10.14", "satori-html": "^0.3.2", "svelte": "5.0.0-next.175", From 7477c4417435f74509393b08c7947ec3229fa77b Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Tue, 17 Sep 2024 22:59:05 -0700 Subject: [PATCH 09/14] Remove sass, lock updated --- pnpm-lock.yaml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84f41fe..c3c844e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -258,9 +258,6 @@ importers: prettier-plugin-svelte: specifier: ^3.2.6 version: 3.2.6(prettier@3.3.3)(svelte@5.0.0-next.175) - sass: - specifier: ^1.79.1 - version: 1.79.1 satori: specifier: ^0.10.14 version: 0.10.14 @@ -6630,6 +6627,7 @@ snapshots: chokidar@4.0.0: dependencies: readdirp: 4.0.1 + optional: true chownr@2.0.0: {} @@ -7406,7 +7404,8 @@ snapshots: imagetools-core@7.0.1: {} - immutable@4.3.7: {} + immutable@4.3.7: + optional: true import-fresh@3.3.0: dependencies: @@ -8337,7 +8336,8 @@ snapshots: dependencies: picomatch: 2.3.1 - readdirp@4.0.1: {} + readdirp@4.0.1: + optional: true redis-errors@1.2.0: {} @@ -8424,6 +8424,7 @@ snapshots: chokidar: 4.0.0 immutable: 4.3.7 source-map-js: 1.2.0 + optional: true satori-html@0.3.2: dependencies: @@ -8569,7 +8570,8 @@ snapshots: minimist: 1.2.8 sander: 0.5.1 - source-map-js@1.2.0: {} + source-map-js@1.2.0: + optional: true source-map-js@1.2.1: {} From 9b50e0fb48d27c4831999f310063f1068982ed0d Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 19 Sep 2024 16:55:03 -0700 Subject: [PATCH 10/14] Adding more OAuth provider scaffolding and using svgs for icons. --- package.json | 1 + pnpm-lock.yaml | 18 +++++++++-------- .../collections/[cuid]/+page.svelte | 2 +- .../settings/profile/+page.server.ts | 8 -------- src/routes/(auth)/login/+page.svelte | 10 ++++++++-- src/routes/(auth)/login/apple/+server.ts | 20 +++++++++++++++++++ src/routes/(auth)/login/google/+server.ts | 20 +++++++++++++++++++ src/routes/(auth)/login/spotify/+server.ts | 20 +++++++++++++++++++ src/routes/(auth)/login/tidal/+server.ts | 20 +++++++++++++++++++ 9 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 src/routes/(auth)/login/apple/+server.ts create mode 100644 src/routes/(auth)/login/google/+server.ts create mode 100644 src/routes/(auth)/login/spotify/+server.ts create mode 100644 src/routes/(auth)/login/tidal/+server.ts diff --git a/package.json b/package.json index 59cf53a..9423b10 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "radix-svelte": "^0.9.0", "rate-limit-redis": "^4.2.0", "reflect-metadata": "^0.2.2", + "simple-icons": "^13.10.0", "svelte-french-toast": "^1.2.0", "svelte-lazy-loader": "^1.0.0", "tailwind-merge": "^2.5.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3c844e..daff87b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,6 +143,9 @@ importers: reflect-metadata: specifier: ^0.2.2 version: 0.2.2 + simple-icons: + specifier: ^13.10.0 + version: 13.10.0 svelte-french-toast: specifier: ^1.2.0 version: 1.2.0(svelte@5.0.0-next.175) @@ -4222,6 +4225,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-icons@13.10.0: + resolution: {integrity: sha512-akHZxjNvq4nLlLsKxDot7A+ZYXgjhAP4GwvWEItHeKiquuWXoI8xGGfP+hzGLbTmbmi2teYmHMS45PIOFy6EFA==} + engines: {node: '>=0.12.18'} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -4237,10 +4244,6 @@ packages: resolution: {integrity: sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==} hasBin: true - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -8423,7 +8426,7 @@ snapshots: dependencies: chokidar: 4.0.0 immutable: 4.3.7 - source-map-js: 1.2.0 + source-map-js: 1.2.1 optional: true satori-html@0.3.2: @@ -8551,6 +8554,8 @@ snapshots: signal-exit@4.1.0: {} + simple-icons@13.10.0: {} + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 @@ -8570,9 +8575,6 @@ snapshots: minimist: 1.2.8 sander: 0.5.1 - source-map-js@1.2.0: - optional: true - source-map-js@1.2.1: {} source-map-support@0.5.21: diff --git a/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte b/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte index 450233c..70a1054 100644 --- a/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte +++ b/src/routes/(app)/(protected)/collections/[cuid]/+page.svelte @@ -40,7 +40,7 @@ console.log('items', items)
{#if items.length === 0} -

No gamesTable in your collection

+

No games in your collection

{:else} {#each items as game (game.game_id)} diff --git a/src/routes/(app)/(protected)/settings/profile/+page.server.ts b/src/routes/(app)/(protected)/settings/profile/+page.server.ts index 77cab00..1841c32 100644 --- a/src/routes/(app)/(protected)/settings/profile/+page.server.ts +++ b/src/routes/(app)/(protected)/settings/profile/+page.server.ts @@ -18,14 +18,6 @@ export const load: PageServerLoad = async (event) => { throw redirect(302, '/login', notSignedInMessage, event) } - console.log('authedUser', authedUser) - // if (userNotAuthenticated(user, session)) { - // redirect(302, '/login', notSignedInMessage, event); - // } - // const dbUser = await db.query.usersTable.findFirst({ - // where: eq(usersTable.id, user!.id!), - // }); - const profileForm = await superValidate(zod(updateProfileSchema), { defaults: { firstName: authedUser?.firstName ?? '', diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 3cfe70c..8d6ecaf 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -9,10 +9,10 @@ import { boredState } from '$lib/stores/boredState.js' import { receive, send } from '$lib/utils/pageCrossfade' import { signInSchema } from '$lib/validations/auth' import { AlertCircle } from 'lucide-svelte' +import { siApple, siGithub, siGoogle } from 'simple-icons' import * as flashModule from 'sveltekit-flash-message/client' import { zodClient } from 'sveltekit-superforms/adapters' import { superForm } from 'sveltekit-superforms/client' -import { Github } from 'lucide-svelte' let { data } = $props() @@ -91,9 +91,15 @@ const { form: loginForm, enhance } = superLoginForm {#snippet oAuthButtons()}
- + + +
{/snippet} \ No newline at end of file diff --git a/src/routes/(auth)/login/apple/+server.ts b/src/routes/(auth)/login/apple/+server.ts new file mode 100644 index 0000000..d9e5c33 --- /dev/null +++ b/src/routes/(auth)/login/apple/+server.ts @@ -0,0 +1,20 @@ +import { github } from '$lib/server/auth' +import { redirect } from '@sveltejs/kit' +import { generateState } from 'arctic' + +import type { RequestEvent } from '@sveltejs/kit' + +export async function GET(event: RequestEvent): Promise { + const state = generateState() + const url = await github.createAuthorizationURL(state) + + event.cookies.set('github_oauth_state', state, { + path: '/', + secure: import.meta.env.PROD, + httpOnly: true, + maxAge: 60 * 10, + sameSite: 'lax', + }) + + redirect(302, url.toString()) +} diff --git a/src/routes/(auth)/login/google/+server.ts b/src/routes/(auth)/login/google/+server.ts new file mode 100644 index 0000000..d9e5c33 --- /dev/null +++ b/src/routes/(auth)/login/google/+server.ts @@ -0,0 +1,20 @@ +import { github } from '$lib/server/auth' +import { redirect } from '@sveltejs/kit' +import { generateState } from 'arctic' + +import type { RequestEvent } from '@sveltejs/kit' + +export async function GET(event: RequestEvent): Promise { + const state = generateState() + const url = await github.createAuthorizationURL(state) + + event.cookies.set('github_oauth_state', state, { + path: '/', + secure: import.meta.env.PROD, + httpOnly: true, + maxAge: 60 * 10, + sameSite: 'lax', + }) + + redirect(302, url.toString()) +} diff --git a/src/routes/(auth)/login/spotify/+server.ts b/src/routes/(auth)/login/spotify/+server.ts new file mode 100644 index 0000000..d9e5c33 --- /dev/null +++ b/src/routes/(auth)/login/spotify/+server.ts @@ -0,0 +1,20 @@ +import { github } from '$lib/server/auth' +import { redirect } from '@sveltejs/kit' +import { generateState } from 'arctic' + +import type { RequestEvent } from '@sveltejs/kit' + +export async function GET(event: RequestEvent): Promise { + const state = generateState() + const url = await github.createAuthorizationURL(state) + + event.cookies.set('github_oauth_state', state, { + path: '/', + secure: import.meta.env.PROD, + httpOnly: true, + maxAge: 60 * 10, + sameSite: 'lax', + }) + + redirect(302, url.toString()) +} diff --git a/src/routes/(auth)/login/tidal/+server.ts b/src/routes/(auth)/login/tidal/+server.ts new file mode 100644 index 0000000..d9e5c33 --- /dev/null +++ b/src/routes/(auth)/login/tidal/+server.ts @@ -0,0 +1,20 @@ +import { github } from '$lib/server/auth' +import { redirect } from '@sveltejs/kit' +import { generateState } from 'arctic' + +import type { RequestEvent } from '@sveltejs/kit' + +export async function GET(event: RequestEvent): Promise { + const state = generateState() + const url = await github.createAuthorizationURL(state) + + event.cookies.set('github_oauth_state', state, { + path: '/', + secure: import.meta.env.PROD, + httpOnly: true, + maxAge: 60 * 10, + sameSite: 'lax', + }) + + redirect(302, url.toString()) +} From cb271d377e05ad0723d151a8923495b48ce6abea Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Thu, 19 Sep 2024 18:06:54 -0700 Subject: [PATCH 11/14] Adding license and starting google oauth. --- .env.example | 2 + LICENSE | 21 +++ package.json | 5 +- src/lib/server/api/common/env.ts | 2 + .../api/controllers/oauth.controller.ts | 140 +++++++++++++----- src/lib/server/auth.ts | 4 +- src/routes/(app)/privacy/+page.svelte | 2 +- .../(auth)/auth/callback/github/+server.ts | 5 - .../(auth)/auth/callback/google/+server.ts | 26 ++++ src/routes/(auth)/login/google/+server.ts | 19 ++- 10 files changed, 172 insertions(+), 54 deletions(-) create mode 100644 LICENSE create mode 100644 src/routes/(auth)/auth/callback/google/+server.ts diff --git a/.env.example b/.env.example index 527dae2..a539bcd 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,8 @@ TWO_FACTOR_TIMEOUT=300000 # OAuth GITHUB_CLIENT_ID="" GITHUB_CLIENT_SECRET="" +GOOGLE_CLIENT_ID="" +GOOGLE_CLIENT_SECRET="" # Public diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2af8905 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License Copyright (c) 2024 Bradley Shellnut + +Permission is hereby granted, +free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice +(including the next paragraph) shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/package.json b/package.json index 9423b10..8caf66b 100644 --- a/package.json +++ b/package.json @@ -129,5 +129,6 @@ "tailwindcss-animate": "^1.0.7", "tsyringe": "^4.8.0", "zod-to-json-schema": "^3.23.3" - } -} + }, + "license": "MIT" +} \ No newline at end of file diff --git a/src/lib/server/api/common/env.ts b/src/lib/server/api/common/env.ts index 95b35c4..5dd1157 100644 --- a/src/lib/server/api/common/env.ts +++ b/src/lib/server/api/common/env.ts @@ -19,6 +19,8 @@ const EnvSchema = z.object({ DB_SEEDING: stringBoolean, GITHUB_CLIENT_ID: z.string(), GITHUB_CLIENT_SECRET: z.string(), + GOOGLE_CLIENT_ID: z.string(), + GOOGLE_CLIENT_SECRET: z.string(), NODE_ENV: z.string().default('development'), ORIGIN: z.string(), PUBLIC_SITE_NAME: z.string(), diff --git a/src/lib/server/api/controllers/oauth.controller.ts b/src/lib/server/api/controllers/oauth.controller.ts index 570f42b..4a7b44e 100644 --- a/src/lib/server/api/controllers/oauth.controller.ts +++ b/src/lib/server/api/controllers/oauth.controller.ts @@ -2,7 +2,7 @@ import 'reflect-metadata' import { Controller } from '$lib/server/api/common/types/controller' import { LuciaService } from '$lib/server/api/services/lucia.service' import { OAuthService } from '$lib/server/api/services/oauth.service' -import { github } from '$lib/server/auth' +import { github, google } from '$lib/server/auth' import { OAuth2RequestError } from 'arctic' import { getCookie, setCookie } from 'hono/cookie' import { TimeSpan } from 'oslo' @@ -18,55 +18,108 @@ export class OAuthController extends Controller { } routes() { - return this.controller.get('/github', async (c) => { - try { - const code = c.req.query('code')?.toString() ?? null - const state = c.req.query('state')?.toString() ?? null - const storedState = getCookie(c).github_oauth_state ?? null + return this.controller + .get('/github', async (c) => { + try { + const code = c.req.query('code')?.toString() ?? null + const state = c.req.query('state')?.toString() ?? null + const storedState = getCookie(c).github_oauth_state ?? null - console.log('code', code, 'state', state, 'storedState', storedState) + console.log('code', code, 'state', state, 'storedState', storedState) - if (!code || !state || !storedState || state !== storedState) { - return c.body(null, 400) + if (!code || !state || !storedState || state !== storedState) { + return c.body(null, 400) + } + + const tokens = await github.validateAuthorizationCode(code) + const githubUserResponse = await fetch('https://api.github.com/user', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + const githubUser: GitHubUser = await githubUserResponse.json() + + const userId = await this.oauthService.handleOAuthUser(githubUser.id, githubUser.login, 'github') + + const session = await this.luciaService.lucia.createSession(userId, {}) + const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id) + + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: + sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() + ? sessionCookie.attributes.maxAge + : new TimeSpan(2, 'w').seconds(), + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires, + }) + + return c.json({ message: 'ok' }) + } catch (error) { + console.error(error) + // the specific error message depends on the provider + if (error instanceof OAuth2RequestError) { + // invalid code + return c.body(null, 400) + } + return c.body(null, 500) } + }) + .get('/google', async (c) => { + try { + const code = c.req.query('code')?.toString() ?? null + const state = c.req.query('state')?.toString() ?? null + const storedState = getCookie(c).google_oauth_state ?? null + const storedCodeVerifier = getCookie(c).google_oauth_code_verifier ?? null - const tokens = await github.validateAuthorizationCode(code) - const githubUserResponse = await fetch('https://api.github.com/user', { - headers: { - Authorization: `Bearer ${tokens.accessToken}`, - }, - }) - const githubUser: GitHubUser = await githubUserResponse.json() + console.log('code', code, 'state', state, 'storedState', storedState) - const userId = await this.oauthService.handleOAuthUser(githubUser.id, githubUser.login, 'github') + if (!code || !storedState || !storedCodeVerifier || state !== storedState) { + return c.body(null, 400) + } - const session = await this.luciaService.lucia.createSession(userId, {}) - const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id) + const tokens = await google.validateAuthorizationCode(code, storedCodeVerifier) + console.log('tokens', tokens) + const googleUserResponse = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + console.log('googleUserResponse', googleUserResponse) + const googleUser: GoogleUser = await googleUserResponse.json() - setCookie(c, sessionCookie.name, sessionCookie.value, { - path: sessionCookie.attributes.path, - maxAge: - sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() - ? sessionCookie.attributes.maxAge - : new TimeSpan(2, 'w').seconds(), - domain: sessionCookie.attributes.domain, - sameSite: sessionCookie.attributes.sameSite as any, - secure: sessionCookie.attributes.secure, - httpOnly: sessionCookie.attributes.httpOnly, - expires: sessionCookie.attributes.expires, - }) + const userId = await this.oauthService.handleOAuthUser(googleUser.id, googleUser.login, 'github') - return c.json({ message: 'ok' }) - } catch (error) { - console.error(error) - // the specific error message depends on the provider - if (error instanceof OAuth2RequestError) { - // invalid code - return c.body(null, 400) + const session = await this.luciaService.lucia.createSession(userId, {}) + const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id) + + setCookie(c, sessionCookie.name, sessionCookie.value, { + path: sessionCookie.attributes.path, + maxAge: + sessionCookie?.attributes?.maxAge && sessionCookie?.attributes?.maxAge < new TimeSpan(365, 'd').seconds() + ? sessionCookie.attributes.maxAge + : new TimeSpan(2, 'w').seconds(), + domain: sessionCookie.attributes.domain, + sameSite: sessionCookie.attributes.sameSite as any, + secure: sessionCookie.attributes.secure, + httpOnly: sessionCookie.attributes.httpOnly, + expires: sessionCookie.attributes.expires, + }) + + return c.json({ message: 'ok' }) + } catch (error) { + console.error(error) + // the specific error message depends on the provider + if (error instanceof OAuth2RequestError) { + // invalid code + return c.body(null, 400) + } + return c.body(null, 500) } - return c.body(null, 500) - } - }) + }) } } @@ -74,3 +127,8 @@ interface GitHubUser { id: number login: string } + +interface GoogleUser { + id: number + login: string +} diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index bf0d9aa..e06aa3e 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -1,4 +1,6 @@ import env from "$lib/server/api/common/env"; -import { GitHub } from "arctic"; +import { GitHub, Google } from "arctic"; export const github = new GitHub(env.GITHUB_CLIENT_ID, env.GITHUB_CLIENT_SECRET); + +export const google = new Google(env.GOOGLE_CLIENT_ID, env.GOOGLE_CLIENT_SECRET, `${env.ORIGIN}/auth/callback/google`); \ No newline at end of file diff --git a/src/routes/(app)/privacy/+page.svelte b/src/routes/(app)/privacy/+page.svelte index 97bcf66..4711f47 100644 --- a/src/routes/(app)/privacy/+page.svelte +++ b/src/routes/(app)/privacy/+page.svelte @@ -1,5 +1,5 @@

Privacy Policy

-

Last Updated: September 13th, 2023

+

Last Updated: September 19th, 2024

At Bored Game, we respect your privacy and are committed to protecting your personal information. We collect only the personal information that is necessary for us to provide our services to you. diff --git a/src/routes/(auth)/auth/callback/github/+server.ts b/src/routes/(auth)/auth/callback/github/+server.ts index f4c0f73..5453600 100644 --- a/src/routes/(auth)/auth/callback/github/+server.ts +++ b/src/routes/(auth)/auth/callback/github/+server.ts @@ -24,8 +24,3 @@ export async function GET(event: RequestEvent): Promise { redirect(StatusCodes.TEMPORARY_REDIRECT, '/') } - -interface GitHubUser { - id: number - login: string -} diff --git a/src/routes/(auth)/auth/callback/google/+server.ts b/src/routes/(auth)/auth/callback/google/+server.ts new file mode 100644 index 0000000..5bc0ad4 --- /dev/null +++ b/src/routes/(auth)/auth/callback/google/+server.ts @@ -0,0 +1,26 @@ +import { StatusCodes } from '$lib/constants/status-codes' +import type { RequestEvent } from '@sveltejs/kit' +import { redirect } from 'sveltekit-flash-message/server' + +export async function GET(event: RequestEvent): Promise { + const { locals, url } = event + const code = url.searchParams.get('code') + const state = url.searchParams.get('state') + console.log('code', code, 'state', state) + + const { data, error } = await locals.api.oauth.google.$get({ query: { code, state } }).then(locals.parseApiResponse) + + if (error) { + return new Response(JSON.stringify(error), { + status: 400, + }) + } + + if (!data) { + return new Response(JSON.stringify({ message: 'Invalid request' }), { + status: 400, + }) + } + + redirect(StatusCodes.TEMPORARY_REDIRECT, '/') +} diff --git a/src/routes/(auth)/login/google/+server.ts b/src/routes/(auth)/login/google/+server.ts index d9e5c33..3c2ce04 100644 --- a/src/routes/(auth)/login/google/+server.ts +++ b/src/routes/(auth)/login/google/+server.ts @@ -1,14 +1,25 @@ -import { github } from '$lib/server/auth' +import { google } from '$lib/server/auth' import { redirect } from '@sveltejs/kit' -import { generateState } from 'arctic' +import { generateCodeVerifier, generateState } from 'arctic' import type { RequestEvent } from '@sveltejs/kit' +// Google Login export async function GET(event: RequestEvent): Promise { const state = generateState() - const url = await github.createAuthorizationURL(state) + const codeVerifier = generateCodeVerifier(); - event.cookies.set('github_oauth_state', state, { + const url = await google.createAuthorizationURL(state, codeVerifier) + + event.cookies.set('google_oauth_state', state, { + path: '/', + secure: import.meta.env.PROD, + httpOnly: true, + maxAge: 60 * 10, + sameSite: 'lax', + }) + + event.cookies.set('google_oauth_code_verifier', codeVerifier, { path: '/', secure: import.meta.env.PROD, httpOnly: true, From 3aa1c77947cbae7b05880778764b087a8a27a8bc Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Fri, 20 Sep 2024 17:25:51 -0700 Subject: [PATCH 12/14] Add picture and email verified columns to the user, migrations, fixing OAuth with Google, creating types for OAuth --- drizzle.config.ts | 2 +- package.json | 12 +- pnpm-lock.yaml | 174 +- src/lib/server/api/common/types/oauth-user.ts | 9 + .../api/controllers/oauth.controller.ts | 38 +- .../migrations/0001_pink_the_enforcers.sql | 2 + .../migrations/meta/0001_snapshot.json | 1876 +++++++++++++++++ .../databases/migrations/meta/_journal.json | 7 + .../api/databases/tables/users.table.ts | 2 + src/lib/server/api/services/oauth.service.ts | 7 +- src/lib/server/api/services/users.service.ts | 20 +- src/routes/(auth)/login/google/+server.ts | 4 +- 12 files changed, 2037 insertions(+), 116 deletions(-) create mode 100644 src/lib/server/api/common/types/oauth-user.ts create mode 100644 src/lib/server/api/databases/migrations/0001_pink_the_enforcers.sql create mode 100644 src/lib/server/api/databases/migrations/meta/0001_snapshot.json diff --git a/drizzle.config.ts b/drizzle.config.ts index b90a261..e9ec449 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,5 +1,5 @@ import 'dotenv/config' -import env from '$lib/server/api/common/env' +import env from './src/lib/server/api/common/env' import { defineConfig } from 'drizzle-kit' export default defineConfig({ diff --git a/package.json b/package.json index 8caf66b..fc21126 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@faker-js/faker": "^8.4.1", "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.83.0", - "@playwright/test": "^1.47.1", + "@playwright/test": "^1.47.2", "@sveltejs/adapter-auto": "^3.2.5", "@sveltejs/enhanced-img": "^0.3.8", "@sveltejs/kit": "^2.5.28", @@ -64,13 +64,13 @@ "svelte-sequential-preprocessor": "^2.0.1", "sveltekit-flash-message": "^2.4.4", "sveltekit-rate-limiter": "^0.5.2", - "sveltekit-superforms": "^2.18.1", + "sveltekit-superforms": "^2.19.0", "tailwindcss": "^3.4.12", "ts-node": "^10.9.2", "tslib": "^2.7.0", "tsx": "^4.19.1", "typescript": "^5.6.2", - "vite": "^5.4.6", + "vite": "^5.4.7", "vitest": "^1.6.0", "zod": "^3.23.8" }, @@ -88,13 +88,13 @@ "@neondatabase/serverless": "^0.9.5", "@paralleldrive/cuid2": "^2.2.2", "@resvg/resvg-js": "^2.6.2", - "@sveltejs/adapter-node": "^5.2.3", + "@sveltejs/adapter-node": "^5.2.4", "@sveltejs/adapter-vercel": "^5.4.4", "@types/feather-icons": "^4.29.4", "@vercel/og": "^0.5.20", - "bits-ui": "^0.21.13", + "bits-ui": "^0.21.15", "boardgamegeekclient": "^1.9.1", - "bullmq": "^5.13.1", + "bullmq": "^5.13.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookie": "^0.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index daff87b..5861e10 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,11 +45,11 @@ importers: specifier: ^2.6.2 version: 2.6.2 '@sveltejs/adapter-node': - specifier: ^5.2.3 - version: 5.2.3(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))) + specifier: ^5.2.4 + version: 5.2.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))) '@sveltejs/adapter-vercel': specifier: ^5.4.4 - version: 5.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))) + version: 5.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))) '@types/feather-icons': specifier: ^4.29.4 version: 4.29.4 @@ -57,14 +57,14 @@ importers: specifier: ^0.5.20 version: 0.5.20 bits-ui: - specifier: ^0.21.13 - version: 0.21.13(svelte@5.0.0-next.175) + specifier: ^0.21.15 + version: 0.21.15(svelte@5.0.0-next.175) boardgamegeekclient: specifier: ^1.9.1 version: 1.9.1 bullmq: - specifier: ^5.13.1 - version: 5.13.1 + specifier: ^5.13.2 + version: 5.13.2 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -91,7 +91,7 @@ importers: version: 4.29.2 formsnap: specifier: ^1.0.1 - version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)) + version: 1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.19.0(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)) handlebars: specifier: ^4.7.8 version: 4.7.8 @@ -181,20 +181,20 @@ importers: specifier: ^0.83.0 version: 0.83.0(svelte@5.0.0-next.175) '@playwright/test': - specifier: ^1.47.1 - version: 1.47.1 + specifier: ^1.47.2 + version: 1.47.2 '@sveltejs/adapter-auto': specifier: ^3.2.5 - version: 3.2.5(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))) + version: 3.2.5(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))) '@sveltejs/enhanced-img': specifier: ^0.3.8 - version: 0.3.8(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + version: 0.3.8(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) '@sveltejs/kit': specifier: ^2.5.28 - version: 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + version: 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + version: 3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) '@types/cookie': specifier: ^0.6.0 version: 0.6.0 @@ -287,13 +287,13 @@ importers: version: 2.0.1 sveltekit-flash-message: specifier: ^2.4.4 - version: 2.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175) + version: 2.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175) sveltekit-rate-limiter: specifier: ^0.5.2 - version: 0.5.2(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))) + version: 0.5.2(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))) sveltekit-superforms: - specifier: ^2.18.1 - version: 2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) + specifier: ^2.19.0 + version: 2.19.0(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) tailwindcss: specifier: ^3.4.12 version: 3.4.12(ts-node@10.9.2(@types/node@20.16.5)(typescript@5.6.2)) @@ -310,8 +310,8 @@ importers: specifier: ^5.6.2 version: 5.6.2 vite: - specifier: ^5.4.6 - version: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + specifier: ^5.4.7 + version: 5.4.7(@types/node@20.16.5)(sass@1.79.1) vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.16.5)(sass@1.79.1) @@ -329,11 +329,11 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@ark/schema@0.2.0': - resolution: {integrity: sha512-IkNWCSHdjaoemMXpps4uFHEAQzwJPbTAS8K2vcQpk90sa+eNBuPSVyB/81/Qyl1VYW0iX3ceGC5NL/OznQv1jg==} + '@ark/schema@0.10.0': + resolution: {integrity: sha512-zpfXwWLOzj9aUK+dXQ6aleJAOgle4/WrHDop5CMX2M88dFQ85NdH8O0v0pvMAQnfFcaQAZ/nVDYLlBJsFc09XA==} - '@ark/util@0.1.0': - resolution: {integrity: sha512-qCLYICQoCy3kEKDVwirQp8qvxhY7NJd8BhhoHaj1l3wCFAk9NUbcDsxAkPStZEMdPI/d7NcbGJe8SWZuRG2twQ==} + '@ark/util@0.10.0': + resolution: {integrity: sha512-uK+9VU5doGMYOoOZVE+XaSs1vYACoaEJdrDkuBx26S4X7y3ChyKsPnIg/9pIw2vUySph1GkAXbvBnfVE2GmXgQ==} '@asteasolutions/zod-to-openapi@7.1.1': resolution: {integrity: sha512-lF0d1gAc0lYLO9/BAGivwTwE2Sh9h6CHuDcbk5KnGBfIuAsAkDC+Fdat4dkQY3CS/zUWKHRmFEma0B7X132Ymw==} @@ -1706,8 +1706,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.47.1': - resolution: {integrity: sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==} + '@playwright/test@1.47.2': + resolution: {integrity: sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==} engines: {node: '>=18'} hasBin: true @@ -1946,8 +1946,8 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/adapter-node@5.2.3': - resolution: {integrity: sha512-0KNrTc9NiEhB1vyVL0HiqZaW2P5JWNJgTYT5PnUZCLO9Oydx8G+6PNtJPJ/NNPyeGrn+6LwR5L8GNRvA4b5Bpw==} + '@sveltejs/adapter-node@5.2.4': + resolution: {integrity: sha512-L9Kngx1ce2SMCbyGBbRaJovl5lzdwH650SzOa50txAAssMfWLj2f8yPsA2eCX8EhT6AxD4RHLNrVa5W8VUYz8w==} peerDependencies: '@sveltejs/kit': ^2.4.0 @@ -2231,8 +2231,8 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - arktype@2.0.0-beta.0: - resolution: {integrity: sha512-fE3ssMiXjr/bLqFPzlDhRlXngdyHQreu7p7i8+dtcY1CA+f8WrVUcue6JxywhnqEJXPG4HOcIwQcC+q4VfeUMQ==} + arktype@2.0.0-rc.8: + resolution: {integrity: sha512-ByrqjptsavUCUL9ptts6BUL2LCNkVZyniOdaBw76dlBQ6gYIhYSeycuuj4gRFwcAafszOnAPD2fAqHK7bbo/Zw==} array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -2272,8 +2272,8 @@ packages: bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - bits-ui@0.21.13: - resolution: {integrity: sha512-7nmOh6Ig7ND4DXZHv1FhNsY9yUGrad0+mf3tc4YN//3MgnJT1LnHtk4HZAKgmxCOe7txSX7/39LtYHbkrXokAQ==} + bits-ui@0.21.15: + resolution: {integrity: sha512-+m5WSpJnFdCcNdXSTIVC1WYBozipO03qRh03GFWgrdxoHiolCfwW71EYG4LPCWYPG6KcTZV0Cj6iHSiZ7cdKdg==} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.118 @@ -2311,8 +2311,8 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - bullmq@5.13.1: - resolution: {integrity: sha512-9Ss2GzV+VVA2Q/+qUxQ7zxAwfLWBwc7DIUNboq0EdXGRf2Ia+Qi7BwT51rnxglu4b/0SRsSElevRT8IZc7HvtQ==} + bullmq@5.13.2: + resolution: {integrity: sha512-McGE8k3mrCvdUHdU0sHkTKDS1xr4pff+hbEKBY51wk5S6Za0gkuejYA620VQTo3Zz37E/NVWMgumwiXPQ3yZcA==} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -3702,13 +3702,13 @@ packages: pkg-types@1.2.0: resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} - playwright-core@1.47.1: - resolution: {integrity: sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==} + playwright-core@1.47.2: + resolution: {integrity: sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==} engines: {node: '>=18'} hasBin: true - playwright@1.47.1: - resolution: {integrity: sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==} + playwright@1.47.2: + resolution: {integrity: sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==} engines: {node: '>=18'} hasBin: true @@ -4490,8 +4490,8 @@ packages: peerDependencies: '@sveltejs/kit': 1.x || 2.x - sveltekit-superforms@2.18.1: - resolution: {integrity: sha512-bdWXlWfvG0AauTj8KGZ00dZGix4jxzGM5ywXFZuodJjwGyEpnfNbui4n1KX5KdIqsVoTU1cKrxt0gZnuJivJsw==} + sveltekit-superforms@2.19.0: + resolution: {integrity: sha512-WJmdYf8WpuDkl6zxdRP72R+wDefx1OhIQYKdsIQqNkFntNq0/BUrkMdUr1i7d/FbX0gS1A9GRflCx3WiYQlAXg==} peerDependencies: '@sveltejs/kit': 1.x || 2.x svelte: 3.x || 4.x || >=5.0.0-next.51 @@ -4716,8 +4716,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.6: - resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} + vite@5.4.7: + resolution: {integrity: sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -4889,12 +4889,12 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@ark/schema@0.2.0': + '@ark/schema@0.10.0': dependencies: - '@ark/util': 0.1.0 + '@ark/util': 0.10.0 optional: true - '@ark/util@0.1.0': + '@ark/util@0.10.0': optional: true '@asteasolutions/zod-to-openapi@7.1.1(zod@3.23.8)': @@ -5925,9 +5925,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.47.1': + '@playwright/test@1.47.2': dependencies: - playwright: 1.47.1 + playwright: 1.47.2 '@polka/url@1.0.0-next.25': {} @@ -6100,41 +6100,41 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sveltejs/adapter-auto@3.2.5(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))': + '@sveltejs/adapter-auto@3.2.5(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))': dependencies: - '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.3(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))': + '@sveltejs/adapter-node@5.2.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.21.2) '@rollup/plugin-json': 6.1.0(rollup@4.21.2) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.21.2) - '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) rollup: 4.21.2 - '@sveltejs/adapter-vercel@5.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))': + '@sveltejs/adapter-vercel@5.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))': dependencies: - '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) '@vercel/nft': 0.27.4 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/enhanced-img@0.3.8(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))': + '@sveltejs/enhanced-img@0.3.8(rollup@4.21.2)(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))': dependencies: magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-parse-markup: 0.1.5(svelte@5.0.0-next.175) - vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + vite: 5.4.7(@types/node@20.16.5)(sass@1.79.1) vite-imagetools: 7.0.4(rollup@4.21.2) transitivePeerDependencies: - rollup - '@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))': + '@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -6148,28 +6148,28 @@ snapshots: sirv: 2.0.4 svelte: 5.0.0-next.175 tiny-glob: 0.2.9 - vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + vite: 5.4.7(@types/node@20.16.5)(sass@1.79.1) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) debug: 4.3.6 svelte: 5.0.0-next.175 - vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + vite: 5.4.7(@types/node@20.16.5)(sass@1.79.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) debug: 4.3.6 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 svelte: 5.0.0-next.175 svelte-hmr: 0.16.0(svelte@5.0.0-next.175) - vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) - vitefu: 0.2.5(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + vite: 5.4.7(@types/node@20.16.5)(sass@1.79.1) + vitefu: 0.2.5(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) transitivePeerDependencies: - supports-color @@ -6465,10 +6465,10 @@ snapshots: dependencies: dequal: 2.0.3 - arktype@2.0.0-beta.0: + arktype@2.0.0-rc.8: dependencies: - '@ark/schema': 0.2.0 - '@ark/util': 0.1.0 + '@ark/schema': 0.10.0 + '@ark/util': 0.10.0 optional: true array-flatten@1.1.1: {} @@ -6501,7 +6501,7 @@ snapshots: dependencies: file-uri-to-path: 1.0.0 - bits-ui@0.21.13(svelte@5.0.0-next.175): + bits-ui@0.21.15(svelte@5.0.0-next.175): dependencies: '@internationalized/date': 3.5.5 '@melt-ui/svelte': 0.76.2(svelte@5.0.0-next.175) @@ -6559,7 +6559,7 @@ snapshots: builtin-modules@3.3.0: {} - bullmq@5.13.1: + bullmq@5.13.2: dependencies: cron-parser: 4.9.0 ioredis: 5.4.1 @@ -7233,11 +7233,11 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)): + formsnap@1.0.1(svelte@5.0.0-next.175)(sveltekit-superforms@2.19.0(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175)): dependencies: nanoid: 5.0.7 svelte: 5.0.0-next.175 - sveltekit-superforms: 2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) + sveltekit-superforms: 2.19.0(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175) forwarded@0.2.0: {} @@ -7953,11 +7953,11 @@ snapshots: mlly: 1.7.1 pathe: 1.1.2 - playwright-core@1.47.1: {} + playwright-core@1.47.2: {} - playwright@1.47.1: + playwright@1.47.2: dependencies: - playwright-core: 1.47.1 + playwright-core: 1.47.2 optionalDependencies: fsevents: 2.3.2 @@ -8790,19 +8790,19 @@ snapshots: magic-string: 0.30.11 zimmerframe: 1.1.2 - sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175): + sveltekit-flash-message@2.4.4(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) svelte: 5.0.0-next.175 - sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1))): + sveltekit-rate-limiter@0.5.2(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1))): dependencies: '@isaacs/ttlcache': 1.4.1 - '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) - sveltekit-superforms@2.18.1(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175): + sveltekit-superforms@2.19.0(@sveltejs/kit@2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(@types/json-schema@7.0.15)(svelte@5.0.0-next.175): dependencies: - '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)) + '@sveltejs/kit': 2.5.28(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)))(svelte@5.0.0-next.175)(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)) devalue: 5.0.0 just-clone: 6.2.0 memoize-weak: 1.0.2 @@ -8815,7 +8815,7 @@ snapshots: '@sodaru/yup-to-json-schema': 2.0.1 '@typeschema/class-validator': 0.2.0(@types/json-schema@7.0.15)(class-validator@0.14.1) '@vinejs/vine': 1.8.0 - arktype: 2.0.0-beta.0 + arktype: 2.0.0-rc.8 class-validator: 0.14.1 joi: 17.13.3 json-schema-to-ts: 3.1.1 @@ -9043,7 +9043,7 @@ snapshots: debug: 4.3.6 pathe: 1.1.2 picocolors: 1.1.0 - vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + vite: 5.4.7(@types/node@20.16.5)(sass@1.79.1) transitivePeerDependencies: - '@types/node' - less @@ -9055,7 +9055,7 @@ snapshots: - supports-color - terser - vite@5.4.6(@types/node@20.16.5)(sass@1.79.1): + vite@5.4.7(@types/node@20.16.5)(sass@1.79.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 @@ -9065,9 +9065,9 @@ snapshots: fsevents: 2.3.3 sass: 1.79.1 - vitefu@0.2.5(vite@5.4.6(@types/node@20.16.5)(sass@1.79.1)): + vitefu@0.2.5(vite@5.4.7(@types/node@20.16.5)(sass@1.79.1)): optionalDependencies: - vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + vite: 5.4.7(@types/node@20.16.5)(sass@1.79.1) vitest@1.6.0(@types/node@20.16.5)(sass@1.79.1): dependencies: @@ -9088,7 +9088,7 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.6(@types/node@20.16.5)(sass@1.79.1) + vite: 5.4.7(@types/node@20.16.5)(sass@1.79.1) vite-node: 1.6.0(@types/node@20.16.5)(sass@1.79.1) why-is-node-running: 2.3.0 optionalDependencies: diff --git a/src/lib/server/api/common/types/oauth-user.ts b/src/lib/server/api/common/types/oauth-user.ts new file mode 100644 index 0000000..9d14b0c --- /dev/null +++ b/src/lib/server/api/common/types/oauth-user.ts @@ -0,0 +1,9 @@ +export type OAuthUser = { + sub: string; + given_name?: string; + family_name?: string; + picture?: string; + username: string; + email?: string; + email_verified?: boolean; +} \ No newline at end of file diff --git a/src/lib/server/api/controllers/oauth.controller.ts b/src/lib/server/api/controllers/oauth.controller.ts index 4a7b44e..ab8edb0 100644 --- a/src/lib/server/api/controllers/oauth.controller.ts +++ b/src/lib/server/api/controllers/oauth.controller.ts @@ -7,6 +7,7 @@ import { OAuth2RequestError } from 'arctic' import { getCookie, setCookie } from 'hono/cookie' import { TimeSpan } from 'oslo' import { inject, injectable } from 'tsyringe' +import type {OAuthUser} from "$lib/server/api/common/types/oauth-user"; @injectable() export class OAuthController extends Controller { @@ -25,8 +26,6 @@ export class OAuthController extends Controller { const state = c.req.query('state')?.toString() ?? null const storedState = getCookie(c).github_oauth_state ?? null - console.log('code', code, 'state', state, 'storedState', storedState) - if (!code || !state || !storedState || state !== storedState) { return c.body(null, 400) } @@ -39,7 +38,13 @@ export class OAuthController extends Controller { }) const githubUser: GitHubUser = await githubUserResponse.json() - const userId = await this.oauthService.handleOAuthUser(githubUser.id, githubUser.login, 'github') + const oAuthUser: OAuthUser = { + sub: `${githubUser.id}`, + username: githubUser.login, + email: null + } + + const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'github') const session = await this.luciaService.lucia.createSession(userId, {}) const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id) @@ -75,23 +80,29 @@ export class OAuthController extends Controller { const storedState = getCookie(c).google_oauth_state ?? null const storedCodeVerifier = getCookie(c).google_oauth_code_verifier ?? null - console.log('code', code, 'state', state, 'storedState', storedState) - if (!code || !storedState || !storedCodeVerifier || state !== storedState) { return c.body(null, 400) } const tokens = await google.validateAuthorizationCode(code, storedCodeVerifier) - console.log('tokens', tokens) - const googleUserResponse = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', { + const googleUserResponse = await fetch("https://openidconnect.googleapis.com/v1/userinfo", { headers: { Authorization: `Bearer ${tokens.accessToken}`, }, }) - console.log('googleUserResponse', googleUserResponse) const googleUser: GoogleUser = await googleUserResponse.json() - const userId = await this.oauthService.handleOAuthUser(googleUser.id, googleUser.login, 'github') + const oAuthUser: OAuthUser = { + sub: googleUser.sub, + given_name: googleUser.given_name, + family_name: googleUser.family_name, + picture: googleUser.picture, + username: googleUser.email, + email: googleUser.email, + email_verified: googleUser.email_verified, + } + + const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'google') const session = await this.luciaService.lucia.createSession(userId, {}) const sessionCookie = this.luciaService.lucia.createSessionCookie(session.id) @@ -129,6 +140,11 @@ interface GitHubUser { } interface GoogleUser { - id: number - login: string + sub: string + name: string + given_name: string + family_name: string + picture: string + email: string + email_verified: boolean } diff --git a/src/lib/server/api/databases/migrations/0001_pink_the_enforcers.sql b/src/lib/server/api/databases/migrations/0001_pink_the_enforcers.sql new file mode 100644 index 0000000..1135b5e --- /dev/null +++ b/src/lib/server/api/databases/migrations/0001_pink_the_enforcers.sql @@ -0,0 +1,2 @@ +ALTER TABLE "users" ADD COLUMN "email_verified" boolean DEFAULT false;--> statement-breakpoint +ALTER TABLE "users" ADD COLUMN "picture" text; \ No newline at end of file diff --git a/src/lib/server/api/databases/migrations/meta/0001_snapshot.json b/src/lib/server/api/databases/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..ae9ae5b --- /dev/null +++ b/src/lib/server/api/databases/migrations/meta/0001_snapshot.json @@ -0,0 +1,1876 @@ +{ + "id": "e1230cae-67ce-4669-885a-3d3fe4462f9e", + "prevId": "4760134e-48bb-47db-b431-56903dad6e24", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "categories_cuid_unique": { + "name": "categories_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.categories_to_external_ids": { + "name": "categories_to_external_ids", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_external_ids_category_id_categories_id_fk": { + "name": "categories_to_external_ids_category_id_categories_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_external_ids_external_id_external_ids_id_fk": { + "name": "categories_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "categories_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_external_ids_category_id_external_id_pk": { + "name": "categories_to_external_ids_category_id_external_id_pk", + "columns": [ + "category_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.categories_to_games": { + "name": "categories_to_games", + "schema": "", + "columns": { + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "categories_to_games_category_id_categories_id_fk": { + "name": "categories_to_games_category_id_categories_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "categories_to_games_game_id_games_id_fk": { + "name": "categories_to_games_game_id_games_id_fk", + "tableFrom": "categories_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "categories_to_games_category_id_game_id_pk": { + "name": "categories_to_games_category_id_game_id_pk", + "columns": [ + "category_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.collection_items": { + "name": "collection_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "times_played": { + "name": "times_played", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collection_items_collection_id_collections_id_fk": { + "name": "collection_items_collection_id_collections_id_fk", + "tableFrom": "collection_items", + "tableTo": "collections", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_items_game_id_games_id_fk": { + "name": "collection_items_game_id_games_id_fk", + "tableFrom": "collection_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collection_items_cuid_unique": { + "name": "collection_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Collection'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "collections_cuid_unique": { + "name": "collections_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'password'" + }, + "secret_data": { + "name": "secret_data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "credentials_user_id_users_id_fk": { + "name": "credentials_user_id_users_id_fk", + "tableFrom": "credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.expansions": { + "name": "expansions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "expansions_base_game_id_games_id_fk": { + "name": "expansions_base_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "expansions_game_id_games_id_fk": { + "name": "expansions_game_id_games_id_fk", + "tableFrom": "expansions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "expansions_cuid_unique": { + "name": "expansions_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.external_ids": { + "name": "external_ids", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "external_id_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "external_ids_cuid_unique": { + "name": "external_ids_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.federated_identity": { + "name": "federated_identity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_provider": { + "name": "identity_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "federated_user_id": { + "name": "federated_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "federated_username": { + "name": "federated_username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federated_identity_user_id_users_id_fk": { + "name": "federated_identity_user_id_users_id_fk", + "tableFrom": "federated_identity", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year_published": { + "name": "year_published", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_players": { + "name": "min_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_players": { + "name": "max_players", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "playtime": { + "name": "playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_playtime": { + "name": "min_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_playtime": { + "name": "max_playtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_age": { + "name": "min_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_url": { + "name": "thumb_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "search_index": { + "name": "search_index", + "columns": [ + { + "expression": "(\n\t\t\t\tsetweight(to_tsvector('english', \"name\"), 'A') ||\n setweight(to_tsvector('english', \"slug\"), 'B')\n )", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_cuid_unique": { + "name": "games_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.games_to_external_ids": { + "name": "games_to_external_ids", + "schema": "", + "columns": { + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "games_to_external_ids_game_id_games_id_fk": { + "name": "games_to_external_ids_game_id_games_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "games_to_external_ids_external_id_external_ids_id_fk": { + "name": "games_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "games_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "games_to_external_ids_game_id_external_id_pk": { + "name": "games_to_external_ids_game_id_external_id_pk", + "columns": [ + "game_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics": { + "name": "mechanics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mechanics_cuid_unique": { + "name": "mechanics_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.mechanics_to_external_ids": { + "name": "mechanics_to_external_ids", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_external_ids_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_external_ids_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_external_ids_external_id_external_ids_id_fk": { + "name": "mechanics_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "mechanics_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_external_ids_mechanic_id_external_id_pk": { + "name": "mechanics_to_external_ids_mechanic_id_external_id_pk", + "columns": [ + "mechanic_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.mechanics_to_games": { + "name": "mechanics_to_games", + "schema": "", + "columns": { + "mechanic_id": { + "name": "mechanic_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mechanics_to_games_mechanic_id_mechanics_id_fk": { + "name": "mechanics_to_games_mechanic_id_mechanics_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "mechanics", + "columnsFrom": [ + "mechanic_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "mechanics_to_games_game_id_games_id_fk": { + "name": "mechanics_to_games_game_id_games_id_fk", + "tableFrom": "mechanics_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "mechanics_to_games_mechanic_id_game_id_pk": { + "name": "mechanics_to_games_mechanic_id_game_id_pk", + "columns": [ + "mechanic_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.publishers": { + "name": "publishers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "publishers_cuid_unique": { + "name": "publishers_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.publishers_to_external_ids": { + "name": "publishers_to_external_ids", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_external_ids_publisher_id_publishers_id_fk": { + "name": "publishers_to_external_ids_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_external_ids_external_id_external_ids_id_fk": { + "name": "publishers_to_external_ids_external_id_external_ids_id_fk", + "tableFrom": "publishers_to_external_ids", + "tableTo": "external_ids", + "columnsFrom": [ + "external_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_external_ids_publisher_id_external_id_pk": { + "name": "publishers_to_external_ids_publisher_id_external_id_pk", + "columns": [ + "publisher_id", + "external_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.publishers_to_games": { + "name": "publishers_to_games", + "schema": "", + "columns": { + "publisher_id": { + "name": "publisher_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "publishers_to_games_publisher_id_publishers_id_fk": { + "name": "publishers_to_games_publisher_id_publishers_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "publishers", + "columnsFrom": [ + "publisher_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "publishers_to_games_game_id_games_id_fk": { + "name": "publishers_to_games_game_id_games_id_fk", + "tableFrom": "publishers_to_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "publishers_to_games_publisher_id_game_id_pk": { + "name": "publishers_to_games_publisher_id_game_id_pk", + "columns": [ + "publisher_id", + "game_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.recovery_codes": { + "name": "recovery_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "recovery_codes_user_id_users_id_fk": { + "name": "recovery_codes_user_id_users_id_fk", + "tableFrom": "recovery_codes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_cuid_unique": { + "name": "roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_country": { + "name": "ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "two_factor_auth_enabled": { + "name": "two_factor_auth_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_two_factor_authenticated": { + "name": "is_two_factor_authenticated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initiated_time": { + "name": "initiated_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_users_id_fk": { + "name": "two_factor_user_id_users_id_fk", + "tableFrom": "two_factor", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "two_factor_cuid_unique": { + "name": "two_factor_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "two_factor_user_id_unique": { + "name": "two_factor_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + } + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "primary": { + "name": "primary", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_roles_cuid_unique": { + "name": "user_roles_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "receive_email": { + "name": "receive_email", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "picture": { + "name": "picture", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mfa_enabled": { + "name": "mfa_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'system'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_cuid_unique": { + "name": "users_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + }, + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.wishlist_items": { + "name": "wishlist_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wishlist_id": { + "name": "wishlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wishlist_items_wishlist_id_wishlists_id_fk": { + "name": "wishlist_items_wishlist_id_wishlists_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "wishlists", + "columnsFrom": [ + "wishlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wishlist_items_game_id_games_id_fk": { + "name": "wishlist_items_game_id_games_id_fk", + "tableFrom": "wishlist_items", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlist_items_cuid_unique": { + "name": "wishlist_items_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + }, + "public.wishlists": { + "name": "wishlists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cuid": { + "name": "cuid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'My Wishlist'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wishlists_user_id_users_id_fk": { + "name": "wishlists_user_id_users_id_fk", + "tableFrom": "wishlists", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wishlists_cuid_unique": { + "name": "wishlists_cuid_unique", + "nullsNotDistinct": false, + "columns": [ + "cuid" + ] + } + } + } + }, + "enums": { + "public.external_id_type": { + "name": "external_id_type", + "schema": "public", + "values": [ + "game", + "category", + "mechanic", + "publisher", + "designer", + "artist" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/lib/server/api/databases/migrations/meta/_journal.json b/src/lib/server/api/databases/migrations/meta/_journal.json index 1f88a00..6022f03 100644 --- a/src/lib/server/api/databases/migrations/meta/_journal.json +++ b/src/lib/server/api/databases/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1725489682980, "tag": "0000_volatile_warhawk", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1726877846811, + "tag": "0001_pink_the_enforcers", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/server/api/databases/tables/users.table.ts b/src/lib/server/api/databases/tables/users.table.ts index 2836dba..c244bbd 100644 --- a/src/lib/server/api/databases/tables/users.table.ts +++ b/src/lib/server/api/databases/tables/users.table.ts @@ -15,6 +15,8 @@ export const usersTable = pgTable('users', { last_name: text('last_name'), verified: boolean('verified').default(false), receive_email: boolean('receive_email').default(false), + email_verified: boolean('email_verified').default(false), + picture: text('picture'), mfa_enabled: boolean('mfa_enabled').notNull().default(false), theme: text('theme').default('system'), ...timestamps, diff --git a/src/lib/server/api/services/oauth.service.ts b/src/lib/server/api/services/oauth.service.ts index 0e62baa..8dae4f9 100644 --- a/src/lib/server/api/services/oauth.service.ts +++ b/src/lib/server/api/services/oauth.service.ts @@ -1,6 +1,7 @@ import { inject, injectable } from 'tsyringe' import { FederatedIdentityRepository } from '../repositories/federated_identity.repository' import { UsersService } from './users.service' +import type {OAuthUser} from "$lib/server/api/common/types/oauth-user"; @injectable() export class OAuthService { @@ -9,14 +10,14 @@ export class OAuthService { @inject(UsersService) private readonly usersService: UsersService, ) {} - async handleOAuthUser(oauthUserId: number, oauthUsername: string, oauthProvider: string) { - const federatedUser = await this.federatedIdentityRepository.findOneByFederatedUserIdAndProvider(`${oauthUserId}`, oauthProvider) + async handleOAuthUser(oAuthUser: OAuthUser, oauthProvider: string) { + const federatedUser = await this.federatedIdentityRepository.findOneByFederatedUserIdAndProvider(oAuthUser.sub, oauthProvider) if (federatedUser) { return federatedUser.user_id } - const user = await this.usersService.createOAuthUser(oauthUserId, oauthUsername, oauthProvider) + const user = await this.usersService.createOAuthUser(oAuthUser, oauthProvider) if (!user) { throw new Error('Failed to create user') diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index 008a41a..8298793 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -5,11 +5,12 @@ import { WishlistsRepository } from '$lib/server/api/repositories/wishlists.repo import { TokensService } from '$lib/server/api/services/tokens.service' import { UserRolesService } from '$lib/server/api/services/user_roles.service' import { inject, injectable } from 'tsyringe' -import { CredentialsType } from '../databases/tables' +import {CredentialsType, RoleName} from '../databases/tables' import { type UpdateUser, UsersRepository } from '../repositories/users.repository' import { CollectionsService } from './collections.service' import { DrizzleService } from './drizzle.service' import { WishlistsService } from './wishlists.service' +import type {OAuthUser} from "$lib/server/api/common/types/oauth-user"; @injectable() export class UsersService { @@ -58,18 +59,23 @@ export class UsersService { return null } - await this.userRolesService.addRoleToUser(createdUser.id, 'user', true, trx) + await this.userRolesService.addRoleToUser(createdUser.id, RoleName.USER, true, trx) await this.wishlistsService.createEmptyNoName(createdUser.id, trx) await this.collectionsService.createEmptyNoName(createdUser.id, trx) }) } - async createOAuthUser(oauthUserId: number, oauthUsername: string, oauthProvider: string) { + async createOAuthUser(oAuthUser: OAuthUser, oauthProvider: string) { return await this.drizzleService.db.transaction(async (trx) => { const createdUser = await this.usersRepository.create( { - username: oauthUsername, + username: oAuthUser.username || oAuthUser.username, + email: oAuthUser.email || null, + first_name: oAuthUser.given_name || null, + last_name: oAuthUser.family_name || null, + picture: oAuthUser.picture || null, + email_verified: oAuthUser.email_verified || false, }, trx, ) @@ -82,13 +88,13 @@ export class UsersService { { identity_provider: oauthProvider, user_id: createdUser.id, - federated_user_id: `${oauthUserId}`, - federated_username: oauthUsername, + federated_user_id: oAuthUser.sub, + federated_username: oAuthUser.email || oAuthUser.username, }, trx, ) - await this.userRolesService.addRoleToUser(createdUser.id, 'user', true, trx) + await this.userRolesService.addRoleToUser(createdUser.id, RoleName.USER, true, trx) await this.wishlistsService.createEmptyNoName(createdUser.id, trx) await this.collectionsService.createEmptyNoName(createdUser.id, trx) diff --git a/src/routes/(auth)/login/google/+server.ts b/src/routes/(auth)/login/google/+server.ts index 3c2ce04..98da6dd 100644 --- a/src/routes/(auth)/login/google/+server.ts +++ b/src/routes/(auth)/login/google/+server.ts @@ -9,7 +9,9 @@ export async function GET(event: RequestEvent): Promise { const state = generateState() const codeVerifier = generateCodeVerifier(); - const url = await google.createAuthorizationURL(state, codeVerifier) + const url = await google.createAuthorizationURL(state, codeVerifier, { + scopes: ["profile", "email", "openid"] + }) event.cookies.set('google_oauth_state', state, { path: '/', From 03b6bdbcf34a77c6bfcead4a3aea959707cd5fa6 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sat, 21 Sep 2024 09:37:21 -0700 Subject: [PATCH 13/14] Fixing types and using provider type. --- src/lib/server/api/common/types/{oauth-user.ts => oauth.ts} | 4 +++- src/lib/server/api/controllers/oauth.controller.ts | 4 ++-- src/lib/server/api/services/oauth.service.ts | 4 ++-- src/lib/server/api/services/users.service.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) rename src/lib/server/api/common/types/{oauth-user.ts => oauth.ts} (73%) diff --git a/src/lib/server/api/common/types/oauth-user.ts b/src/lib/server/api/common/types/oauth.ts similarity index 73% rename from src/lib/server/api/common/types/oauth-user.ts rename to src/lib/server/api/common/types/oauth.ts index 9d14b0c..60e8bbd 100644 --- a/src/lib/server/api/common/types/oauth-user.ts +++ b/src/lib/server/api/common/types/oauth.ts @@ -6,4 +6,6 @@ export type OAuthUser = { username: string; email?: string; email_verified?: boolean; -} \ No newline at end of file +} + +export type OAuthProviders = 'github' | 'google' | 'apple' \ No newline at end of file diff --git a/src/lib/server/api/controllers/oauth.controller.ts b/src/lib/server/api/controllers/oauth.controller.ts index ab8edb0..6a542d0 100644 --- a/src/lib/server/api/controllers/oauth.controller.ts +++ b/src/lib/server/api/controllers/oauth.controller.ts @@ -7,7 +7,7 @@ import { OAuth2RequestError } from 'arctic' import { getCookie, setCookie } from 'hono/cookie' import { TimeSpan } from 'oslo' import { inject, injectable } from 'tsyringe' -import type {OAuthUser} from "$lib/server/api/common/types/oauth-user"; +import type {OAuthUser} from "$lib/server/api/common/types/oauth"; @injectable() export class OAuthController extends Controller { @@ -41,7 +41,7 @@ export class OAuthController extends Controller { const oAuthUser: OAuthUser = { sub: `${githubUser.id}`, username: githubUser.login, - email: null + email: undefined } const userId = await this.oauthService.handleOAuthUser(oAuthUser, 'github') diff --git a/src/lib/server/api/services/oauth.service.ts b/src/lib/server/api/services/oauth.service.ts index 8dae4f9..ef563c6 100644 --- a/src/lib/server/api/services/oauth.service.ts +++ b/src/lib/server/api/services/oauth.service.ts @@ -1,7 +1,7 @@ import { inject, injectable } from 'tsyringe' import { FederatedIdentityRepository } from '../repositories/federated_identity.repository' import { UsersService } from './users.service' -import type {OAuthUser} from "$lib/server/api/common/types/oauth-user"; +import type {OAuthUser, OAuthProviders} from "$lib/server/api/common/types/oauth"; @injectable() export class OAuthService { @@ -10,7 +10,7 @@ export class OAuthService { @inject(UsersService) private readonly usersService: UsersService, ) {} - async handleOAuthUser(oAuthUser: OAuthUser, oauthProvider: string) { + async handleOAuthUser(oAuthUser: OAuthUser, oauthProvider: OAuthProviders) { const federatedUser = await this.federatedIdentityRepository.findOneByFederatedUserIdAndProvider(oAuthUser.sub, oauthProvider) if (federatedUser) { diff --git a/src/lib/server/api/services/users.service.ts b/src/lib/server/api/services/users.service.ts index 8298793..172908f 100644 --- a/src/lib/server/api/services/users.service.ts +++ b/src/lib/server/api/services/users.service.ts @@ -10,7 +10,7 @@ import { type UpdateUser, UsersRepository } from '../repositories/users.reposito import { CollectionsService } from './collections.service' import { DrizzleService } from './drizzle.service' import { WishlistsService } from './wishlists.service' -import type {OAuthUser} from "$lib/server/api/common/types/oauth-user"; +import type {OAuthUser} from "$lib/server/api/common/types/oauth"; @injectable() export class UsersService { From e4ac3ee7f0cadd68e1335713d755b13273626582 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sat, 21 Sep 2024 09:38:30 -0700 Subject: [PATCH 14/14] Removing simple icons dependency --- package.json | 1 - pnpm-lock.yaml | 9 --------- src/routes/(auth)/login/+page.svelte | 1 - 3 files changed, 11 deletions(-) diff --git a/package.json b/package.json index fc21126..c56b854 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,6 @@ "radix-svelte": "^0.9.0", "rate-limit-redis": "^4.2.0", "reflect-metadata": "^0.2.2", - "simple-icons": "^13.10.0", "svelte-french-toast": "^1.2.0", "svelte-lazy-loader": "^1.0.0", "tailwind-merge": "^2.5.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5861e10..b13a638 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,9 +143,6 @@ importers: reflect-metadata: specifier: ^0.2.2 version: 0.2.2 - simple-icons: - specifier: ^13.10.0 - version: 13.10.0 svelte-french-toast: specifier: ^1.2.0 version: 1.2.0(svelte@5.0.0-next.175) @@ -4225,10 +4222,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-icons@13.10.0: - resolution: {integrity: sha512-akHZxjNvq4nLlLsKxDot7A+ZYXgjhAP4GwvWEItHeKiquuWXoI8xGGfP+hzGLbTmbmi2teYmHMS45PIOFy6EFA==} - engines: {node: '>=0.12.18'} - simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -8554,8 +8547,6 @@ snapshots: signal-exit@4.1.0: {} - simple-icons@13.10.0: {} - simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 8d6ecaf..662bab1 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -9,7 +9,6 @@ import { boredState } from '$lib/stores/boredState.js' import { receive, send } from '$lib/utils/pageCrossfade' import { signInSchema } from '$lib/validations/auth' import { AlertCircle } from 'lucide-svelte' -import { siApple, siGithub, siGoogle } from 'simple-icons' import * as flashModule from 'sveltekit-flash-message/client' import { zodClient } from 'sveltekit-superforms/adapters' import { superForm } from 'sveltekit-superforms/client'