diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts index eb0d5a5..92501a0 100644 --- a/src/lib/auth-client.ts +++ b/src/lib/auth-client.ts @@ -7,6 +7,13 @@ import type { auth } from '$lib/server/auth'; const accountNumberClient = { id: 'account-number', getActions: ($fetch) => ({ + createAccount: async (name?: string) => + $fetch('/create-account', { + method: 'POST', + body: { + name, + }, + }), signInAccountNumber: async (accountNumber: string) => $fetch('/sign-in/account-number', { method: 'POST', diff --git a/src/lib/components/app/auth-dialog.svelte b/src/lib/components/app/auth-dialog.svelte index 3b5605c..15209e6 100644 --- a/src/lib/components/app/auth-dialog.svelte +++ b/src/lib/components/app/auth-dialog.svelte @@ -9,18 +9,21 @@ import Input from '../ui/input/input.svelte'; import Key from '@lucide/svelte/icons/key'; import Trash2 from '@lucide/svelte/icons/trash-2'; + import ArrowLeft from '@lucide/svelte/icons/arrow-left'; import type { Passkey } from '@better-auth/passkey'; - + const session = authClient.useSession(); let open = $state(false); let accountNumber = $state(''); + let name = $state(''); let authMessage = $state(''); let busyAction = $state(null); let passkeyMessage = $state(''); const user = $derived($session.data?.user); let passkeys = $state([]); let loadedPasskeysForUserId = $state(null); + let authScreen = $state<'login' | 'create'>('login'); let passkeyName = $state(''); @@ -48,12 +51,11 @@ } }); - const runAuthAction = async ( action: string, request: () => Promise<{ error?: { message?: string | null } | null }>, fallbackMessage: string, - onSuccess?: () => void, + onSuccess?: () => void | Promise ) => { busyAction = action; authMessage = ''; @@ -67,7 +69,7 @@ return; } - onSuccess?.(); + await onSuccess?.(); open = false; }; @@ -78,11 +80,20 @@ 'Account number sign-in failed', () => { accountNumber = ''; - }, + } ); const createAccount = () => - runAuthAction('create-account', () => authClient.signIn.anonymous(), 'Account creation failed'); + runAuthAction( + 'create-account', + () => authClient.createAccount(name), + 'Account creation failed', + async () => { + await session.get().refetch(); + name = ''; + authScreen = 'login'; + } + ); const signInWithPasskey = () => runAuthAction( @@ -91,7 +102,7 @@ authClient.signIn.passkey({ autoFill: true, }), - 'Passkey sign-in failed', + 'Passkey sign-in failed' ); const signOut = () => runAuthAction('sign-out', () => authClient.signOut(), 'Sign out failed'); @@ -143,7 +154,7 @@ - +
+ +
+

{passkey.name}

+

+ Added on {new Date(passkey.createdAt).toLocaleDateString()} +

+
+ +
{/each} @@ -221,41 +236,81 @@ {:else}
-
- -
- + {#if authScreen === 'login'} +
+ +
+ + +
+
+ + +
+
+ or +
+
+ + + {#if authMessage} +

{authMessage}

+ {/if} + {:else if authScreen === 'create'} + + + +
+
-
- - -
-
- or -
-
- - - {#if authMessage} -

{authMessage}

{/if}
{/if} diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 3d04738..23321cd 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -16,10 +16,59 @@ const generateAccountNumber = () => const generateOpaqueIdentifier = () => `${crypto.randomUUID()}@internal.invalid`; +const getAnonymousDisplayName = (name?: string | null) => { + const trimmedName = name?.trim(); + return trimmedName ? trimmedName : 'Chillhop listener'; +}; + +const createAnonymousSession = async (ctx: any, name?: string | null) => { + const user = await ctx.context.internalAdapter.createUser({ + email: generateOpaqueIdentifier(), + emailVerified: false, + isAnonymous: true, + name: getAnonymousDisplayName(name), + createdAt: new Date(), + updatedAt: new Date(), + }); + + if (!user) { + throw new APIError('INTERNAL_SERVER_ERROR', { + message: 'Failed to create user', + }); + } + + const session = await ctx.context.internalAdapter.createSession(user.id); + if (!session) { + throw new APIError('INTERNAL_SERVER_ERROR', { + message: 'Failed to create session', + }); + } + + await setSessionCookie( + ctx, + { session, user } as Parameters[1], + ); + + return { + token: session.token, + user, + }; +}; + const accountNumber = () => ({ id: 'account-number', endpoints: { + createAccount: createAuthEndpoint( + '/create-account', + { + method: 'POST', + body: z.object({ + name: z.string().trim().max(100).optional(), + }), + }, + async (ctx) => ctx.json(await createAnonymousSession(ctx, ctx.body.name)), + ), signInAccountNumber: createAuthEndpoint( '/sign-in/account-number', { @@ -91,7 +140,7 @@ const authConfig = { }, plugins: [ anonymous({ - generateName: () => 'Chillhop listener', + generateName: () => getAnonymousDisplayName(), generateRandomEmail: generateOpaqueIdentifier, }), accountNumber(),