feat(auth): implementar passkeys e gerenciamento em ajustes

This commit is contained in:
Felipe Coutinho
2026-03-02 01:33:05 +00:00
parent ff382a0ca7
commit 3d3a9e1414
13 changed files with 3164 additions and 7 deletions

View File

@@ -5,6 +5,25 @@ Todas as mudanças notáveis deste projeto serão documentadas neste arquivo.
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/), O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/). e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).
## [1.7.6] - 2026-03-02
### Adicionado
- Suporte completo a Passkeys (WebAuthn) com plugin `@better-auth/passkey` no servidor e `passkeyClient` no cliente de autenticação
- Tabela `passkey` no banco de dados para persistência de credenciais WebAuthn vinculadas ao usuário
- Nova aba **Passkeys** em `/ajustes` com gerenciamento de credenciais: listar, adicionar, renomear e remover passkeys
- Ação de login com passkey na tela de autenticação (`/login`)
### Alterado
- `PasskeysForm` refatorado para melhor experiência com React 19/Next 16: detecção de suporte do navegador, bloqueio de ações simultâneas e atualização da lista sem loader global após operações
### Corrigido
- Login com passkey na tela de autenticação agora fica disponível em navegadores com WebAuthn, mesmo sem suporte a Conditional UI
- Listagem de passkeys em Ajustes agora trata `createdAt` ausente sem gerar data inválida na interface
- Migração `0017_previous_warstar` tornou-se idempotente para colunas de `preferencias_usuario` com `IF NOT EXISTS`, evitando falha em bancos já migrados
## [1.7.5] - 2026-02-28 ## [1.7.5] - 2026-02-28
### Adicionado ### Adicionado

View File

@@ -4,6 +4,7 @@ import { redirect } from "next/navigation";
import { CompanionTab } from "@/components/ajustes/companion-tab"; import { CompanionTab } from "@/components/ajustes/companion-tab";
import { DeleteAccountForm } from "@/components/ajustes/delete-account-form"; import { DeleteAccountForm } from "@/components/ajustes/delete-account-form";
import { PasskeysForm } from "@/components/ajustes/passkeys-form";
import { PreferencesForm } from "@/components/ajustes/preferences-form"; import { PreferencesForm } from "@/components/ajustes/preferences-form";
import { UpdateEmailForm } from "@/components/ajustes/update-email-form"; import { UpdateEmailForm } from "@/components/ajustes/update-email-form";
import { UpdateNameForm } from "@/components/ajustes/update-name-form"; import { UpdateNameForm } from "@/components/ajustes/update-name-form";
@@ -39,6 +40,7 @@ export default async function Page() {
<TabsTrigger value="companion">Companion</TabsTrigger> <TabsTrigger value="companion">Companion</TabsTrigger>
<TabsTrigger value="nome">Alterar nome</TabsTrigger> <TabsTrigger value="nome">Alterar nome</TabsTrigger>
<TabsTrigger value="senha">Alterar senha</TabsTrigger> <TabsTrigger value="senha">Alterar senha</TabsTrigger>
<TabsTrigger value="passkeys">Passkeys</TabsTrigger>
<TabsTrigger value="email">Alterar e-mail</TabsTrigger> <TabsTrigger value="email">Alterar e-mail</TabsTrigger>
<TabsTrigger value="deletar" className="text-destructive"> <TabsTrigger value="deletar" className="text-destructive">
Deletar conta Deletar conta
@@ -114,6 +116,21 @@ export default async function Page() {
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value="passkeys" className="mt-4">
<Card className="p-6">
<div className="space-y-4">
<div>
<h2 className="text-lg font-bold mb-1">Passkeys</h2>
<p className="text-sm text-muted-foreground mb-4">
Passkeys permitem login sem senha, usando biometria (Face ID,
Touch ID, Windows Hello) ou chaves de segurança.
</p>
</div>
<PasskeysForm />
</div>
</Card>
</TabsContent>
<TabsContent value="email" className="mt-4"> <TabsContent value="email" className="mt-4">
<Card className="p-6"> <Card className="p-6">
<div className="space-y-4"> <div className="space-y-4">

View File

@@ -105,7 +105,7 @@
/* Base surfaces - warm dark with consistent hue family */ /* Base surfaces - warm dark with consistent hue family */
--background: oklch(18.5% 0.002 70); --background: oklch(18.5% 0.002 70);
--foreground: oklch(92% 0.015 80); --foreground: oklch(92% 0.015 80);
--card: oklch(22.717% 0.00244 67.467); --card: oklch(0.13 0.01 64.18);
--card-foreground: oklch(92% 0.015 80); --card-foreground: oklch(92% 0.015 80);
--popover: oklch(24% 0.003 70); --popover: oklch(24% 0.003 70);
--popover-foreground: oklch(92% 0.015 80); --popover-foreground: oklch(92% 0.015 80);

View File

@@ -0,0 +1,418 @@
"use client";
import {
RiAddLine,
RiAlertLine,
RiDeleteBinLine,
RiFingerprintLine,
RiLoader4Line,
RiPencilLine,
} from "@remixicon/react";
import { formatDistanceToNow } from "date-fns";
import { ptBR } from "date-fns/locale";
import { useCallback, useEffect, useState } from "react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { authClient } from "@/lib/auth/client";
interface Passkey {
id: string;
name: string | null;
deviceType: string;
createdAt: Date | null;
}
export function PasskeysForm() {
const [passkeys, setPasskeys] = useState<Passkey[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [passkeySupported, setPasskeySupported] = useState(false);
// Add passkey
const [isAddOpen, setIsAddOpen] = useState(false);
const [addName, setAddName] = useState("");
const [isAdding, setIsAdding] = useState(false);
// Rename passkey
const [editingId, setEditingId] = useState<string | null>(null);
const [editName, setEditName] = useState("");
const [isRenaming, setIsRenaming] = useState(false);
// Delete passkey
const [deleteId, setDeleteId] = useState<string | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const isMutating = isAdding || isRenaming || isDeleting;
const fetchPasskeys = useCallback(
async (options?: { showLoader?: boolean }) => {
const showLoader = options?.showLoader ?? true;
if (showLoader) setIsLoading(true);
setError(null);
try {
const { data, error: fetchError } =
await authClient.passkey.listUserPasskeys();
if (fetchError) {
setError(fetchError.message || "Erro ao carregar passkeys.");
return;
}
setPasskeys(
(data ?? []).map((p) => ({
id: p.id,
name: p.name,
deviceType: p.deviceType,
createdAt: p.createdAt ? new Date(p.createdAt) : null,
})),
);
} catch {
setError("Erro ao carregar passkeys.");
} finally {
if (showLoader) setIsLoading(false);
}
},
[],
);
useEffect(() => {
if (typeof window === "undefined") return;
setPasskeySupported(typeof PublicKeyCredential !== "undefined");
fetchPasskeys();
}, [fetchPasskeys]);
const handleAdd = async () => {
if (!passkeySupported) {
setError("Passkeys não são suportadas neste navegador/dispositivo.");
return;
}
setIsAdding(true);
setError(null);
try {
const { error: addError } = await authClient.passkey.addPasskey({
name: addName.trim() || undefined,
});
if (addError) {
setError(addError.message || "Erro ao registrar passkey.");
return;
}
setAddName("");
setIsAddOpen(false);
await fetchPasskeys({ showLoader: false });
} catch {
setError("Erro ao registrar passkey.");
} finally {
setIsAdding(false);
}
};
const handleRename = async (id: string) => {
if (!editName.trim()) return;
setIsRenaming(true);
setError(null);
try {
const { error: renameError } = await authClient.passkey.updatePasskey({
id,
name: editName.trim(),
});
if (renameError) {
setError(renameError.message || "Erro ao renomear passkey.");
return;
}
setEditingId(null);
setEditName("");
await fetchPasskeys({ showLoader: false });
} catch {
setError("Erro ao renomear passkey.");
} finally {
setIsRenaming(false);
}
};
const handleDelete = async () => {
if (!deleteId) return;
setIsDeleting(true);
setError(null);
try {
const { error: deleteError } = await authClient.passkey.deletePasskey({
id: deleteId,
});
if (deleteError) {
setError(deleteError.message || "Erro ao remover passkey.");
return;
}
setDeleteId(null);
await fetchPasskeys({ showLoader: false });
} catch {
setError("Erro ao remover passkey.");
} finally {
setIsDeleting(false);
}
};
const startEditing = (passkey: Passkey) => {
setEditingId(passkey.id);
setEditName(passkey.name || "");
};
const cancelEditing = () => {
setEditingId(null);
setEditName("");
};
const deviceTypeLabel = (type: string) => {
switch (type) {
case "singleDevice":
return "Dispositivo único";
case "multiDevice":
return "Multi-dispositivo";
default:
return type;
}
};
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-medium">Suas passkeys</h3>
<p className="text-sm text-muted-foreground">
Gerencie suas passkeys para login sem senha.
</p>
</div>
<Dialog
open={isAddOpen}
onOpenChange={(open) => {
if (!open) {
setAddName("");
setError(null);
}
setIsAddOpen(open);
}}
>
<DialogTrigger asChild>
<Button size="sm" disabled={isMutating || !passkeySupported}>
<RiAddLine className="h-4 w-4 mr-1" />
Adicionar
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Registrar Passkey</DialogTitle>
<DialogDescription>
um nome para identificar esta passkey (opcional). Em seguida,
seu navegador solicitará a confirmação biométrica.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="passkeyName">Nome (opcional)</Label>
<Input
id="passkeyName"
placeholder="Ex: MacBook Pro, iPhone..."
value={addName}
onChange={(e) => setAddName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
void handleAdd();
}
}}
disabled={isAdding}
/>
</div>
{error && (
<div className="flex items-center gap-2 text-sm text-destructive">
<RiAlertLine className="h-4 w-4" />
{error}
</div>
)}
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsAddOpen(false)}
disabled={isAdding}
>
Cancelar
</Button>
<Button onClick={handleAdd} disabled={isAdding}>
{isAdding ? (
<>
<RiLoader4Line className="h-4 w-4 animate-spin mr-1" />
Registrando...
</>
) : (
"Registrar"
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
{error && !isAddOpen && (
<div className="flex items-center gap-2 text-sm text-destructive">
<RiAlertLine className="h-4 w-4 shrink-0" />
{error}
</div>
)}
{!passkeySupported && !isLoading && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<RiAlertLine className="h-4 w-4 shrink-0" />
Este navegador/dispositivo não suporta passkeys.
</div>
)}
{isLoading ? (
<div className="flex items-center justify-center py-8">
<RiLoader4Line className="h-5 w-5 animate-spin text-muted-foreground" />
</div>
) : passkeys.length === 0 ? (
<div className="flex items-center gap-3 py-4 text-muted-foreground">
<RiFingerprintLine className="h-5 w-5" />
<p className="text-sm">
Nenhuma passkey cadastrada. Adicione uma para login sem senha.
</p>
</div>
) : (
<div className="divide-y py-2">
{passkeys.map((pk) => (
<div
key={pk.id}
className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
>
<div className="flex items-center gap-3 min-w-0">
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted">
<RiFingerprintLine className="h-4 w-4" />
</div>
<div className="min-w-0">
{editingId === pk.id ? (
<div className="flex items-center gap-2">
<Input
className="h-7 text-sm w-40"
value={editName}
onChange={(e) => setEditName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleRename(pk.id);
if (e.key === "Escape") cancelEditing();
}}
autoFocus
disabled={isRenaming}
/>
<Button
size="sm"
variant="ghost"
className="h-7 px-2"
onClick={() => handleRename(pk.id)}
disabled={isRenaming || !editName.trim()}
>
{isRenaming ? (
<RiLoader4Line className="h-3 w-3 animate-spin" />
) : (
"Salvar"
)}
</Button>
<Button
size="sm"
variant="ghost"
className="h-7 px-2"
onClick={cancelEditing}
disabled={isRenaming}
>
Cancelar
</Button>
</div>
) : (
<>
<div className="flex items-center gap-2">
<span className="text-sm font-bold truncate">
{pk.name || "Passkey sem nome"}
</span>
<Button
variant="ghost"
size="icon"
className="h-5 w-5 text-muted-foreground hover:text-foreground"
onClick={() => startEditing(pk)}
disabled={isMutating}
>
<RiPencilLine className="h-3 w-3" />
</Button>
</div>
<p className="text-xs text-muted-foreground py-1">
{deviceTypeLabel(pk.deviceType)}
{pk.createdAt
? ` · Criada ${formatDistanceToNow(pk.createdAt, {
addSuffix: true,
locale: ptBR,
})}`
: " · Data de criação indisponível"}
</p>
</>
)}
</div>
</div>
{editingId !== pk.id && (
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
onClick={() => setDeleteId(pk.id)}
disabled={isMutating}
>
<RiDeleteBinLine className="h-4 w-4" />
</Button>
)}
</div>
))}
</div>
)}
{/* Delete Confirmation Dialog */}
<AlertDialog
open={!!deleteId}
onOpenChange={(open) => !open && setDeleteId(null)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Remover passkey?</AlertDialogTitle>
<AlertDialogDescription>
Esta passkey não poderá mais ser usada para login. Esta ação não
pode ser desfeita.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}>
Cancelar
</AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
disabled={isDeleting}
className="bg-destructive text-white hover:bg-destructive/90"
>
{isDeleting ? "Removendo..." : "Remover"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { RiLoader4Line } from "@remixicon/react"; import { RiFingerprintLine, RiLoader4Line } from "@remixicon/react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { type FormEvent, useState } from "react"; import { type FormEvent, useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
@@ -33,6 +33,32 @@ export function LoginForm({ className, ...props }: DivProps) {
const [error, setError] = useState(""); const [error, setError] = useState("");
const [loadingEmail, setLoadingEmail] = useState(false); const [loadingEmail, setLoadingEmail] = useState(false);
const [loadingGoogle, setLoadingGoogle] = useState(false); const [loadingGoogle, setLoadingGoogle] = useState(false);
const [loadingPasskey, setLoadingPasskey] = useState(false);
const [passkeySupported, setPasskeySupported] = useState(false);
useEffect(() => {
if (typeof window === "undefined") return;
if (typeof PublicKeyCredential === "undefined") return;
setPasskeySupported(true);
if (
typeof PublicKeyCredential.isConditionalMediationAvailable === "function"
) {
PublicKeyCredential.isConditionalMediationAvailable()
.then((available) => {
if (available) {
// Conditional UI é opcional: habilita autofill quando disponível.
authClient.signIn.passkey({
mediation: "conditional",
});
}
})
.catch(() => {
// Ignora falhas de detecção e mantém login manual por passkey.
});
}
}, []);
async function handleSubmit(e: FormEvent<HTMLFormElement>) { async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault(); e.preventDefault();
@@ -97,6 +123,29 @@ export function LoginForm({ className, ...props }: DivProps) {
); );
} }
async function handlePasskey() {
setError("");
setLoadingPasskey(true);
const { error: passkeyError } = await authClient.signIn.passkey({
fetchOptions: {
onSuccess: () => {
setLoadingPasskey(false);
router.replace("/dashboard");
},
onError: (ctx) => {
setError(ctx.error.message);
setLoadingPasskey(false);
},
},
});
if (passkeyError) {
setError(passkeyError.message || "Erro ao entrar com passkey.");
setLoadingPasskey(false);
}
}
return ( return (
<div className={cn("flex flex-col gap-6", className)} {...props}> <div className={cn("flex flex-col gap-6", className)} {...props}>
<Logo className="mb-2" /> <Logo className="mb-2" />
@@ -118,7 +167,7 @@ export function LoginForm({ className, ...props }: DivProps) {
id="email" id="email"
type="email" type="email"
placeholder="Digite seu e-mail" placeholder="Digite seu e-mail"
autoComplete="email" autoComplete="username webauthn"
required required
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
@@ -145,7 +194,7 @@ export function LoginForm({ className, ...props }: DivProps) {
<Field> <Field>
<Button <Button
type="submit" type="submit"
disabled={loadingEmail || loadingGoogle} disabled={loadingEmail || loadingGoogle || loadingPasskey}
className="w-full" className="w-full"
> >
{loadingEmail ? ( {loadingEmail ? (
@@ -164,11 +213,35 @@ export function LoginForm({ className, ...props }: DivProps) {
<GoogleAuthButton <GoogleAuthButton
onClick={handleGoogle} onClick={handleGoogle}
loading={loadingGoogle} loading={loadingGoogle}
disabled={loadingEmail || loadingGoogle || !isGoogleAvailable} disabled={
loadingEmail ||
loadingGoogle ||
loadingPasskey ||
!isGoogleAvailable
}
text="Entrar com Google" text="Entrar com Google"
/> />
</Field> </Field>
{passkeySupported && (
<Field>
<Button
variant="outline"
type="button"
onClick={handlePasskey}
disabled={loadingEmail || loadingGoogle || loadingPasskey}
className="w-full gap-2"
>
{loadingPasskey ? (
<RiLoader4Line className="h-4 w-4 animate-spin" />
) : (
<RiFingerprintLine className="h-5 w-5" />
)}
<span>Entrar com Passkey</span>
</Button>
</Field>
)}
<FieldDescription className="text-center"> <FieldDescription className="text-center">
Não tem uma conta?{" "} Não tem uma conta?{" "}
<a href="/signup" className="underline underline-offset-4"> <a href="/signup" className="underline underline-offset-4">

View File

@@ -100,6 +100,27 @@ export const verification = pgTable("verification", {
}), }),
}); });
// ===================== PASSKEY (WebAuthn) =====================
export const passkey = pgTable("passkey", {
id: text("id").primaryKey(),
name: text("name"),
publicKey: text("publicKey").notNull(),
userId: text("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
credentialID: text("credentialID").notNull(),
counter: integer("counter").notNull(),
deviceType: text("deviceType").notNull(),
backedUp: boolean("backedUp").notNull(),
transports: text("transports"),
aaguid: text("aaguid"),
createdAt: timestamp("createdAt", {
mode: "date",
withTimezone: true,
}),
});
export const preferenciasUsuario = pgTable("preferencias_usuario", { export const preferenciasUsuario = pgTable("preferencias_usuario", {
id: uuid("id").primaryKey().default(sql`gen_random_uuid()`), id: uuid("id").primaryKey().default(sql`gen_random_uuid()`),
userId: text("user_id") userId: text("user_id")

View File

@@ -0,0 +1,17 @@
CREATE TABLE "passkey" (
"id" text PRIMARY KEY NOT NULL,
"name" text,
"publicKey" text NOT NULL,
"userId" text NOT NULL,
"credentialID" text NOT NULL,
"counter" integer NOT NULL,
"deviceType" text NOT NULL,
"backedUp" boolean NOT NULL,
"transports" text,
"aaguid" text,
"createdAt" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "preferencias_usuario" ADD COLUMN IF NOT EXISTS "extrato_note_as_column" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "preferencias_usuario" ADD COLUMN IF NOT EXISTS "lancamentos_column_order" jsonb;--> statement-breakpoint
ALTER TABLE "passkey" ADD CONSTRAINT "passkey_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;

File diff suppressed because it is too large Load Diff

View File

@@ -120,6 +120,13 @@
"when": 1771166328908, "when": 1771166328908,
"tag": "0016_complete_randall", "tag": "0016_complete_randall",
"breakpoints": true "breakpoints": true
},
{
"idx": 17,
"version": "7",
"when": 1772400510326,
"tag": "0017_previous_warstar",
"breakpoints": true
} }
] ]
} }

View File

@@ -1,9 +1,11 @@
import { passkeyClient } from "@better-auth/passkey/client";
import { createAuthClient } from "better-auth/react"; import { createAuthClient } from "better-auth/react";
const baseURL = process.env.BETTER_AUTH_URL?.replace(/\/$/, ""); const baseURL = process.env.BETTER_AUTH_URL?.replace(/\/$/, "");
export const authClient = createAuthClient({ export const authClient = createAuthClient({
...(baseURL ? { baseURL } : {}), ...(baseURL ? { baseURL } : {}),
plugins: [passkeyClient()],
}); });
/** /**

View File

@@ -1,3 +1,4 @@
import { passkey } from "@better-auth/passkey";
import { betterAuth } from "better-auth"; import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzleAdapter } from "better-auth/adapters/drizzle";
import type { GoogleProfile } from "better-auth/social-providers"; import type { GoogleProfile } from "better-auth/social-providers";
@@ -83,6 +84,13 @@ export const auth = betterAuth({
}, },
}, },
// Plugins
plugins: [
passkey({
rpName: "OpenMonetis",
}),
],
// Google OAuth (se configurado) // Google OAuth (se configurado)
socialProviders: socialProviders:
googleClientId && googleClientSecret googleClientId && googleClientSecret

View File

@@ -1,6 +1,6 @@
{ {
"name": "openmonetis", "name": "openmonetis",
"version": "1.7.5", "version": "1.7.6",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
@@ -30,6 +30,7 @@
"@ai-sdk/anthropic": "^3.0.48", "@ai-sdk/anthropic": "^3.0.48",
"@ai-sdk/google": "^3.0.33", "@ai-sdk/google": "^3.0.33",
"@ai-sdk/openai": "^3.0.36", "@ai-sdk/openai": "^3.0.36",
"@better-auth/passkey": "^1.5.0",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",

281
pnpm-lock.yaml generated
View File

@@ -17,6 +17,9 @@ importers:
'@ai-sdk/openai': '@ai-sdk/openai':
specifier: ^3.0.36 specifier: ^3.0.36
version: 3.0.36(zod@4.3.6) version: 3.0.36(zod@4.3.6)
'@better-auth/passkey':
specifier: ^1.5.0
version: 1.5.0(@better-auth/core@1.5.0(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.4.19(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.19.0))(next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.19.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(better-call@1.3.2(zod@4.3.6))(nanostores@1.1.0)
'@dnd-kit/core': '@dnd-kit/core':
specifier: ^6.3.1 specifier: ^6.3.1
version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -268,6 +271,30 @@ packages:
kysely: ^0.28.5 kysely: ^0.28.5
nanostores: ^1.0.1 nanostores: ^1.0.1
'@better-auth/core@1.5.0':
resolution: {integrity: sha512-nDPmW7I9VGRACEei31fHaZxGwD/yICraDllZ/f25jbWXYaxDaW88RuH1ZhbOUKmGJlZtDxcjN1+YmcVIc1ioNw==}
peerDependencies:
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
'@cloudflare/workers-types': '>=4'
better-call: 1.3.2
jose: ^6.1.0
kysely: ^0.28.5
nanostores: ^1.0.1
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
'@better-auth/passkey@1.5.0':
resolution: {integrity: sha512-joupofIxoUEzwd3/T/d7JRSHxPx9kBLYJs6eBhcuso564/O9SrAOKbzfepEmouBMow6VA78bpwAGkVHpMkSwyg==}
peerDependencies:
'@better-auth/core': 1.5.0
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
better-auth: 1.5.0
better-call: 1.3.2
nanostores: ^1.0.1
'@better-auth/telemetry@1.4.19': '@better-auth/telemetry@1.4.19':
resolution: {integrity: sha512-ApGNS7olCTtDpKF8Ow3Z+jvFAirOj7c4RyFUpu8axklh3mH57ndpfUAUjhgA8UVoaaH/mnm/Tl884BlqiewLyw==} resolution: {integrity: sha512-ApGNS7olCTtDpKF8Ow3Z+jvFAirOj7c4RyFUpu8axklh3mH57ndpfUAUjhgA8UVoaaH/mnm/Tl884BlqiewLyw==}
peerDependencies: peerDependencies:
@@ -276,6 +303,9 @@ packages:
'@better-auth/utils@0.3.0': '@better-auth/utils@0.3.0':
resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==}
'@better-auth/utils@0.3.1':
resolution: {integrity: sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg==}
'@better-fetch/fetch@1.1.21': '@better-fetch/fetch@1.1.21':
resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==}
@@ -834,6 +864,9 @@ packages:
'@floating-ui/utils@0.2.10': '@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
'@img/colour@1.0.0': '@img/colour@1.0.0':
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -1003,6 +1036,9 @@ packages:
'@jridgewell/trace-mapping@0.3.31': '@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@levischuck/tiny-cbor@0.2.11':
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
'@next/env@16.1.6': '@next/env@16.1.6':
resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==}
@@ -1077,6 +1113,43 @@ packages:
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
'@peculiar/asn1-android@2.6.0':
resolution: {integrity: sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ==}
'@peculiar/asn1-cms@2.6.1':
resolution: {integrity: sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==}
'@peculiar/asn1-csr@2.6.1':
resolution: {integrity: sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==}
'@peculiar/asn1-ecc@2.6.1':
resolution: {integrity: sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==}
'@peculiar/asn1-pfx@2.6.1':
resolution: {integrity: sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==}
'@peculiar/asn1-pkcs8@2.6.1':
resolution: {integrity: sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==}
'@peculiar/asn1-pkcs9@2.6.1':
resolution: {integrity: sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==}
'@peculiar/asn1-rsa@2.6.1':
resolution: {integrity: sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==}
'@peculiar/asn1-schema@2.6.0':
resolution: {integrity: sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==}
'@peculiar/asn1-x509-attr@2.6.1':
resolution: {integrity: sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==}
'@peculiar/asn1-x509@2.6.1':
resolution: {integrity: sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==}
'@peculiar/x509@1.14.3':
resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==}
engines: {node: '>=20.0.0'}
'@radix-ui/number@1.1.1': '@radix-ui/number@1.1.1':
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
@@ -1866,6 +1939,13 @@ packages:
peerDependencies: peerDependencies:
react: '>=18.2.0' react: '>=18.2.0'
'@simplewebauthn/browser@13.2.2':
resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
'@simplewebauthn/server@13.2.3':
resolution: {integrity: sha512-ZhcVBOw63birYx9jVfbhK6rTehckVes8PeWV324zpmdxr0BUfylospwMzcrxrdMcOi48MHWj2LCA+S528LnGvg==}
engines: {node: '>=20.0.0'}
'@stablelib/base64@1.0.1': '@stablelib/base64@1.0.1':
resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
@@ -2105,6 +2185,10 @@ packages:
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'} engines: {node: '>=10'}
asn1js@3.0.7:
resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==}
engines: {node: '>=12.0.0'}
babel-plugin-react-compiler@1.0.0: babel-plugin-react-compiler@1.0.0:
resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==}
@@ -2186,6 +2270,14 @@ packages:
zod: zod:
optional: true optional: true
better-call@1.3.2:
resolution: {integrity: sha512-4cZIfrerDsNTn3cm+MhLbUePN0gdwkhSXEuG7r/zuQ8c/H7iU0/jSK5TD3FW7U0MgKHce/8jGpPYNO4Ve+4NBw==}
peerDependencies:
zod: ^4.0.0
peerDependenciesMeta:
zod:
optional: true
buffer-from@1.1.2: buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -2699,6 +2791,13 @@ packages:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
pvtsutils@1.3.6:
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
pvutils@1.1.5:
resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==}
engines: {node: '>=16.0.0'}
radix-ui@1.4.3: radix-ui@1.4.3:
resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==}
peerDependencies: peerDependencies:
@@ -2791,6 +2890,9 @@ packages:
redux@5.0.1: redux@5.0.1:
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
regenerator-runtime@0.13.11: regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
@@ -2827,6 +2929,9 @@ packages:
set-cookie-parser@2.7.2: set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
set-cookie-parser@3.0.1:
resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==}
sharp@0.34.5: sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -2899,6 +3004,9 @@ packages:
tiny-invariant@1.3.3: tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -2907,6 +3015,10 @@ packages:
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
hasBin: true hasBin: true
tsyringe@4.10.0:
resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==}
engines: {node: '>= 6.0.0'}
typescript@5.9.3: typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
@@ -3041,6 +3153,29 @@ snapshots:
nanostores: 1.1.0 nanostores: 1.1.0
zod: 4.3.6 zod: 4.3.6
'@better-auth/core@1.5.0(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)':
dependencies:
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
'@standard-schema/spec': 1.1.0
better-call: 1.3.2(zod@4.3.6)
jose: 6.1.3
kysely: 0.28.11
nanostores: 1.1.0
zod: 4.3.6
'@better-auth/passkey@1.5.0(@better-auth/core@1.5.0(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-auth@1.4.19(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.19.0))(next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.19.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(better-call@1.3.2(zod@4.3.6))(nanostores@1.1.0)':
dependencies:
'@better-auth/core': 1.5.0(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
'@simplewebauthn/browser': 13.2.2
'@simplewebauthn/server': 13.2.3
better-auth: 1.4.19(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.19.0))(next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.19.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
better-call: 1.3.2(zod@4.3.6)
nanostores: 1.1.0
zod: 4.3.6
'@better-auth/telemetry@1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))': '@better-auth/telemetry@1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))':
dependencies: dependencies:
'@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
@@ -3049,6 +3184,8 @@ snapshots:
'@better-auth/utils@0.3.0': {} '@better-auth/utils@0.3.0': {}
'@better-auth/utils@0.3.1': {}
'@better-fetch/fetch@1.1.21': {} '@better-fetch/fetch@1.1.21': {}
'@biomejs/biome@2.4.4': '@biomejs/biome@2.4.4':
@@ -3369,6 +3506,8 @@ snapshots:
'@floating-ui/utils@0.2.10': {} '@floating-ui/utils@0.2.10': {}
'@hexagon/base64@1.1.28': {}
'@img/colour@1.0.0': '@img/colour@1.0.0':
optional: true optional: true
@@ -3485,6 +3624,8 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
'@levischuck/tiny-cbor@0.2.11': {}
'@next/env@16.1.6': {} '@next/env@16.1.6': {}
'@next/swc-darwin-arm64@16.1.6': '@next/swc-darwin-arm64@16.1.6':
@@ -3522,6 +3663,102 @@ snapshots:
'@opentelemetry/api@1.9.0': {} '@opentelemetry/api@1.9.0': {}
'@peculiar/asn1-android@2.6.0':
dependencies:
'@peculiar/asn1-schema': 2.6.0
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-cms@2.6.1':
dependencies:
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
'@peculiar/asn1-x509-attr': 2.6.1
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-csr@2.6.1':
dependencies:
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-ecc@2.6.1':
dependencies:
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-pfx@2.6.1':
dependencies:
'@peculiar/asn1-cms': 2.6.1
'@peculiar/asn1-pkcs8': 2.6.1
'@peculiar/asn1-rsa': 2.6.1
'@peculiar/asn1-schema': 2.6.0
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-pkcs8@2.6.1':
dependencies:
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-pkcs9@2.6.1':
dependencies:
'@peculiar/asn1-cms': 2.6.1
'@peculiar/asn1-pfx': 2.6.1
'@peculiar/asn1-pkcs8': 2.6.1
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
'@peculiar/asn1-x509-attr': 2.6.1
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-rsa@2.6.1':
dependencies:
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-schema@2.6.0':
dependencies:
asn1js: 3.0.7
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/asn1-x509-attr@2.6.1':
dependencies:
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
asn1js: 3.0.7
tslib: 2.8.1
'@peculiar/asn1-x509@2.6.1':
dependencies:
'@peculiar/asn1-schema': 2.6.0
asn1js: 3.0.7
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/x509@1.14.3':
dependencies:
'@peculiar/asn1-cms': 2.6.1
'@peculiar/asn1-csr': 2.6.1
'@peculiar/asn1-ecc': 2.6.1
'@peculiar/asn1-pkcs9': 2.6.1
'@peculiar/asn1-rsa': 2.6.1
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
pvtsutils: 1.3.6
reflect-metadata: 0.2.2
tslib: 2.8.1
tsyringe: 4.10.0
'@radix-ui/number@1.1.1': {} '@radix-ui/number@1.1.1': {}
'@radix-ui/primitive@1.1.3': {} '@radix-ui/primitive@1.1.3': {}
@@ -4348,6 +4585,19 @@ snapshots:
dependencies: dependencies:
react: 19.2.4 react: 19.2.4
'@simplewebauthn/browser@13.2.2': {}
'@simplewebauthn/server@13.2.3':
dependencies:
'@hexagon/base64': 1.1.28
'@levischuck/tiny-cbor': 0.2.11
'@peculiar/asn1-android': 2.6.0
'@peculiar/asn1-ecc': 2.6.1
'@peculiar/asn1-rsa': 2.6.1
'@peculiar/asn1-schema': 2.6.0
'@peculiar/asn1-x509': 2.6.1
'@peculiar/x509': 1.14.3
'@stablelib/base64@1.0.1': {} '@stablelib/base64@1.0.1': {}
'@standard-schema/spec@1.1.0': {} '@standard-schema/spec@1.1.0': {}
@@ -4515,6 +4765,12 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
asn1js@3.0.7:
dependencies:
pvtsutils: 1.3.6
pvutils: 1.1.5
tslib: 2.8.1
babel-plugin-react-compiler@1.0.0: babel-plugin-react-compiler@1.0.0:
dependencies: dependencies:
'@babel/types': 7.29.0 '@babel/types': 7.29.0
@@ -4556,6 +4812,15 @@ snapshots:
optionalDependencies: optionalDependencies:
zod: 4.3.6 zod: 4.3.6
better-call@1.3.2(zod@4.3.6):
dependencies:
'@better-auth/utils': 0.3.1
'@better-fetch/fetch': 1.1.21
rou3: 0.7.12
set-cookie-parser: 3.0.1
optionalDependencies:
zod: 4.3.6
buffer-from@1.1.2: {} buffer-from@1.1.2: {}
caniuse-lite@1.0.30001770: {} caniuse-lite@1.0.30001770: {}
@@ -5007,6 +5272,12 @@ snapshots:
dependencies: dependencies:
xtend: 4.0.2 xtend: 4.0.2
pvtsutils@1.3.6:
dependencies:
tslib: 2.8.1
pvutils@1.1.5: {}
radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
'@radix-ui/primitive': 1.1.3 '@radix-ui/primitive': 1.1.3
@@ -5154,6 +5425,8 @@ snapshots:
redux@5.0.1: {} redux@5.0.1: {}
reflect-metadata@0.2.2: {}
regenerator-runtime@0.13.11: regenerator-runtime@0.13.11:
optional: true optional: true
@@ -5178,6 +5451,8 @@ snapshots:
set-cookie-parser@2.7.2: {} set-cookie-parser@2.7.2: {}
set-cookie-parser@3.0.1: {}
sharp@0.34.5: sharp@0.34.5:
dependencies: dependencies:
'@img/colour': 1.0.0 '@img/colour': 1.0.0
@@ -5264,6 +5539,8 @@ snapshots:
tiny-invariant@1.3.3: {} tiny-invariant@1.3.3: {}
tslib@1.14.1: {}
tslib@2.8.1: {} tslib@2.8.1: {}
tsx@4.21.0: tsx@4.21.0:
@@ -5273,6 +5550,10 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
tsyringe@4.10.0:
dependencies:
tslib: 1.14.1
typescript@5.9.3: {} typescript@5.9.3: {}
undici-types@7.18.2: {} undici-types@7.18.2: {}