mirror of
https://github.com/SrIzan10/lofi.git
synced 2026-06-06 00:56:53 +00:00
feat: initial auth dialog panel
This commit is contained in:
2
bun.lock
2
bun.lock
@@ -15,7 +15,7 @@
|
|||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"better-auth": "~1.4.21",
|
"better-auth": "~1.4.21",
|
||||||
"bits-ui": "^1.3.19",
|
"bits-ui": "^1.4.7",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
"drizzle-orm": "^0.45.2",
|
"drizzle-orm": "^0.45.2",
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://next.shadcn-svelte.com/schema.json",
|
"$schema": "https://next.shadcn-svelte.com/schema.json",
|
||||||
"style": "new-york",
|
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "tailwind.config.ts",
|
|
||||||
"css": "src/app.css",
|
"css": "src/app.css",
|
||||||
"baseColor": "neutral"
|
"baseColor": "neutral"
|
||||||
},
|
},
|
||||||
@@ -10,8 +8,9 @@
|
|||||||
"components": "$lib/components",
|
"components": "$lib/components",
|
||||||
"utils": "$lib/utils",
|
"utils": "$lib/utils",
|
||||||
"ui": "$lib/components/ui",
|
"ui": "$lib/components/ui",
|
||||||
"hooks": "$lib/hooks"
|
"hooks": "$lib/hooks",
|
||||||
|
"lib": "$lib"
|
||||||
},
|
},
|
||||||
"typescript": true,
|
"typescript": true,
|
||||||
"registry": "https://next.shadcn-svelte.com/registry"
|
"registry": "https://tw3.shadcn-svelte.com/registry/new-york"
|
||||||
}
|
}
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -25,6 +25,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@better-auth/cli": "~1.4.21",
|
"@better-auth/cli": "~1.4.21",
|
||||||
|
"@better-auth/passkey": "~1.4.21",
|
||||||
"@cloudflare/workers-types": "^4.20250517.0",
|
"@cloudflare/workers-types": "^4.20250517.0",
|
||||||
"@lucide/svelte": "^0.492.0",
|
"@lucide/svelte": "^0.492.0",
|
||||||
"@sveltejs/adapter-cloudflare": "^7.2.6",
|
"@sveltejs/adapter-cloudflare": "^7.2.6",
|
||||||
@@ -32,9 +33,12 @@
|
|||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"bits-ui": "^1.3.19",
|
"better-auth": "~1.4.21",
|
||||||
|
"bits-ui": "^1.4.7",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
|
"drizzle-orm": "^0.45.2",
|
||||||
|
"mode-watcher": "^1.0.5",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
@@ -46,11 +50,7 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vite": "^6.0.0",
|
"vite": "^6.0.0",
|
||||||
"wrangler": "^4.63.0",
|
"wrangler": "^4.63.0"
|
||||||
"better-auth": "~1.4.21",
|
|
||||||
"drizzle-orm": "^0.45.2",
|
|
||||||
"mode-watcher": "^1.0.5",
|
|
||||||
"@better-auth/passkey": "~1.4.21"
|
|
||||||
},
|
},
|
||||||
"packageManager": "bun@1.3.5"
|
"packageManager": "bun@1.3.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
|
import type { BetterAuthClientPlugin } from 'better-auth/client';
|
||||||
|
import { anonymousClient, inferAdditionalFields } from 'better-auth/client/plugins';
|
||||||
import { createAuthClient } from 'better-auth/svelte';
|
import { createAuthClient } from 'better-auth/svelte';
|
||||||
import { passkeyClient } from '@better-auth/passkey/client';
|
import { passkeyClient } from '@better-auth/passkey/client';
|
||||||
|
import type { auth } from '$lib/server/auth';
|
||||||
|
|
||||||
|
const accountNumberClient = {
|
||||||
|
id: 'account-number',
|
||||||
|
getActions: ($fetch) => ({
|
||||||
|
signInAccountNumber: async (accountNumber: string) =>
|
||||||
|
$fetch('/sign-in/account-number', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
accountNumber,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
} satisfies BetterAuthClientPlugin;
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
plugins: [passkeyClient()],
|
plugins: [inferAdditionalFields<typeof auth>(), anonymousClient(), passkeyClient(), accountNumberClient],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { authClient } from "@/auth-client";
|
import AuthDialog from './auth-dialog.svelte';
|
||||||
import Button from "../ui/button/button.svelte";
|
|
||||||
|
|
||||||
const session = authClient.useSession();
|
|
||||||
const getAccountNumber = () => {
|
|
||||||
const user = $session.data?.user as
|
|
||||||
| {
|
|
||||||
accountNumber?: string;
|
|
||||||
account_number?: string;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
return user?.accountNumber ?? user?.account_number ?? null;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex absolute top-0 right-0 items-center p-4 bg-white/10 backdrop-blur-lg rounded-bl-xl shadow-lg *:text-bold space-x-2"
|
class="flex absolute top-0 right-0 items-center p-4 bg-white/10 backdrop-blur-lg rounded-bl-xl shadow-lg *:text-bold space-x-2"
|
||||||
>
|
>
|
||||||
{#if $session.data}
|
<AuthDialog />
|
||||||
<div class="text-right">
|
|
||||||
<p>Signed in as {$session.data.user.name}</p>
|
|
||||||
{#if getAccountNumber()}
|
|
||||||
<p class="text-xs opacity-80">#{getAccountNumber()}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="px-3 py-1 rounded-md bg-red-500 text-white hover:bg-red-600 transition"
|
|
||||||
on:click={() => authClient.signOut()}
|
|
||||||
>
|
|
||||||
Sign out
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<Button href="/demo/better-auth/login">
|
|
||||||
Sign in
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
261
src/lib/components/app/auth-dialog.svelte
Normal file
261
src/lib/components/app/auth-dialog.svelte
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||||
|
import { authClient } from '$lib';
|
||||||
|
import LogIn from '@lucide/svelte/icons/log-in';
|
||||||
|
import Settings from '@lucide/svelte/icons/settings-2';
|
||||||
|
import UserRound from '@lucide/svelte/icons/user-round';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
import Label from '../ui/label/label.svelte';
|
||||||
|
import Input from '../ui/input/input.svelte';
|
||||||
|
import Key from '@lucide/svelte/icons/key';
|
||||||
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
|
import type { Passkey } from '@better-auth/passkey';
|
||||||
|
|
||||||
|
const session = authClient.useSession();
|
||||||
|
|
||||||
|
let open = $state(false);
|
||||||
|
let accountNumber = $state('');
|
||||||
|
let authMessage = $state('');
|
||||||
|
let busyAction = $state<string | null>(null);
|
||||||
|
let passkeyMessage = $state('');
|
||||||
|
const user = $derived($session.data?.user);
|
||||||
|
let passkeys = $state<Passkey[]>([]);
|
||||||
|
let loadedPasskeysForUserId = $state<string | null>(null);
|
||||||
|
|
||||||
|
let passkeyName = $state('');
|
||||||
|
|
||||||
|
const loadPasskeys = async () => {
|
||||||
|
if (!user) {
|
||||||
|
passkeys = [];
|
||||||
|
loadedPasskeysForUserId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await authClient.passkey.listUserPasskeys();
|
||||||
|
passkeys = result.data ?? [];
|
||||||
|
loadedPasskeysForUserId = user.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!user) {
|
||||||
|
passkeys = [];
|
||||||
|
loadedPasskeysForUserId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedPasskeysForUserId !== user.id) {
|
||||||
|
loadPasskeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const runAuthAction = async (
|
||||||
|
action: string,
|
||||||
|
request: () => Promise<{ error?: { message?: string | null } | null }>,
|
||||||
|
fallbackMessage: string,
|
||||||
|
onSuccess?: () => void,
|
||||||
|
) => {
|
||||||
|
busyAction = action;
|
||||||
|
authMessage = '';
|
||||||
|
|
||||||
|
const result = await request();
|
||||||
|
|
||||||
|
busyAction = null;
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
authMessage = result.error.message || fallbackMessage;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess?.();
|
||||||
|
open = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const signInWithAccountNumber = () =>
|
||||||
|
runAuthAction(
|
||||||
|
'account-number',
|
||||||
|
() => authClient.signInAccountNumber(accountNumber.replace(/\D/g, '')),
|
||||||
|
'Account number sign-in failed',
|
||||||
|
() => {
|
||||||
|
accountNumber = '';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const createAccount = () =>
|
||||||
|
runAuthAction('create-account', () => authClient.signIn.anonymous(), 'Account creation failed');
|
||||||
|
|
||||||
|
const signInWithPasskey = () =>
|
||||||
|
runAuthAction(
|
||||||
|
'passkey-sign-in',
|
||||||
|
() =>
|
||||||
|
authClient.signIn.passkey({
|
||||||
|
autoFill: true,
|
||||||
|
}),
|
||||||
|
'Passkey sign-in failed',
|
||||||
|
);
|
||||||
|
|
||||||
|
const signOut = () => runAuthAction('sign-out', () => authClient.signOut(), 'Sign out failed');
|
||||||
|
|
||||||
|
const addPasskey = async () => {
|
||||||
|
if (!passkeyName) {
|
||||||
|
passkeyMessage = 'Please enter a name for your passkey';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
busyAction = 'add-passkey';
|
||||||
|
passkeyMessage = '';
|
||||||
|
|
||||||
|
const result = await authClient.passkey.addPasskey({
|
||||||
|
name: passkeyName,
|
||||||
|
authenticatorAttachment: 'platform',
|
||||||
|
});
|
||||||
|
|
||||||
|
busyAction = null;
|
||||||
|
if (result.error) {
|
||||||
|
passkeyMessage = result.error.message || 'Failed to add passkey';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadPasskeys();
|
||||||
|
passkeyMessage = 'Passkey added to your account.';
|
||||||
|
passkeyName = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePasskey = async (id: string) => {
|
||||||
|
busyAction = `delete-passkey-${id}`;
|
||||||
|
passkeyMessage = '';
|
||||||
|
|
||||||
|
const result = await authClient.passkey.deletePasskey({ id });
|
||||||
|
|
||||||
|
busyAction = null;
|
||||||
|
if (result.error) {
|
||||||
|
passkeyMessage = result.error.message || 'Failed to delete passkey';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadPasskeys();
|
||||||
|
passkeyMessage = 'Passkey removed from your account.';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root bind:open>
|
||||||
|
<Dialog.Trigger>
|
||||||
|
<Button class="flex items-center gap-2">
|
||||||
|
{#if user}
|
||||||
|
<Settings />
|
||||||
|
Settings
|
||||||
|
{:else}
|
||||||
|
<LogIn />
|
||||||
|
Sign in
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>
|
||||||
|
{user ? 'Your account' : 'Log into lofi.srizan.dev'}
|
||||||
|
</Dialog.Title>
|
||||||
|
</Dialog.Header>
|
||||||
|
{#if user}
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="rounded-lg border border-white/10 bg-white/5 p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="rounded-full bg-white/10 p-2">
|
||||||
|
<UserRound class="size-4" />
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<p class="font-medium">{user.name}</p>
|
||||||
|
<p class="text-sm opacity-70">Account #{user.accountNumber}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if passkeys.length > 0}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-medium">Your passkeys</p>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
{#each passkeys as passkey (passkey.id)}
|
||||||
|
<div class="flex items-center gap-2 rounded-lg border border-white/10 bg-white/5 p-3">
|
||||||
|
<Key class="size-4" />
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p>{passkey.name}</p>
|
||||||
|
<p class="text-sm opacity-70">Added on {new Date(passkey.createdAt).toLocaleDateString()}</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
aria-label={`Delete passkey ${passkey.name}`}
|
||||||
|
onclick={() => deletePasskey(passkey.id)}
|
||||||
|
disabled={busyAction === `delete-passkey-${passkey.id}`}
|
||||||
|
>
|
||||||
|
{#if busyAction === `delete-passkey-${passkey.id}`}
|
||||||
|
...
|
||||||
|
{:else}
|
||||||
|
<Trash2 class="size-4" />
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-sm font-medium">Security</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="passkeyName"
|
||||||
|
bind:value={passkeyName}
|
||||||
|
placeholder="Passkey name (e.g. 'My phone')"
|
||||||
|
/>
|
||||||
|
<Button onclick={addPasskey} disabled={busyAction === 'add-passkey'}>
|
||||||
|
{busyAction === 'add-passkey' ? 'Waiting for passkey...' : 'Add a passkey'}
|
||||||
|
</Button>
|
||||||
|
{#if passkeyMessage}
|
||||||
|
<p class="text-sm opacity-80">{passkeyMessage}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="ghost" onclick={signOut}>Sign out</Button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex w-full flex-col gap-1.5">
|
||||||
|
<Label for="accountNumber">Account Number</Label>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="accountNumber"
|
||||||
|
bind:value={accountNumber}
|
||||||
|
placeholder="7276769420"
|
||||||
|
autocomplete="one-time-code webauthn"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="icon"
|
||||||
|
aria-label="Sign in with account number"
|
||||||
|
onclick={signInWithAccountNumber}
|
||||||
|
disabled={busyAction === 'account-number'}
|
||||||
|
>
|
||||||
|
<Key />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button type="button" onclick={signInWithAccountNumber} disabled={busyAction === 'account-number'}>
|
||||||
|
{busyAction === 'account-number' ? 'Signing in...' : 'Sign in with account number'}
|
||||||
|
</Button>
|
||||||
|
<Button type="button" onclick={signInWithPasskey} disabled={busyAction === 'passkey-sign-in'} variant="ghost">
|
||||||
|
{busyAction === 'passkey-sign-in' ? 'Waiting for passkey...' : 'Sign in with passkey'}
|
||||||
|
</Button>
|
||||||
|
<Button type="button" onclick={createAccount} disabled={busyAction === 'create-account'} variant="ghost">
|
||||||
|
{busyAction === 'create-account' ? 'Creating account...' : 'Create account number'}
|
||||||
|
</Button>
|
||||||
|
{#if authMessage}
|
||||||
|
<p class="text-sm text-red-400">{authMessage}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
7
src/lib/components/ui/input/index.ts
Normal file
7
src/lib/components/ui/input/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./input.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Input,
|
||||||
|
};
|
||||||
46
src/lib/components/ui/input/input.svelte
Normal file
46
src/lib/components/ui/input/input.svelte
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
|
||||||
|
import type { WithElementRef } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
||||||
|
|
||||||
|
type Props = WithElementRef<
|
||||||
|
Omit<HTMLInputAttributes, "type"> &
|
||||||
|
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
||||||
|
>;
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
value = $bindable(),
|
||||||
|
type,
|
||||||
|
files = $bindable(),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if type === "file"}
|
||||||
|
<input
|
||||||
|
bind:this={ref}
|
||||||
|
class={cn(
|
||||||
|
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
type="file"
|
||||||
|
bind:files
|
||||||
|
bind:value
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
bind:this={ref}
|
||||||
|
class={cn(
|
||||||
|
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{type}
|
||||||
|
bind:value
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
7
src/lib/components/ui/label/index.ts
Normal file
7
src/lib/components/ui/label/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./label.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Label,
|
||||||
|
};
|
||||||
19
src/lib/components/ui/label/label.svelte
Normal file
19
src/lib/components/ui/label/label.svelte
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Label as LabelPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
class: className,
|
||||||
|
...restProps
|
||||||
|
}: LabelPrimitive.RootProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
bind:ref
|
||||||
|
class={cn(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
@@ -14,6 +14,8 @@ import * as z from 'zod';
|
|||||||
const generateAccountNumber = () =>
|
const generateAccountNumber = () =>
|
||||||
Array.from(crypto.getRandomValues(new Uint8Array(16)), (value) => (value % 10).toString()).join('');
|
Array.from(crypto.getRandomValues(new Uint8Array(16)), (value) => (value % 10).toString()).join('');
|
||||||
|
|
||||||
|
const generateOpaqueIdentifier = () => `${crypto.randomUUID()}@internal.invalid`;
|
||||||
|
|
||||||
const accountNumber = () =>
|
const accountNumber = () =>
|
||||||
({
|
({
|
||||||
id: 'account-number',
|
id: 'account-number',
|
||||||
@@ -35,17 +37,7 @@ const accountNumber = () =>
|
|||||||
value: ctx.body.accountNumber,
|
value: ctx.body.accountNumber,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})) as
|
})) as (Record<string, any> | null);
|
||||||
| ({
|
|
||||||
id: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
email: string;
|
|
||||||
emailVerified: boolean;
|
|
||||||
name: string;
|
|
||||||
image?: string | null;
|
|
||||||
} & Record<string, any>)
|
|
||||||
| null;
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new APIError('UNAUTHORIZED', {
|
throw new APIError('UNAUTHORIZED', {
|
||||||
@@ -60,7 +52,10 @@ const accountNumber = () =>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await setSessionCookie(ctx, { session, user });
|
await setSessionCookie(
|
||||||
|
ctx,
|
||||||
|
{ session, user } as Parameters<typeof setSessionCookie>[1],
|
||||||
|
);
|
||||||
|
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
token: session.token,
|
token: session.token,
|
||||||
@@ -74,7 +69,6 @@ const accountNumber = () =>
|
|||||||
const authConfig = {
|
const authConfig = {
|
||||||
baseURL: env.ORIGIN,
|
baseURL: env.ORIGIN,
|
||||||
secret: env.BETTER_AUTH_SECRET,
|
secret: env.BETTER_AUTH_SECRET,
|
||||||
emailAndPassword: { enabled: false },
|
|
||||||
user: {
|
user: {
|
||||||
additionalFields: {
|
additionalFields: {
|
||||||
accountNumber: {
|
accountNumber: {
|
||||||
@@ -98,7 +92,7 @@ const authConfig = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
anonymous({
|
anonymous({
|
||||||
generateName: () => 'Chillhop listener',
|
generateName: () => 'Chillhop listener',
|
||||||
emailDomainName: 'accounts.chillhop.local',
|
generateRandomEmail: generateOpaqueIdentifier,
|
||||||
}),
|
}),
|
||||||
accountNumber(),
|
accountNumber(),
|
||||||
passkey({
|
passkey({
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { fail, redirect } from '@sveltejs/kit';
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
import type { Actions } from './$types';
|
import type { Actions, PageServerLoad } from './$types';
|
||||||
import type { PageServerLoad } from './$types';
|
|
||||||
|
|
||||||
import { APIError } from 'better-auth';
|
import { APIError } from 'better-auth';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user