mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
add: oidc,passkey,disable email/password,sign-up
This commit is contained in:
@@ -26,6 +26,24 @@ AUTH_GOOGLE_ID=
|
|||||||
AUTH_GOOGLE_SECRET=
|
AUTH_GOOGLE_SECRET=
|
||||||
AUTH_GOOGLE_METHOD=
|
AUTH_GOOGLE_METHOD=
|
||||||
|
|
||||||
|
AUTH_OIDC_ID=""
|
||||||
|
AUTH_OIDC_TITLE=""
|
||||||
|
AUTH_OIDC_DESC=""
|
||||||
|
AUTH_OIDC_ICON=""
|
||||||
|
AUTH_OIDC_CLIENT=""
|
||||||
|
AUTH_OIDC_SECRET=""
|
||||||
|
AUTH_OIDC_ISSUER_URL=""
|
||||||
|
AUTH_OIDC_HOST=""
|
||||||
|
AUTH_OIDC_SCOPES=""
|
||||||
|
AUTH_OIDC_DISCOVERY_ENDPOINT=""
|
||||||
|
AUTH_OIDC_JWKS_ENDPOINT=""
|
||||||
|
AUTH_OIDC_PKCE=true
|
||||||
|
ALLOWED_GROUP="admin"
|
||||||
|
|
||||||
|
AUTH_EMAIL_PASSWORD_ENABLED=true
|
||||||
|
AUTH_SIGNUP_ENABLED=true
|
||||||
|
AUTH_PASSKEY_ENABLED=true
|
||||||
|
|
||||||
# Retention
|
# Retention
|
||||||
RETENTION_CRON="* * * * *"
|
RETENTION_CRON="* * * * *"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import {CardContent, CardHeader} from "@/components/ui/card";
|
import { CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
|
||||||
import {TooltipProvider} from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import {ForgotPasswordForm} from "@/components/wrappers/auth/login/forgot-password-form/forgot-password-form";
|
import { ForgotPasswordForm } from "@/components/wrappers/auth/login/forgot-password-form/forgot-password-form";
|
||||||
import {CardAuth} from "@/features/layout/card-auth";
|
import { env } from "@/env.mjs";
|
||||||
|
import { CardAuth } from "@/features/layout/card-auth";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default async function RoutePage(props: { searchParams: Promise<{ callbackUrl: string | undefined }> }) {
|
export default async function RoutePage(props: { searchParams: Promise<{ callbackUrl: string | undefined }> }) {
|
||||||
|
if (env.AUTH_EMAIL_PASSWORD_ENABLED !== "true") {
|
||||||
|
redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
@@ -12,12 +17,11 @@ export default async function RoutePage(props: { searchParams: Promise<{ callbac
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="grid gap-2 text-center mb-2">
|
<div className="grid gap-2 text-center mb-2">
|
||||||
<h1 className="text-3xl font-bold">Reset password</h1>
|
<h1 className="text-3xl font-bold">Reset password</h1>
|
||||||
<p className="text-balance text-muted-foreground">Enter your email address and we'll send you a
|
<p className="text-balance text-muted-foreground">Enter your email address and we'll send you a link to reset your password.</p>
|
||||||
link to reset your password.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ForgotPasswordForm/>
|
<ForgotPasswordForm />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardAuth>
|
</CardAuth>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
+18
-19
@@ -1,19 +1,19 @@
|
|||||||
import {LoginForm} from "@/components/wrappers/auth/login/login-form/login-form";
|
import { LoginForm } from "@/components/wrappers/auth/login/login-form/login-form";
|
||||||
import {Metadata} from "next";
|
import { Metadata } from "next";
|
||||||
import {SUPPORTED_PROVIDERS} from "../../../portabase.config";
|
import { SUPPORTED_PROVIDERS } from "@/lib/auth/config";
|
||||||
import {SocialAuthButtons} from "@/components/wrappers/auth/social-buttons";
|
import { SocialAuthButtons } from "@/components/wrappers/auth/social-buttons";
|
||||||
import {TooltipProvider} from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import {CardContent, CardHeader} from "@/components/ui/card";
|
import { CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {Separator} from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import {CardAuth} from "@/features/layout/card-auth";
|
import { CardAuth } from "@/features/layout/card-auth";
|
||||||
|
import { env } from "@/env.mjs";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Login",
|
title: "Login",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function SignInPage() {
|
export default async function SignInPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<CardAuth className="w-full">
|
<CardAuth className="w-full">
|
||||||
@@ -24,29 +24,28 @@ export default async function SignInPage() {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<LoginForm/>
|
{env.AUTH_EMAIL_PASSWORD_ENABLED === "true" && <LoginForm isPasskeyEnabled={env.AUTH_PASSKEY_ENABLED === "true"} />}
|
||||||
|
|
||||||
{SUPPORTED_PROVIDERS.filter((p) => !p.isManual && p.isActive).length > 0 && (
|
{env.AUTH_EMAIL_PASSWORD_ENABLED === "true" && SUPPORTED_PROVIDERS.filter((p) => !p.isManual && p.isActive).length > 0 && (
|
||||||
<>
|
|
||||||
<div className="relative my-4 flex items-center justify-center overflow-hidden">
|
<div className="relative my-4 flex items-center justify-center overflow-hidden">
|
||||||
<Separator/>
|
<Separator />
|
||||||
<div className="px-2 text-center text-sm">OR</div>
|
<div className="px-2 text-center text-sm">OR</div>
|
||||||
<Separator/>
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
<SocialAuthButtons providers={SUPPORTED_PROVIDERS}/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{SUPPORTED_PROVIDERS.filter((p) => !p.isManual && p.isActive).length > 0 && <SocialAuthButtons providers={SUPPORTED_PROVIDERS} />}
|
||||||
|
|
||||||
|
{env.AUTH_SIGNUP_ENABLED === "true" && (
|
||||||
<div className="mt-4 text-center text-sm">
|
<div className="mt-4 text-center text-sm">
|
||||||
Don't have an account ?{" "}
|
Don't have an account ?{" "}
|
||||||
<Link href="/register" className="underline">
|
<Link href="/register" className="underline">
|
||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardAuth>
|
</CardAuth>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import { PageParams } from "@/types/next";
|
||||||
import {RegisterForm} from "@/components/wrappers/auth/register/register-form/register-form";
|
import { RegisterForm } from "@/components/wrappers/auth/register/register-form/register-form";
|
||||||
import {Metadata} from "next";
|
import { Metadata } from "next";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { env } from "@/env.mjs";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Register",
|
title: "Register",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
if (env.AUTH_SIGNUP_ENABLED !== "true") {
|
||||||
|
redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto grid w-full gap-6">
|
<div className="mx-auto grid w-full gap-6">
|
||||||
<RegisterForm/>
|
<RegisterForm />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
@@ -4,9 +4,13 @@ import { ResetPasswordForm } from "@/components/wrappers/auth/login/reset-passwo
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { auth } from "@/lib/auth/auth";
|
import { auth } from "@/lib/auth/auth";
|
||||||
import { Avatar, AvatarImage, AvatarFallback } from "@radix-ui/react-avatar";
|
import { Avatar, AvatarImage, AvatarFallback } from "@radix-ui/react-avatar";
|
||||||
import {CardAuth} from "@/features/layout/card-auth";
|
import { CardAuth } from "@/features/layout/card-auth";
|
||||||
|
import { env } from "@/env.mjs";
|
||||||
|
|
||||||
export default async function RoutePage(props: { searchParams: Promise<{ token: string | undefined }> }) {
|
export default async function RoutePage(props: { searchParams: Promise<{ token: string | undefined }> }) {
|
||||||
|
if (env.AUTH_EMAIL_PASSWORD_ENABLED !== "true") {
|
||||||
|
return redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
const { token } = await props.searchParams;
|
const { token } = await props.searchParams;
|
||||||
|
|
||||||
|
|||||||
+13
-1
@@ -12,7 +12,7 @@ services:
|
|||||||
- POSTGRES_USER=devuser
|
- POSTGRES_USER=devuser
|
||||||
- POSTGRES_PASSWORD=changeme
|
- POSTGRES_PASSWORD=changeme
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD-SHELL", "pg_isready -U devuser -d devdb" ]
|
test: ["CMD-SHELL", "pg_isready -U devuser -d devdb"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -31,5 +31,17 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./private/uploads/tmp:/data/uploads/tmp
|
- ./private/uploads/tmp:/data/uploads/tmp
|
||||||
|
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:latest
|
||||||
|
command: start-dev
|
||||||
|
environment:
|
||||||
|
KC_BOOTSTRAP_ADMIN_USERNAME: admin
|
||||||
|
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- keycloak-data:/opt/keycloak/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
|
keycloak-data:
|
||||||
|
|||||||
+3
-1
@@ -14,6 +14,8 @@
|
|||||||
"auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts"
|
"auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@better-auth/passkey": "^1.4.18",
|
||||||
|
"@better-auth/sso": "^1.4.18",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@radix-ui/react-accordion": "^1.2.12",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
@@ -53,7 +55,7 @@
|
|||||||
"@zenstackhq/runtime": "2.14.2",
|
"@zenstackhq/runtime": "2.14.2",
|
||||||
"argon2": "^0.43.1",
|
"argon2": "^0.43.1",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"better-auth": "1.4.5",
|
"better-auth": "1.4.18",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
|
|||||||
Generated
+429
-36
@@ -8,6 +8,12 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@better-auth/passkey':
|
||||||
|
specifier: ^1.4.18
|
||||||
|
version: 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-auth@1.4.18(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.9)(drizzle-orm@0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3)))(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(better-call@1.1.8(zod@3.25.76))(nanostores@1.1.0)
|
||||||
|
'@better-auth/sso':
|
||||||
|
specifier: ^1.4.18
|
||||||
|
version: 1.4.18(@better-auth/utils@0.3.0)(better-auth@1.4.18(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.9)(drizzle-orm@0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3)))(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))
|
||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.2.2
|
specifier: ^5.2.2
|
||||||
version: 5.2.2(react-hook-form@7.71.1(react@19.2.4))
|
version: 5.2.2(react-hook-form@7.71.1(react@19.2.4))
|
||||||
@@ -126,8 +132,8 @@ importers:
|
|||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.4.5
|
specifier: 1.4.18
|
||||||
version: 1.4.5(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 1.4.18(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.9)(drizzle-orm@0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3)))(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@@ -350,6 +356,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@authenio/xml-encryption@2.0.2':
|
||||||
|
resolution: {integrity: sha512-cTlrKttbrRHEw3W+0/I609A2Matj5JQaRvfLtEIGZvlN0RaPi+3ANsMeqAyCAVlH/lUIW2tmtBlSMni74lcXeg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
'@babel/code-frame@7.29.0':
|
'@babel/code-frame@7.29.0':
|
||||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -437,26 +447,42 @@ packages:
|
|||||||
'@balena/dockerignore@1.0.2':
|
'@balena/dockerignore@1.0.2':
|
||||||
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
|
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
|
||||||
|
|
||||||
'@better-auth/core@1.4.5':
|
'@better-auth/core@1.4.18':
|
||||||
resolution: {integrity: sha512-dQ3hZOkUJzeBXfVEPTm2LVbzmWwka1nqd9KyWmB2OMlMfjr7IdUeBX4T7qJctF67d7QDhlX95jMoxu6JG0Eucw==}
|
resolution: {integrity: sha512-q+awYgC7nkLEBdx2sW0iJjkzgSHlIxGnOpsN1r/O1+a4m7osJNHtfK2mKJSL1I+GfNyIlxJF8WvD/NLuYMpmcg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@better-auth/utils': 0.3.0
|
'@better-auth/utils': 0.3.0
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.21
|
||||||
better-call: 1.1.4
|
better-call: 1.1.8
|
||||||
jose: ^6.1.0
|
jose: ^6.1.0
|
||||||
kysely: ^0.28.5
|
kysely: ^0.28.5
|
||||||
nanostores: ^1.0.1
|
nanostores: ^1.0.1
|
||||||
|
|
||||||
'@better-auth/telemetry@1.4.5':
|
'@better-auth/passkey@1.4.18':
|
||||||
resolution: {integrity: sha512-r3NyksbaBYA10SC86JA6QwmZfHwFutkUGcphgWGfu6MVx1zutYmZehIeC8LxTjOWZqqF9FI8vLjglWBHvPQeTg==}
|
resolution: {integrity: sha512-27YfrHCc95fm9e6V3RSN44JZunGHbQd0Lc26ErndPl2bCQ0VMJQi70WNLu6iqEiZYG8YSnxbMOg4/ivk8D7+lA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@better-auth/core': 1.4.5
|
'@better-auth/core': 1.4.18
|
||||||
|
'@better-auth/utils': 0.3.0
|
||||||
|
'@better-fetch/fetch': 1.1.21
|
||||||
|
better-auth: 1.4.18
|
||||||
|
better-call: 1.1.8
|
||||||
|
nanostores: ^1.0.1
|
||||||
|
|
||||||
|
'@better-auth/sso@1.4.18':
|
||||||
|
resolution: {integrity: sha512-jwTxZUBp71W6YVOavy50DBZ4OFi0a9MGvTTAi+mxMuINFRcz2RyGZv0gb3jy8AJT97TwKU60qRYIWYYyJj1uhA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@better-auth/utils': 0.3.0
|
||||||
|
better-auth: 1.4.18
|
||||||
|
|
||||||
|
'@better-auth/telemetry@1.4.18':
|
||||||
|
resolution: {integrity: sha512-e5rDF8S4j3Um/0LIVATL2in9dL4lfO2fr2v1Wio4qTMRbfxqnUDTa+6SZtwdeJrbc4O+a3c+IyIpjG9Q/6GpfQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@better-auth/core': 1.4.18
|
||||||
|
|
||||||
'@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-fetch/fetch@1.1.18':
|
'@better-fetch/fetch@1.1.21':
|
||||||
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
|
resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==}
|
||||||
|
|
||||||
'@chevrotain/cst-dts-gen@10.4.2':
|
'@chevrotain/cst-dts-gen@10.4.2':
|
||||||
resolution: {integrity: sha512-0+4bNjlndNWMoVLH/+y4uHnf6GrTipsC+YTppJxelVJo+xeRVQ0s2PpkdDCVTsu7efyj+8r1gFiwVXsp6JZ0iQ==}
|
resolution: {integrity: sha512-0+4bNjlndNWMoVLH/+y4uHnf6GrTipsC+YTppJxelVJo+xeRVQ0s2PpkdDCVTsu7efyj+8r1gFiwVXsp6JZ0iQ==}
|
||||||
@@ -1160,6 +1186,9 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
'@hexagon/base64@1.1.28':
|
||||||
|
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
|
||||||
|
|
||||||
'@hookform/resolvers@5.2.2':
|
'@hookform/resolvers@5.2.2':
|
||||||
resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==}
|
resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1516,6 +1545,9 @@ packages:
|
|||||||
'@js-sdsl/ordered-map@4.4.2':
|
'@js-sdsl/ordered-map@4.4.2':
|
||||||
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
|
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
|
||||||
|
|
||||||
|
'@levischuck/tiny-cbor@0.2.11':
|
||||||
|
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
|
||||||
|
|
||||||
'@lottiefiles/dotlottie-react@0.13.3':
|
'@lottiefiles/dotlottie-react@0.13.3':
|
||||||
resolution: {integrity: sha512-V4FfdYlqzjBUX7f0KV6vfQOOI0Cp+3XeG/ZqSDFSEVg5P7fpROpDv5/I9aTM8sOCESK1SWT96Fem+QVUnBV1wQ==}
|
resolution: {integrity: sha512-V4FfdYlqzjBUX7f0KV6vfQOOI0Cp+3XeG/ZqSDFSEVg5P7fpROpDv5/I9aTM8sOCESK1SWT96Fem+QVUnBV1wQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1671,6 +1703,43 @@ packages:
|
|||||||
'@paralleldrive/cuid2@2.3.1':
|
'@paralleldrive/cuid2@2.3.1':
|
||||||
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
|
resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==}
|
||||||
|
|
||||||
|
'@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'}
|
||||||
|
|
||||||
'@phc/format@1.0.0':
|
'@phc/format@1.0.0':
|
||||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2667,6 +2736,13 @@ packages:
|
|||||||
'@selderee/plugin-htmlparser2@0.11.0':
|
'@selderee/plugin-htmlparser2@0.11.0':
|
||||||
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||||
|
|
||||||
|
'@simplewebauthn/browser@13.2.2':
|
||||||
|
resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
|
||||||
|
|
||||||
|
'@simplewebauthn/server@13.2.2':
|
||||||
|
resolution: {integrity: sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@socket.io/component-emitter@3.1.2':
|
'@socket.io/component-emitter@3.1.2':
|
||||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||||
|
|
||||||
@@ -3161,6 +3237,14 @@ packages:
|
|||||||
'@webassemblyjs/wast-printer@1.14.1':
|
'@webassemblyjs/wast-printer@1.14.1':
|
||||||
resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
|
resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
|
||||||
|
|
||||||
|
'@xmldom/is-dom-node@1.0.1':
|
||||||
|
resolution: {integrity: sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
|
'@xmldom/xmldom@0.8.11':
|
||||||
|
resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
'@xtuc/ieee754@1.2.0':
|
'@xtuc/ieee754@1.2.0':
|
||||||
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
||||||
|
|
||||||
@@ -3344,6 +3428,10 @@ packages:
|
|||||||
asn1@0.2.6:
|
asn1@0.2.6:
|
||||||
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
|
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
|
||||||
|
|
||||||
|
asn1js@3.0.7:
|
||||||
|
resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
ast-types-flow@0.0.8:
|
ast-types-flow@0.0.8:
|
||||||
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
|
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
|
||||||
|
|
||||||
@@ -3405,27 +3493,55 @@ packages:
|
|||||||
bcryptjs@2.4.3:
|
bcryptjs@2.4.3:
|
||||||
resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==}
|
resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==}
|
||||||
|
|
||||||
better-auth@1.4.5:
|
better-auth@1.4.18:
|
||||||
resolution: {integrity: sha512-pHV2YE0OogRHvoA6pndHXCei4pcep/mjY7psSaHVrRgjBtumVI68SV1g9U9XPRZ4KkoGca9jfwuv+bB2UILiFw==}
|
resolution: {integrity: sha512-bnyifLWBPcYVltH3RhS7CM62MoelEqC6Q+GnZwfiDWNfepXoQZBjEvn4urcERC7NTKgKq5zNBM8rvPvRBa6xcg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@lynx-js/react': '*'
|
'@lynx-js/react': '*'
|
||||||
|
'@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||||
'@sveltejs/kit': ^2.0.0
|
'@sveltejs/kit': ^2.0.0
|
||||||
'@tanstack/react-start': ^1.0.0
|
'@tanstack/react-start': ^1.0.0
|
||||||
|
'@tanstack/solid-start': ^1.0.0
|
||||||
|
better-sqlite3: ^12.0.0
|
||||||
|
drizzle-kit: '>=0.31.4'
|
||||||
|
drizzle-orm: '>=0.41.0'
|
||||||
|
mongodb: ^6.0.0 || ^7.0.0
|
||||||
|
mysql2: ^3.0.0
|
||||||
next: ^14.0.0 || ^15.0.0 || ^16.0.0
|
next: ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||||
|
pg: ^8.0.0
|
||||||
|
prisma: ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||||
react: ^18.0.0 || ^19.0.0
|
react: ^18.0.0 || ^19.0.0
|
||||||
react-dom: ^18.0.0 || ^19.0.0
|
react-dom: ^18.0.0 || ^19.0.0
|
||||||
solid-js: ^1.0.0
|
solid-js: ^1.0.0
|
||||||
svelte: ^4.0.0 || ^5.0.0
|
svelte: ^4.0.0 || ^5.0.0
|
||||||
|
vitest: ^2.0.0 || ^3.0.0 || ^4.0.0
|
||||||
vue: ^3.0.0
|
vue: ^3.0.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@lynx-js/react':
|
'@lynx-js/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
'@prisma/client':
|
||||||
|
optional: true
|
||||||
'@sveltejs/kit':
|
'@sveltejs/kit':
|
||||||
optional: true
|
optional: true
|
||||||
'@tanstack/react-start':
|
'@tanstack/react-start':
|
||||||
optional: true
|
optional: true
|
||||||
|
'@tanstack/solid-start':
|
||||||
|
optional: true
|
||||||
|
better-sqlite3:
|
||||||
|
optional: true
|
||||||
|
drizzle-kit:
|
||||||
|
optional: true
|
||||||
|
drizzle-orm:
|
||||||
|
optional: true
|
||||||
|
mongodb:
|
||||||
|
optional: true
|
||||||
|
mysql2:
|
||||||
|
optional: true
|
||||||
next:
|
next:
|
||||||
optional: true
|
optional: true
|
||||||
|
pg:
|
||||||
|
optional: true
|
||||||
|
prisma:
|
||||||
|
optional: true
|
||||||
react:
|
react:
|
||||||
optional: true
|
optional: true
|
||||||
react-dom:
|
react-dom:
|
||||||
@@ -3434,11 +3550,13 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
svelte:
|
svelte:
|
||||||
optional: true
|
optional: true
|
||||||
|
vitest:
|
||||||
|
optional: true
|
||||||
vue:
|
vue:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
better-call@1.1.4:
|
better-call@1.1.8:
|
||||||
resolution: {integrity: sha512-NJouLY6IVKv0nDuFoc6FcbKDFzEnmgMNofC9F60Mwx1Ecm7X6/Ecyoe5b+JSVZ42F/0n46/M89gbYP1ZCVv8xQ==}
|
resolution: {integrity: sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
zod: ^4.0.0
|
zod: ^4.0.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@@ -3538,6 +3656,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
camelcase@6.3.0:
|
||||||
|
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001769:
|
caniuse-lite@1.0.30001769:
|
||||||
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
|
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
|
||||||
|
|
||||||
@@ -4337,6 +4459,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==}
|
resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
fast-xml-parser@5.3.5:
|
||||||
|
resolution: {integrity: sha512-JeaA2Vm9ffQKp9VjvfzObuMCjUYAp5WDYhRYL5LrBPY/jUDlUtOvDfot0vKSkB9tuX885BDHjtw4fZadD95wnA==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
fastq@1.20.1:
|
fastq@1.20.1:
|
||||||
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
||||||
|
|
||||||
@@ -5221,10 +5347,6 @@ packages:
|
|||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
ms@4.0.0-nightly.202508271359:
|
|
||||||
resolution: {integrity: sha512-WC/Eo7NzFrOV/RRrTaI0fxKVbNCzEy76j2VqNV8SxDf9D69gSE2Lh0QwYvDlhiYmheBYExAvEAxVf5NoN0cj2A==}
|
|
||||||
engines: {node: '>=20'}
|
|
||||||
|
|
||||||
mz@2.7.0:
|
mz@2.7.0:
|
||||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||||
|
|
||||||
@@ -5375,6 +5497,9 @@ packages:
|
|||||||
node-releases@2.0.27:
|
node-releases@2.0.27:
|
||||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||||
|
|
||||||
|
node-rsa@1.1.1:
|
||||||
|
resolution: {integrity: sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==}
|
||||||
|
|
||||||
nodemailer@7.0.13:
|
nodemailer@7.0.13:
|
||||||
resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==}
|
resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@@ -5489,6 +5614,9 @@ packages:
|
|||||||
package-json-from-dist@1.0.1:
|
package-json-from-dist@1.0.1:
|
||||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
||||||
|
pako@1.0.11:
|
||||||
|
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||||
|
|
||||||
param-case@3.0.4:
|
param-case@3.0.4:
|
||||||
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
|
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
|
||||||
|
|
||||||
@@ -5729,6 +5857,13 @@ packages:
|
|||||||
pure-rand@6.1.0:
|
pure-rand@6.1.0:
|
||||||
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
|
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
|
||||||
|
|
||||||
|
pvtsutils@1.3.6:
|
||||||
|
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
|
||||||
|
|
||||||
|
pvutils@1.1.5:
|
||||||
|
resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
qr.js@0.0.0:
|
qr.js@0.0.0:
|
||||||
resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==}
|
resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==}
|
||||||
|
|
||||||
@@ -5897,6 +6032,9 @@ packages:
|
|||||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
|
reflect-metadata@0.2.2:
|
||||||
|
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
|
||||||
|
|
||||||
reflect.getprototypeof@1.0.10:
|
reflect.getprototypeof@1.0.10:
|
||||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -5979,6 +6117,9 @@ packages:
|
|||||||
safer-buffer@2.1.2:
|
safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
|
samlify@2.10.2:
|
||||||
|
resolution: {integrity: sha512-y5s1cHwclqwP8h7K2Wj9SfP1q+1S9+jrs5OAegYTLAiuFi7nDvuKqbiXLmUTvYPMpzHcX94wTY2+D604jgTKvA==}
|
||||||
|
|
||||||
sax@1.4.4:
|
sax@1.4.4:
|
||||||
resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==}
|
resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==}
|
||||||
engines: {node: '>=11.0.0'}
|
engines: {node: '>=11.0.0'}
|
||||||
@@ -6235,6 +6376,9 @@ packages:
|
|||||||
strnum@1.1.2:
|
strnum@1.1.2:
|
||||||
resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==}
|
resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==}
|
||||||
|
|
||||||
|
strnum@2.1.2:
|
||||||
|
resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==}
|
||||||
|
|
||||||
styled-jsx@5.1.6:
|
styled-jsx@5.1.6:
|
||||||
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
|
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
@@ -6391,6 +6535,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
|
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
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==}
|
||||||
|
|
||||||
@@ -6399,6 +6546,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'}
|
||||||
|
|
||||||
tw-animate-css@1.4.0:
|
tw-animate-css@1.4.0:
|
||||||
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||||
|
|
||||||
@@ -6537,6 +6688,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
uuid@8.3.2:
|
||||||
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
uuid@9.0.1:
|
uuid@9.0.1:
|
||||||
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -6693,10 +6848,20 @@ packages:
|
|||||||
utf-8-validate:
|
utf-8-validate:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
xml-crypto@6.1.2:
|
||||||
|
resolution: {integrity: sha512-leBOVQdVi8FvPJrMYoum7Ici9qyxfE4kVi+AkpUoYCSXaQF4IlBm1cneTK9oAxR61LpYxTx7lNcsnBIeRpGW2w==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
xml-escape@1.1.0:
|
||||||
|
resolution: {integrity: sha512-B/T4sDK8Z6aUh/qNr7mjKAwwncIljFuUP+DO/D5hloYFj+90O88z8Wf7oSucZTHxBAsC1/CTP4rtx/x1Uf72Mg==}
|
||||||
|
|
||||||
xml2js@0.6.2:
|
xml2js@0.6.2:
|
||||||
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
|
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
|
|
||||||
|
xml@1.0.1:
|
||||||
|
resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==}
|
||||||
|
|
||||||
xmlbuilder@11.0.1:
|
xmlbuilder@11.0.1:
|
||||||
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
|
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -6705,6 +6870,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
|
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
xpath@0.0.32:
|
||||||
|
resolution: {integrity: sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==}
|
||||||
|
engines: {node: '>=0.6.0'}
|
||||||
|
|
||||||
|
xpath@0.0.33:
|
||||||
|
resolution: {integrity: sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==}
|
||||||
|
engines: {node: '>=0.6.0'}
|
||||||
|
|
||||||
xtend@4.0.2:
|
xtend@4.0.2:
|
||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
@@ -6774,6 +6947,12 @@ snapshots:
|
|||||||
'@jridgewell/gen-mapping': 0.3.13
|
'@jridgewell/gen-mapping': 0.3.13
|
||||||
'@jridgewell/trace-mapping': 0.3.31
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
|
||||||
|
'@authenio/xml-encryption@2.0.2':
|
||||||
|
dependencies:
|
||||||
|
'@xmldom/xmldom': 0.8.11
|
||||||
|
escape-html: 1.0.3
|
||||||
|
xpath: 0.0.32
|
||||||
|
|
||||||
'@babel/code-frame@7.29.0':
|
'@babel/code-frame@7.29.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-validator-identifier': 7.28.5
|
'@babel/helper-validator-identifier': 7.28.5
|
||||||
@@ -6923,26 +7102,48 @@ snapshots:
|
|||||||
|
|
||||||
'@balena/dockerignore@1.0.2': {}
|
'@balena/dockerignore@1.0.2': {}
|
||||||
|
|
||||||
'@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)':
|
'@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/utils': 0.3.0
|
'@better-auth/utils': 0.3.0
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.21
|
||||||
'@standard-schema/spec': 1.1.0
|
'@standard-schema/spec': 1.1.0
|
||||||
better-call: 1.1.4(zod@4.3.6)
|
better-call: 1.1.8(zod@3.25.76)
|
||||||
jose: 6.1.3
|
jose: 6.1.3
|
||||||
kysely: 0.28.11
|
kysely: 0.28.11
|
||||||
nanostores: 1.1.0
|
nanostores: 1.1.0
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
|
|
||||||
'@better-auth/telemetry@1.4.5(@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))':
|
'@better-auth/passkey@1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-auth@1.4.18(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.9)(drizzle-orm@0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3)))(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(better-call@1.1.8(zod@3.25.76))(nanostores@1.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
|
'@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
|
||||||
'@better-auth/utils': 0.3.0
|
'@better-auth/utils': 0.3.0
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.21
|
||||||
|
'@simplewebauthn/browser': 13.2.2
|
||||||
|
'@simplewebauthn/server': 13.2.2
|
||||||
|
better-auth: 1.4.18(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.9)(drizzle-orm@0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3)))(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
|
better-call: 1.1.8(zod@3.25.76)
|
||||||
|
nanostores: 1.1.0
|
||||||
|
zod: 4.3.6
|
||||||
|
|
||||||
|
'@better-auth/sso@1.4.18(@better-auth/utils@0.3.0)(better-auth@1.4.18(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.9)(drizzle-orm@0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3)))(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))':
|
||||||
|
dependencies:
|
||||||
|
'@better-auth/utils': 0.3.0
|
||||||
|
'@better-fetch/fetch': 1.1.21
|
||||||
|
better-auth: 1.4.18(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.9)(drizzle-orm@0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3)))(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
|
fast-xml-parser: 5.3.5
|
||||||
|
jose: 6.1.3
|
||||||
|
samlify: 2.10.2
|
||||||
|
zod: 4.3.6
|
||||||
|
|
||||||
|
'@better-auth/telemetry@1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))':
|
||||||
|
dependencies:
|
||||||
|
'@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
|
||||||
|
'@better-auth/utils': 0.3.0
|
||||||
|
'@better-fetch/fetch': 1.1.21
|
||||||
|
|
||||||
'@better-auth/utils@0.3.0': {}
|
'@better-auth/utils@0.3.0': {}
|
||||||
|
|
||||||
'@better-fetch/fetch@1.1.18': {}
|
'@better-fetch/fetch@1.1.21': {}
|
||||||
|
|
||||||
'@chevrotain/cst-dts-gen@10.4.2':
|
'@chevrotain/cst-dts-gen@10.4.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7377,6 +7578,8 @@ snapshots:
|
|||||||
protobufjs: 7.5.4
|
protobufjs: 7.5.4
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
|
|
||||||
|
'@hexagon/base64@1.1.28': {}
|
||||||
|
|
||||||
'@hookform/resolvers@5.2.2(react-hook-form@7.71.1(react@19.2.4))':
|
'@hookform/resolvers@5.2.2(react-hook-form@7.71.1(react@19.2.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/utils': 0.3.0
|
'@standard-schema/utils': 0.3.0
|
||||||
@@ -7625,6 +7828,8 @@ snapshots:
|
|||||||
|
|
||||||
'@js-sdsl/ordered-map@4.4.2': {}
|
'@js-sdsl/ordered-map@4.4.2': {}
|
||||||
|
|
||||||
|
'@levischuck/tiny-cbor@0.2.11': {}
|
||||||
|
|
||||||
'@lottiefiles/dotlottie-react@0.13.3(react@19.0.0)':
|
'@lottiefiles/dotlottie-react@0.13.3(react@19.0.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lottiefiles/dotlottie-web': 0.42.0
|
'@lottiefiles/dotlottie-web': 0.42.0
|
||||||
@@ -7719,6 +7924,102 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@noble/hashes': 1.8.0
|
'@noble/hashes': 1.8.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
|
||||||
|
|
||||||
'@phc/format@1.0.0': {}
|
'@phc/format@1.0.0': {}
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
@@ -9204,6 +9505,19 @@ snapshots:
|
|||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
selderee: 0.11.0
|
selderee: 0.11.0
|
||||||
|
|
||||||
|
'@simplewebauthn/browser@13.2.2': {}
|
||||||
|
|
||||||
|
'@simplewebauthn/server@13.2.2':
|
||||||
|
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
|
||||||
|
|
||||||
'@socket.io/component-emitter@3.1.2': {}
|
'@socket.io/component-emitter@3.1.2': {}
|
||||||
|
|
||||||
'@standard-schema/spec@1.1.0': {}
|
'@standard-schema/spec@1.1.0': {}
|
||||||
@@ -9700,6 +10014,10 @@ snapshots:
|
|||||||
'@webassemblyjs/ast': 1.14.1
|
'@webassemblyjs/ast': 1.14.1
|
||||||
'@xtuc/long': 4.2.2
|
'@xtuc/long': 4.2.2
|
||||||
|
|
||||||
|
'@xmldom/is-dom-node@1.0.1': {}
|
||||||
|
|
||||||
|
'@xmldom/xmldom@0.8.11': {}
|
||||||
|
|
||||||
'@xtuc/ieee754@1.2.0': {}
|
'@xtuc/ieee754@1.2.0': {}
|
||||||
|
|
||||||
'@xtuc/long@4.2.2': {}
|
'@xtuc/long@4.2.2': {}
|
||||||
@@ -10018,6 +10336,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
|
asn1js@3.0.7:
|
||||||
|
dependencies:
|
||||||
|
pvtsutils: 1.3.6
|
||||||
|
pvutils: 1.1.5
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
ast-types-flow@0.0.8: {}
|
ast-types-flow@0.0.8: {}
|
||||||
|
|
||||||
async-exit-hook@2.0.1: {}
|
async-exit-hook@2.0.1: {}
|
||||||
@@ -10065,30 +10389,43 @@ snapshots:
|
|||||||
|
|
||||||
bcryptjs@2.4.3: {}
|
bcryptjs@2.4.3: {}
|
||||||
|
|
||||||
better-auth@1.4.5(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
better-auth@1.4.18(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(drizzle-kit@0.31.9)(drizzle-orm@0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3)))(next@16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
|
'@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
|
||||||
'@better-auth/telemetry': 1.4.5(@better-auth/core@1.4.5(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.18)(better-call@1.1.4(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))
|
'@better-auth/telemetry': 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@3.25.76))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0))
|
||||||
'@better-auth/utils': 0.3.0
|
'@better-auth/utils': 0.3.0
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.21
|
||||||
'@noble/ciphers': 2.1.1
|
'@noble/ciphers': 2.1.1
|
||||||
'@noble/hashes': 2.0.1
|
'@noble/hashes': 2.0.1
|
||||||
better-call: 1.1.4(zod@4.3.6)
|
better-call: 1.1.8(zod@4.3.6)
|
||||||
defu: 6.1.4
|
defu: 6.1.4
|
||||||
jose: 6.1.3
|
jose: 6.1.3
|
||||||
kysely: 0.28.11
|
kysely: 0.28.11
|
||||||
ms: 4.0.0-nightly.202508271359
|
|
||||||
nanostores: 1.1.0
|
nanostores: 1.1.0
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
'@prisma/client': 6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3)
|
||||||
|
drizzle-kit: 0.31.9
|
||||||
|
drizzle-orm: 0.43.1(@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(kysely@0.28.11)(pg@8.18.0)(prisma@6.7.0(typescript@5.9.3))
|
||||||
next: 16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
next: 16.1.5(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
|
pg: 8.18.0
|
||||||
|
prisma: 6.7.0(typescript@5.9.3)
|
||||||
react: 19.2.4
|
react: 19.2.4
|
||||||
react-dom: 19.2.4(react@19.2.4)
|
react-dom: 19.2.4(react@19.2.4)
|
||||||
|
|
||||||
better-call@1.1.4(zod@4.3.6):
|
better-call@1.1.8(zod@3.25.76):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/utils': 0.3.0
|
'@better-auth/utils': 0.3.0
|
||||||
'@better-fetch/fetch': 1.1.18
|
'@better-fetch/fetch': 1.1.21
|
||||||
|
rou3: 0.7.12
|
||||||
|
set-cookie-parser: 2.7.2
|
||||||
|
optionalDependencies:
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
better-call@1.1.8(zod@4.3.6):
|
||||||
|
dependencies:
|
||||||
|
'@better-auth/utils': 0.3.0
|
||||||
|
'@better-fetch/fetch': 1.1.21
|
||||||
rou3: 0.7.12
|
rou3: 0.7.12
|
||||||
set-cookie-parser: 2.7.2
|
set-cookie-parser: 2.7.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -10209,6 +10546,8 @@ snapshots:
|
|||||||
|
|
||||||
camelcase-css@2.0.1: {}
|
camelcase-css@2.0.1: {}
|
||||||
|
|
||||||
|
camelcase@6.3.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001769: {}
|
caniuse-lite@1.0.30001769: {}
|
||||||
|
|
||||||
capital-case@1.0.4:
|
capital-case@1.0.4:
|
||||||
@@ -11217,6 +11556,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
strnum: 1.1.2
|
strnum: 1.1.2
|
||||||
|
|
||||||
|
fast-xml-parser@5.3.5:
|
||||||
|
dependencies:
|
||||||
|
strnum: 2.1.2
|
||||||
|
|
||||||
fastq@1.20.1:
|
fastq@1.20.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
reusify: 1.1.0
|
reusify: 1.1.0
|
||||||
@@ -12070,8 +12413,6 @@ snapshots:
|
|||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
ms@4.0.0-nightly.202508271359: {}
|
|
||||||
|
|
||||||
mz@2.7.0:
|
mz@2.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
any-promise: 1.3.0
|
any-promise: 1.3.0
|
||||||
@@ -12189,6 +12530,10 @@ snapshots:
|
|||||||
|
|
||||||
node-releases@2.0.27: {}
|
node-releases@2.0.27: {}
|
||||||
|
|
||||||
|
node-rsa@1.1.1:
|
||||||
|
dependencies:
|
||||||
|
asn1: 0.2.6
|
||||||
|
|
||||||
nodemailer@7.0.13: {}
|
nodemailer@7.0.13: {}
|
||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
@@ -12328,6 +12673,8 @@ snapshots:
|
|||||||
|
|
||||||
package-json-from-dist@1.0.1: {}
|
package-json-from-dist@1.0.1: {}
|
||||||
|
|
||||||
|
pako@1.0.11: {}
|
||||||
|
|
||||||
param-case@3.0.4:
|
param-case@3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
dot-case: 3.0.4
|
dot-case: 3.0.4
|
||||||
@@ -12559,6 +12906,12 @@ snapshots:
|
|||||||
|
|
||||||
pure-rand@6.1.0: {}
|
pure-rand@6.1.0: {}
|
||||||
|
|
||||||
|
pvtsutils@1.3.6:
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
pvutils@1.1.5: {}
|
||||||
|
|
||||||
qr.js@0.0.0: {}
|
qr.js@0.0.0: {}
|
||||||
|
|
||||||
qs@6.14.1:
|
qs@6.14.1:
|
||||||
@@ -12784,6 +13137,8 @@ snapshots:
|
|||||||
tiny-invariant: 1.3.3
|
tiny-invariant: 1.3.3
|
||||||
victory-vendor: 36.9.2
|
victory-vendor: 36.9.2
|
||||||
|
|
||||||
|
reflect-metadata@0.2.2: {}
|
||||||
|
|
||||||
reflect.getprototypeof@1.0.10:
|
reflect.getprototypeof@1.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
@@ -12883,6 +13238,20 @@ snapshots:
|
|||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
|
samlify@2.10.2:
|
||||||
|
dependencies:
|
||||||
|
'@authenio/xml-encryption': 2.0.2
|
||||||
|
'@xmldom/xmldom': 0.8.11
|
||||||
|
camelcase: 6.3.0
|
||||||
|
node-forge: 1.3.3
|
||||||
|
node-rsa: 1.1.1
|
||||||
|
pako: 1.0.11
|
||||||
|
uuid: 8.3.2
|
||||||
|
xml: 1.0.1
|
||||||
|
xml-crypto: 6.1.2
|
||||||
|
xml-escape: 1.1.0
|
||||||
|
xpath: 0.0.32
|
||||||
|
|
||||||
sax@1.4.4: {}
|
sax@1.4.4: {}
|
||||||
|
|
||||||
scheduler@0.25.0: {}
|
scheduler@0.25.0: {}
|
||||||
@@ -13270,6 +13639,8 @@ snapshots:
|
|||||||
|
|
||||||
strnum@1.1.2: {}
|
strnum@1.1.2: {}
|
||||||
|
|
||||||
|
strnum@2.1.2: {}
|
||||||
|
|
||||||
styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.0.0):
|
styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.0.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
client-only: 0.0.1
|
client-only: 0.0.1
|
||||||
@@ -13456,6 +13827,8 @@ snapshots:
|
|||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
strip-bom: 3.0.0
|
strip-bom: 3.0.0
|
||||||
|
|
||||||
|
tslib@1.14.1: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
tsx@4.21.0:
|
tsx@4.21.0:
|
||||||
@@ -13465,6 +13838,10 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
tsyringe@4.10.0:
|
||||||
|
dependencies:
|
||||||
|
tslib: 1.14.1
|
||||||
|
|
||||||
tw-animate-css@1.4.0: {}
|
tw-animate-css@1.4.0: {}
|
||||||
|
|
||||||
tweetnacl@0.14.5: {}
|
tweetnacl@0.14.5: {}
|
||||||
@@ -13651,6 +14028,8 @@ snapshots:
|
|||||||
|
|
||||||
uuid@11.1.0: {}
|
uuid@11.1.0: {}
|
||||||
|
|
||||||
|
uuid@8.3.2: {}
|
||||||
|
|
||||||
uuid@9.0.1: {}
|
uuid@9.0.1: {}
|
||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
@@ -13844,15 +14223,29 @@ snapshots:
|
|||||||
|
|
||||||
ws@8.19.0: {}
|
ws@8.19.0: {}
|
||||||
|
|
||||||
|
xml-crypto@6.1.2:
|
||||||
|
dependencies:
|
||||||
|
'@xmldom/is-dom-node': 1.0.1
|
||||||
|
'@xmldom/xmldom': 0.8.11
|
||||||
|
xpath: 0.0.33
|
||||||
|
|
||||||
|
xml-escape@1.1.0: {}
|
||||||
|
|
||||||
xml2js@0.6.2:
|
xml2js@0.6.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
sax: 1.4.4
|
sax: 1.4.4
|
||||||
xmlbuilder: 11.0.1
|
xmlbuilder: 11.0.1
|
||||||
|
|
||||||
|
xml@1.0.1: {}
|
||||||
|
|
||||||
xmlbuilder@11.0.1: {}
|
xmlbuilder@11.0.1: {}
|
||||||
|
|
||||||
xmlhttprequest-ssl@2.1.2: {}
|
xmlhttprequest-ssl@2.1.2: {}
|
||||||
|
|
||||||
|
xpath@0.0.32: {}
|
||||||
|
|
||||||
|
xpath@0.0.33: {}
|
||||||
|
|
||||||
xtend@4.0.2: {}
|
xtend@4.0.2: {}
|
||||||
|
|
||||||
y18n@5.0.8: {}
|
y18n@5.0.8: {}
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export interface AuthProviderConfig {
|
|
||||||
id: "google" | "github" | "credential";
|
|
||||||
isActive: boolean;
|
|
||||||
icon: string;
|
|
||||||
isManual?: boolean;
|
|
||||||
credentials?: {
|
|
||||||
clientId: string;
|
|
||||||
clientSecret: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const PORTABASE_DEFAULT_SETTINGS = {
|
export const PORTABASE_DEFAULT_SETTINGS = {
|
||||||
SECURITY: {
|
SECURITY: {
|
||||||
CSP: {
|
CSP: {
|
||||||
@@ -75,36 +61,3 @@ export const PORTABASE_DEFAULT_SETTINGS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const SUPPORTED_PROVIDERS: AuthProviderConfig[] = [
|
|
||||||
{
|
|
||||||
id: "google",
|
|
||||||
icon: "hugeicons:chrome",
|
|
||||||
isActive: Boolean(process.env.AUTH_GOOGLE_METHOD),
|
|
||||||
// isManual: true,
|
|
||||||
credentials: {
|
|
||||||
clientId: process.env.AUTH_GOOGLE_ID || "",
|
|
||||||
clientSecret: process.env.AUTH_GOOGLE_SECRET || "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "github",
|
|
||||||
icon: "iconoir:github",
|
|
||||||
isActive: Boolean(process.env.AUTH_GITHUB_METHOD),
|
|
||||||
credentials: {
|
|
||||||
clientId: process.env.AUTH_GITHUB_ID || "",
|
|
||||||
clientSecret: process.env.AUTH_GITHUB_SECRET || "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "credential",
|
|
||||||
isActive: true,
|
|
||||||
icon: "proicons:key",
|
|
||||||
isManual: true,
|
|
||||||
credentials: {
|
|
||||||
clientId: "",
|
|
||||||
clientSecret: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ import {PasswordInput} from "@/components/ui/password-input";
|
|||||||
|
|
||||||
export type loginFormProps = {
|
export type loginFormProps = {
|
||||||
defaultValues?: LoginType;
|
defaultValues?: LoginType;
|
||||||
|
isPasskeyEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LoginForm = (props: loginFormProps) => {
|
export const LoginForm = (props: loginFormProps) => {
|
||||||
|
const {isPasskeyEnabled = false} = props;
|
||||||
|
|
||||||
|
|
||||||
const [urlParams, setUrlParams] = useState<URLSearchParams>();
|
const [urlParams, setUrlParams] = useState<URLSearchParams>();
|
||||||
@@ -106,7 +108,7 @@ export const LoginForm = (props: loginFormProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<PasswordInput autoComplete="current-password webauthn"
|
<PasswordInput autoComplete={isPasskeyEnabled ? "current-password webauthn" : "current-password"}
|
||||||
placeholder={"Enter your password"} {...field} />
|
placeholder={"Enter your password"} {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage/>
|
<FormMessage/>
|
||||||
|
|||||||
@@ -1,29 +1,52 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { authClient } from "@/lib/auth/auth-client";
|
import { authClient, passkey } from "@/lib/auth/auth-client";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {AuthProviderConfig} from "../../../../portabase.config";
|
import { Icon } from "@iconify/react";
|
||||||
import {Icon} from "@iconify/react";
|
import { AuthProviderConfig } from "@/lib/auth/config";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export function SocialAuthButtons({ providers }: { providers: AuthProviderConfig[] }) {
|
export function SocialAuthButtons({ providers }: { providers: AuthProviderConfig[] }) {
|
||||||
const socialProviders = providers.filter(p => p.isActive && !p.isManual);
|
const socialProviders = providers.filter((p) => p.isActive && p.type !== "credential");
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState<string | null>(null);
|
const [isLoading, setIsLoading] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleSocialSignIn = async (providerId: string) => {
|
const handleSocialSignIn = async (provider: AuthProviderConfig) => {
|
||||||
setIsLoading(providerId);
|
setIsLoading(provider.id);
|
||||||
try {
|
try {
|
||||||
const { error } = await authClient.signIn.social({
|
let result;
|
||||||
provider: providerId as "google" | "github",
|
if (provider.id === "passkey") {
|
||||||
|
result = await authClient.signIn.passkey({
|
||||||
|
fetchOptions: {
|
||||||
|
onSuccess() {
|
||||||
|
router.push("/dashboard");
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error("An error occurred during passkey authentication. Please try again.");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (provider.type === "sso") {
|
||||||
|
result = await authClient.signIn.sso({
|
||||||
|
providerId: provider.id,
|
||||||
|
providerType: "oidc",
|
||||||
callbackURL: "/dashboard",
|
callbackURL: "/dashboard",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
|
||||||
toast.error("An error occurred while signing in with the provider. Please try again.");
|
|
||||||
} else {
|
} else {
|
||||||
|
result = await authClient.signIn.social({
|
||||||
|
provider: provider.id as "google" | "github",
|
||||||
|
callbackURL: "/dashboard",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result?.error) {
|
||||||
|
toast.error("An error occurred while signing in with the provider. Please try again.");
|
||||||
|
} else if (provider.id !== "passkey") {
|
||||||
toast.success("Redirecting to provider...");
|
toast.success("Redirecting to provider...");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -38,29 +61,24 @@ export function SocialAuthButtons({ providers }: { providers: AuthProviderConfig
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
{socialProviders.map((provider) => (
|
{socialProviders.map((provider) => (
|
||||||
<Button key={provider.id} variant="outline" className="w-full gap-2" onClick={() => handleSocialSignIn(provider.id)} disabled={!!isLoading}>
|
<Button key={provider.id} variant="outline" className="w-full gap-2" onClick={() => handleSocialSignIn(provider)} disabled={!!isLoading}>
|
||||||
{isLoading === provider.id ? <Loader2 className="h-4 w-4 animate-spin" /> :
|
{isLoading === provider.id ? (
|
||||||
<Icon icon={provider.icon} className="h-4 w-4"/>
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
}
|
) : provider.icon.startsWith("/") || provider.icon.startsWith("http") ? (
|
||||||
<span>{PROVIDERS_TEXT[provider.id].title}</span>
|
<Image
|
||||||
|
src={provider.icon}
|
||||||
|
alt={provider.id || "icon"}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className="h-4 w-4"
|
||||||
|
unoptimized={provider.icon.startsWith("http")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon icon={provider.icon} className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
<span>{provider.title || provider.name}</span>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const PROVIDERS_TEXT = {
|
|
||||||
credential: {
|
|
||||||
title: "Password",
|
|
||||||
description: "Use your email address and password to sign in."
|
|
||||||
},
|
|
||||||
google: {
|
|
||||||
title: "Google",
|
|
||||||
description: "Sign in with your Google account."
|
|
||||||
},
|
|
||||||
github: {
|
|
||||||
title: "GitHub",
|
|
||||||
description: "Sign in with your GitHub account."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import {currentUser} from "@/lib/auth/current-user";
|
import { currentUser } from "@/lib/auth/current-user";
|
||||||
import {getAccounts, getSession, getSessions} from "@/lib/auth/auth";
|
import { getAccounts, getSession, getSessions } from "@/lib/auth/auth";
|
||||||
import {LoggedInButtonClient} from "./logged-in-button";
|
import { LoggedInButtonClient } from "./logged-in-button";
|
||||||
import {SUPPORTED_PROVIDERS} from "../../../../../../portabase.config";
|
import { SUPPORTED_PROVIDERS } from "@/lib/auth/config";
|
||||||
|
|
||||||
|
|
||||||
export const LoggedInButton = async () => {
|
export const LoggedInButton = async () => {
|
||||||
const user = await currentUser();
|
const user = await currentUser();
|
||||||
@@ -10,7 +9,6 @@ export const LoggedInButton = async () => {
|
|||||||
const currentSession = await getSession();
|
const currentSession = await getSession();
|
||||||
const accounts = await getAccounts();
|
const accounts = await getAccounts();
|
||||||
|
|
||||||
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,29 +1,21 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {ChevronsUpDown} from "lucide-react";
|
import { ChevronsUpDown } from "lucide-react";
|
||||||
import {Avatar, AvatarFallback, AvatarImage} from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import {SidebarMenuButton} from "@/components/ui/sidebar";
|
import { SidebarMenuButton } from "@/components/ui/sidebar";
|
||||||
import {LoggedInDropdown} from "./logged-in-dropdown";
|
import { LoggedInDropdown } from "./logged-in-dropdown";
|
||||||
import {Account, Session, User} from "better-auth";
|
import { Account, Session, User } from "better-auth";
|
||||||
import {AuthProviderConfig} from "../../../../../../portabase.config";
|
import { AuthProviderConfig } from "@/lib/auth/config";
|
||||||
|
|
||||||
type LoggedInButtonClientProps = {
|
type LoggedInButtonClientProps = {
|
||||||
user: User,
|
user: User;
|
||||||
sessions: Session[],
|
sessions: Session[];
|
||||||
currentSession: Session,
|
currentSession: Session;
|
||||||
accounts: Account[],
|
accounts: Account[];
|
||||||
providers: AuthProviderConfig[]
|
providers: AuthProviderConfig[];
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export const LoggedInButtonClient = ({
|
|
||||||
user,
|
|
||||||
sessions,
|
|
||||||
currentSession,
|
|
||||||
accounts,
|
|
||||||
providers
|
|
||||||
}: LoggedInButtonClientProps) => {
|
|
||||||
|
|
||||||
|
export const LoggedInButtonClient = ({ user, sessions, currentSession, accounts, providers }: LoggedInButtonClientProps) => {
|
||||||
return (
|
return (
|
||||||
<LoggedInDropdown
|
<LoggedInDropdown
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -40,20 +32,16 @@ export const LoggedInButtonClient = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Avatar className="size-6">
|
<Avatar className="size-6">
|
||||||
<AvatarFallback>{user.name[0].toUpperCase()}</AvatarFallback>
|
<AvatarFallback>{user.name[0].toUpperCase()}</AvatarFallback>
|
||||||
{user.image && <AvatarImage src={user.image}/>}
|
{user.image && <AvatarImage src={user.image} />}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex flex-col items-start">
|
||||||
<span
|
<span className="text-sm font-medium first-letter:capitalize max-w-[170px] truncate">{user.name}</span>
|
||||||
className="text-sm font-medium first-letter:capitalize max-w-[170px] truncate">{user.name}</span>
|
<span className="text-xs text-muted-foreground max-w-[170px] truncate" title={user.email}>
|
||||||
<span
|
|
||||||
className="text-xs text-muted-foreground max-w-[170px] truncate"
|
|
||||||
title={user.email}
|
|
||||||
>
|
|
||||||
{user.email}
|
{user.email}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50"/>
|
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</LoggedInDropdown>
|
</LoggedInDropdown>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {PropsWithChildren, ReactNode, useState} from "react";
|
import { PropsWithChildren, ReactNode, useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
@@ -10,11 +10,11 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSepara
|
|||||||
|
|
||||||
import { signOut } from "@/lib/auth/auth-client";
|
import { signOut } from "@/lib/auth/auth-client";
|
||||||
|
|
||||||
import {ProfileModal} from "@/components/wrappers/dashboard/common/profile/profile-modal";
|
import { ProfileModal } from "@/components/wrappers/dashboard/common/profile/profile-modal";
|
||||||
|
|
||||||
import {Account, Session, User as UserType} from "@/db/schema/02_user";
|
import { Account, Session, User as UserType } from "@/db/schema/02_user";
|
||||||
|
|
||||||
import {AuthProviderConfig} from "../../../../../../portabase.config";
|
import { AuthProviderConfig } from "@/lib/auth/config";
|
||||||
|
|
||||||
export type LoggedInDropdownProps = PropsWithChildren<{
|
export type LoggedInDropdownProps = PropsWithChildren<{
|
||||||
user: UserType;
|
user: UserType;
|
||||||
@@ -22,13 +22,10 @@ export type LoggedInDropdownProps = PropsWithChildren<{
|
|||||||
currentSession: Session;
|
currentSession: Session;
|
||||||
accounts: Account[];
|
accounts: Account[];
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
providers: AuthProviderConfig[]
|
providers: AuthProviderConfig[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const LoggedInDropdown = ({ user, sessions, currentSession, accounts, children, providers }: LoggedInDropdownProps) => {
|
export const LoggedInDropdown = ({ user, sessions, currentSession, accounts, children, providers }: LoggedInDropdownProps) => {
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
@@ -85,9 +82,5 @@ export const LoggedInDropdown = ({ user, sessions, currentSession, accounts, chi
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||||
import { Tabs, TabsContent } from "@/components/ui/tabs";
|
import { Tabs, TabsContent } from "@/components/ui/tabs";
|
||||||
import { Account, Session, User } from "@/db/schema/02_user";
|
|
||||||
import { ProfileSidebar } from "./profile-sidebar";
|
import { ProfileSidebar } from "./profile-sidebar";
|
||||||
import {ProfileProviders} from "@/components/wrappers/dashboard/profile/profile-providers";
|
import { AuthProviderConfig } from "@/lib/auth/config";
|
||||||
import {ProfileAccount} from "@/components/wrappers/dashboard/profile/profile-account";
|
import { User, Session, Account } from "@/db/schema/02_user";
|
||||||
import {ProfileAppearance} from "@/components/wrappers/dashboard/profile/profile-apperance";
|
import { ProfileGeneral } from "../../profile/profile-general";
|
||||||
import {ProfileSecurity} from "@/components/wrappers/dashboard/profile/profile-security";
|
import { ProfileSecurity } from "../../profile/profile-security";
|
||||||
import {ProfileGeneral} from "@/components/wrappers/dashboard/profile/profile-general";
|
import { ProfileProviders } from "../../profile/profile-providers";
|
||||||
import {AuthProviderConfig} from "../../../../../../portabase.config";
|
import { ProfileAccount } from "../../profile/profile-account";
|
||||||
|
import { ProfileAppearance } from "../../profile/profile-apperance";
|
||||||
|
|
||||||
type ProfileModalProps = {
|
type ProfileModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -19,14 +18,13 @@ type ProfileModalProps = {
|
|||||||
currentSession: Session;
|
currentSession: Session;
|
||||||
accounts: Account[];
|
accounts: Account[];
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
providers: AuthProviderConfig[]
|
providers: AuthProviderConfig[];
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProfileModal = ({ user, sessions, currentSession, accounts, open, onOpenChange, providers }: ProfileModalProps) => {
|
export const ProfileModal = ({ user, sessions, currentSession, accounts, open, onOpenChange, providers }: ProfileModalProps) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="w-[95vw] h-[90vh] max-w-md lg:max-w-[1000px] lg:h-[800px] pb-6 overflow-hidden flex flex-col outline-none gap-0 rounded-xl bg-background">
|
<DialogContent className="w-[95vw] h-[90vh] max-w-md lg:max-w-[1000px] lg:h-[800px] pb-6 p-0 overflow-hidden flex flex-col outline-none gap-0 rounded-xl bg-background">
|
||||||
<DialogHeader className="sr-only">
|
<DialogHeader className="sr-only">
|
||||||
<DialogTitle>Settings</DialogTitle>
|
<DialogTitle>Settings</DialogTitle>
|
||||||
<DialogDescription>Manage your account settings</DialogDescription>
|
<DialogDescription>Manage your account settings</DialogDescription>
|
||||||
@@ -45,6 +43,8 @@ export const ProfileModal = ({ user, sessions, currentSession, accounts, open, o
|
|||||||
sessions={sessions}
|
sessions={sessions}
|
||||||
currentSession={currentSession}
|
currentSession={currentSession}
|
||||||
credentialAccount={accounts.find((acc) => acc.providerId === "credential")!}
|
credentialAccount={accounts.find((acc) => acc.providerId === "credential")!}
|
||||||
|
isPasswordEnabled={providers.some((p) => p.id === "credential")}
|
||||||
|
isPasskeyEnabled={providers.some((p) => p.id === "passkey")}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { use } from "react";
|
import React from "react";
|
||||||
import { TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { UserIcon, Settings, Palette, ShieldHalf, Workflow } from "lucide-react";
|
import { UserIcon, Settings, Palette, ShieldHalf, Workflow } from "lucide-react";
|
||||||
import { User } from "@/db/schema/02_user";
|
import { User } from "@/db/schema/02_user";
|
||||||
@@ -10,7 +10,6 @@ interface ProfileSidebarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ProfileSidebar({ user }: ProfileSidebarProps) {
|
export function ProfileSidebar({ user }: ProfileSidebarProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full lg:w-[260px] flex-shrink-0 lg:border-r bg-muted/10 p-4 lg:p-6 flex flex-col gap-4 border-b lg:border-b-0">
|
<div className="w-full lg:w-[260px] flex-shrink-0 lg:border-r bg-muted/10 p-4 lg:p-6 flex flex-col gap-4 border-b lg:border-b-0">
|
||||||
<div className="flex items-center px-2 mb-2">
|
<div className="flex items-center px-2 mb-2">
|
||||||
|
|||||||
@@ -3,13 +3,18 @@
|
|||||||
import { ServerActionResult } from "@/types/action-type";
|
import { ServerActionResult } from "@/types/action-type";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { auth } from "@/lib/auth/auth";
|
import { auth, getPasskeys, revokePasskey } from "@/lib/auth/auth";
|
||||||
import {userAction} from "@/lib/safe-actions/actions";
|
import { userAction } from "@/lib/safe-actions/actions";
|
||||||
|
|
||||||
const RevokeSessionSchema = z.object({
|
const RevokeSessionSchema = z.object({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const RevokePasskeySchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
export const revokeSessionAction = userAction.schema(RevokeSessionSchema).action(async ({ parsedInput }): Promise<ServerActionResult<{}>> => {
|
export const revokeSessionAction = userAction.schema(RevokeSessionSchema).action(async ({ parsedInput }): Promise<ServerActionResult<{}>> => {
|
||||||
try {
|
try {
|
||||||
const session = await auth.api.getSession({
|
const session = await auth.api.getSession({
|
||||||
@@ -99,3 +104,46 @@ export const revokeAllSessionsAction = userAction.action(async (): Promise<Serve
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const getPasskeysAction = userAction.action(async (): Promise<ServerActionResult<any[]>> => {
|
||||||
|
try {
|
||||||
|
const passkeys = await getPasskeys();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: passkeys || [],
|
||||||
|
actionSuccess: {
|
||||||
|
message: "passkeys_fetched",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "error_fetching_passkeys",
|
||||||
|
cause: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const revokePasskeyAction = userAction.schema(RevokePasskeySchema).action(async ({ parsedInput }): Promise<ServerActionResult<{}>> => {
|
||||||
|
try {
|
||||||
|
await revokePasskey(parsedInput.id);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: {},
|
||||||
|
actionSuccess: {
|
||||||
|
message: "passkey_revoked",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "error_revoking_passkey",
|
||||||
|
cause: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,37 +1,37 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, {useState} from "react";
|
import { useState } from "react";
|
||||||
import {Button} from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {Badge} from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {Loader2, AlertTriangle} from "lucide-react";
|
import { Loader2, AlertTriangle } from "lucide-react";
|
||||||
import {Account} from "@/db/schema/02_user";
|
import { authClient } from "@/lib/auth/auth-client";
|
||||||
import {authClient} from "@/lib/auth/auth-client";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import { toast } from "sonner";
|
||||||
import {toast} from "sonner";
|
import { useRouter } from "next/navigation";
|
||||||
import {useRouter} from "next/navigation";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
import {Alert, AlertDescription} from "@/components/ui/alert";
|
import { SetPasswordProfileProviderModal } from "./modal/set-password-modal";
|
||||||
import {SetPasswordProfileProviderModal} from "./modal/set-password-modal";
|
import { Icon } from "@iconify/react";
|
||||||
import {AuthProviderConfig} from "../../../../../portabase.config";
|
import Image from "next/image";
|
||||||
import {Icon} from "@iconify/react";
|
import { AuthProviderConfig } from "@/lib/auth/config";
|
||||||
|
import { Account } from "@/db/schema/02_user";
|
||||||
|
|
||||||
interface ProfileProviderProps {
|
interface ProfileProviderProps {
|
||||||
accounts: Account[];
|
accounts: Account[];
|
||||||
providers: AuthProviderConfig[]
|
providers: AuthProviderConfig[];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProfileProviders({accounts, providers}: ProfileProviderProps) {
|
export function ProfileProviders({ accounts, providers }: ProfileProviderProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const totalConnected = accounts.length;
|
const totalConnected = accounts.length;
|
||||||
const [loadingProvider, setLoadingProvider] = useState<string | null>(null);
|
const [loadingProvider, setLoadingProvider] = useState<string | null>(null);
|
||||||
|
|
||||||
const [isPasswordDialogOpen, setIsPasswordDialogOpen] = useState(false);
|
const [isPasswordDialogOpen, setIsPasswordDialogOpen] = useState(false);
|
||||||
|
|
||||||
const {mutate: unlinkAccount} = useMutation({
|
const { mutate: unlinkAccount } = useMutation({
|
||||||
mutationFn: async (providerId: string) => {
|
mutationFn: async (providerId: string) => {
|
||||||
setLoadingProvider(providerId);
|
setLoadingProvider(providerId);
|
||||||
const {error} = await authClient.unlinkAccount({ providerId });
|
const { error } = await authClient.unlinkAccount({ providerId });
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -45,17 +45,25 @@ export function ProfileProviders({accounts, providers}: ProfileProviderProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const {mutate: linkAccount} = useMutation({
|
const { mutate: linkAccount } = useMutation({
|
||||||
mutationFn: async (providerId: string) => {
|
mutationFn: async (provider: AuthProviderConfig) => {
|
||||||
setLoadingProvider(providerId);
|
setLoadingProvider(provider.id);
|
||||||
const {error} = await authClient.signIn.social({
|
let result;
|
||||||
provider: providerId as "google" | "github" | "credential",
|
if (provider.type === "sso") {
|
||||||
|
result = await authClient.signIn.sso({
|
||||||
|
providerId: provider.id,
|
||||||
|
providerType: "oidc",
|
||||||
callbackURL: "/dashboard",
|
callbackURL: "/dashboard",
|
||||||
});
|
});
|
||||||
if (error) throw error;
|
} else {
|
||||||
|
result = await authClient.signIn.social({
|
||||||
|
provider: provider.id as "google" | "github" | "credential",
|
||||||
|
callbackURL: "/dashboard",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (result.error) throw result.error;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Provider successfully Linked!");
|
|
||||||
setLoadingProvider(null);
|
setLoadingProvider(null);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
},
|
},
|
||||||
@@ -65,42 +73,42 @@ export function ProfileProviders({accounts, providers}: ProfileProviderProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const enterpriseProviders = providers.filter((p) => p.type === "sso" && p.id !== "passkey" && p.type !== "credential");
|
||||||
<div className="space-y-8 animate-in fade-in-50 duration-300">
|
const otherProviders = providers.filter((p) => p.type !== "sso" && p.id !== "passkey" && p.type !== "credential");
|
||||||
<div className="mb-6 space-y-1">
|
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Connected Accounts</h2>
|
|
||||||
<p className="text-sm text-muted-foreground">Manage the providers used to sign in to your account.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-4">
|
const renderProvider = (provider: AuthProviderConfig) => {
|
||||||
{providers.map((provider) => {
|
|
||||||
const linkedAccount = accounts.find((acc) => acc.providerId === provider.id);
|
const linkedAccount = accounts.find((acc) => acc.providerId === provider.id);
|
||||||
const isConnected = !!linkedAccount;
|
const isConnected = !!linkedAccount;
|
||||||
// const canUnlink = totalConnected > 1 || (totalConnected === 1 && !provider.isManual);
|
const canUnlink = totalConnected > 1 || (totalConnected === 1 && !provider.isManual);
|
||||||
const canUnlink = totalConnected > 1 ;
|
|
||||||
// const isLoading = isUnlinking || isLinking;
|
|
||||||
const isLoading = loadingProvider === provider.id;
|
const isLoading = loadingProvider === provider.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={provider.id}
|
<div key={provider.id} className="flex items-center justify-between p-4 border rounded-lg hover:bg-muted/30 transition-colors">
|
||||||
className="flex items-center justify-between p-4 border rounded-lg hover:bg-muted/30 transition-colors">
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center">
|
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center">
|
||||||
<Icon icon={provider.icon} className="w-5 h-5"/>
|
{provider.icon.startsWith("/") || provider.icon.startsWith("http") ? (
|
||||||
|
<Image
|
||||||
|
src={provider.icon}
|
||||||
|
alt={provider.id}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className="w-5 h-5"
|
||||||
|
unoptimized={provider.icon.startsWith("http")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon icon={provider.icon} className="w-5 h-5" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<div className="font-medium flex items-center gap-2">
|
<div className="font-medium flex items-center gap-2">
|
||||||
{PROVIDERS_TEXT[provider.id].title}
|
{provider.title || provider.name}
|
||||||
{isConnected && (
|
{isConnected && (
|
||||||
<Badge variant="secondary"
|
<Badge variant="secondary" className="text-[10px] h-5 px-1.5 text-green-600 bg-green-500/10 border-0">
|
||||||
className="text-[10px] h-5 px-1.5 text-green-600 bg-green-500/10 border-0">
|
|
||||||
Active
|
Active
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">{provider.description}</div>
|
||||||
{isConnected ? "Connected" : "Not Connected"}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -117,17 +125,13 @@ export function ProfileProviders({accounts, providers}: ProfileProviderProps) {
|
|||||||
disabled={!canUnlink || isLoading || provider.isManual}
|
disabled={!canUnlink || isLoading || provider.isManual}
|
||||||
className={!canUnlink ? "opacity-50 cursor-not-allowed" : ""}
|
className={!canUnlink ? "opacity-50 cursor-not-allowed" : ""}
|
||||||
>
|
>
|
||||||
{isLoading ? <Loader2
|
{isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Unlink"}
|
||||||
className="w-4 h-4 animate-spin"/> : "Unlink"}
|
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
{!canUnlink && (
|
{!canUnlink && (
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>
|
<p>You cannot unlink your last authentication provider.</p>
|
||||||
You cannot unlink your last authentication provider or if you
|
|
||||||
don't have a password set.
|
|
||||||
</p>
|
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -135,16 +139,10 @@ export function ProfileProviders({accounts, providers}: ProfileProviderProps) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{provider.id === "credential" ? (
|
{provider.id === "credential" ? (
|
||||||
<SetPasswordProfileProviderModal open={isPasswordDialogOpen}
|
<SetPasswordProfileProviderModal open={isPasswordDialogOpen} onOpenChange={setIsPasswordDialogOpen} />
|
||||||
onOpenChange={setIsPasswordDialogOpen}/>
|
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button variant="default" size="sm" onClick={() => linkAccount(provider)} disabled={isLoading || provider.isManual}>
|
||||||
variant="default"
|
{isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Link"}
|
||||||
size="sm"
|
|
||||||
onClick={() => linkAccount(provider.id)}
|
|
||||||
disabled={isLoading || provider.isManual}
|
|
||||||
>
|
|
||||||
{isLoading ? <Loader2 className="w-4 h-4 animate-spin"/> : "Link"}
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -152,33 +150,31 @@ export function ProfileProviders({accounts, providers}: ProfileProviderProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8 animate-in fade-in-50 duration-300">
|
||||||
|
<div className="mb-6 space-y-1">
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">Authentication</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">Manage how you access your account.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{enterpriseProviders.length > 0 && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">Enterprise Connection</h3>
|
||||||
|
<div className="grid gap-4">{enterpriseProviders.map(renderProvider)}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wider">Standard Connections</h3>
|
||||||
|
<div className="grid gap-4">{otherProviders.map(renderProvider)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Alert variant={"default"}>
|
<Alert variant={"default"}>
|
||||||
<AlertTriangle className="w-5 h-5 shrink-0 mt-0.5"/>
|
<AlertTriangle className="w-5 h-5 shrink-0 mt-0.5" />
|
||||||
<AlertDescription>
|
<AlertDescription>Linked providers allow you to log in to your account using any of these methods.</AlertDescription>
|
||||||
Linked providers allow you to log in to your account using any of these methods. If you use the same
|
|
||||||
email address with another provider, it will be automatically linked when you log in.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const PROVIDERS_TEXT = {
|
|
||||||
credential: {
|
|
||||||
title: "Password",
|
|
||||||
description: "Use your email address and password to sign in.",
|
|
||||||
},
|
|
||||||
google: {
|
|
||||||
title: "Google",
|
|
||||||
description: "Sign in with your Google account.",
|
|
||||||
},
|
|
||||||
github: {
|
|
||||||
title: "GitHub",
|
|
||||||
description: "Sign in with your GitHub account.",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,49 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {useState} from "react";
|
import { useState } from "react";
|
||||||
import {Button} from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {Separator} from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import {Badge} from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {Globe, LogOut, Loader2} from "lucide-react";
|
import { Globe, LogOut, Loader2, Fingerprint, Trash2, Plus } from "lucide-react";
|
||||||
import {Account, Session, User} from "@/db/schema/02_user";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import { toast } from "sonner";
|
||||||
import {toast} from "sonner";
|
import { revokeAllSessionsAction, revokeSessionAction, getPasskeysAction, revokePasskeyAction } from "./actions/security.action";
|
||||||
import {revokeAllSessionsAction, revokeSessionAction} from "./actions/security.action";
|
import { useRouter } from "next/navigation";
|
||||||
import {useRouter} from "next/navigation";
|
import { ResetPasswordProfileProviderModal } from "./modal/reset-password-modal";
|
||||||
import {ResetPasswordProfileProviderModal} from "./modal/reset-password-modal";
|
import { SetPasswordProfileProviderModal } from "./modal/set-password-modal";
|
||||||
import {SetPasswordProfileProviderModal} from "./modal/set-password-modal";
|
import { Setup2FAProfileProviderModal } from "./modal/setup-2fa-modal";
|
||||||
import {Setup2FAProfileProviderModal} from "./modal/setup-2fa-modal";
|
import { Disable2FAProfileProviderModal } from "./modal/disable-2fa-modal";
|
||||||
import {Disable2FAProfileProviderModal} from "./modal/disable-2fa-modal";
|
import { ViewBackupCodesModal } from "./modal/view-backup-codes-modal";
|
||||||
import {ViewBackupCodesModal} from "./modal/view-backup-codes-modal";
|
import { getDeviceDetails } from "@/utils/detection";
|
||||||
import {getDeviceDetails} from "@/utils/detection";
|
import { timeAgo } from "@/utils/date-formatting";
|
||||||
import {timeAgo} from "@/utils/date-formatting";
|
import { authClient } from "@/lib/auth/auth-client";
|
||||||
|
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 { Account, Session, User } from "@/db/schema/02_user";
|
||||||
|
|
||||||
interface ProfileSecurityProps {
|
interface ProfileSecurityProps {
|
||||||
user: User;
|
user: User;
|
||||||
sessions: Session[];
|
sessions: Session[];
|
||||||
credentialAccount: Account;
|
credentialAccount: Account;
|
||||||
currentSession: Session;
|
currentSession: Session;
|
||||||
|
isPasswordEnabled?: boolean;
|
||||||
|
isPasskeyEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProfileSecurity({user, sessions, credentialAccount, currentSession}: ProfileSecurityProps) {
|
export function ProfileSecurity({ user, sessions, credentialAccount, currentSession, isPasswordEnabled = false, isPasskeyEnabled = false }: ProfileSecurityProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [isBackupCodesDialogOpen, setIsBackupCodesDialogOpen] = useState(false);
|
const [isBackupCodesDialogOpen, setIsBackupCodesDialogOpen] = useState(false);
|
||||||
const [isPasswordDialogOpen, setIsPasswordDialogOpen] = useState(false);
|
const [isPasswordDialogOpen, setIsPasswordDialogOpen] = useState(false);
|
||||||
const [isSetup2FADialogOpen, setIsSetup2FADialogOpen] = useState(false);
|
const [isSetup2FADialogOpen, setIsSetup2FADialogOpen] = useState(false);
|
||||||
const [isDisable2FADialogOpen, setIsDisable2FADialogOpen] = useState(false);
|
const [isDisable2FADialogOpen, setIsDisable2FADialogOpen] = useState(false);
|
||||||
|
const [isAddPasskeyOpen, setIsAddPasskeyOpen] = useState(false);
|
||||||
|
const [passkeyName, setPasskeyName] = useState("");
|
||||||
|
|
||||||
const {mutate: revokeSession, isPending: isRevoking} = useMutation({
|
const { mutate: revokeSession, isPending: isRevoking } = useMutation({
|
||||||
mutationFn: async (token: string) => {
|
mutationFn: async (token: string) => {
|
||||||
const result = await revokeSessionAction({token});
|
const result = await revokeSessionAction({ token });
|
||||||
const inner = result?.data;
|
const inner = result?.data;
|
||||||
if (inner?.success) {
|
if (inner?.success) {
|
||||||
toast.success("Session successfully revoked");
|
toast.success("Session successfully revoked");
|
||||||
@@ -46,7 +54,7 @@ export function ProfileSecurity({user, sessions, credentialAccount, currentSessi
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const {mutate: revokeOthers, isPending: isRevokingOthers} = useMutation({
|
const { mutate: revokeOthers, isPending: isRevokingOthers } = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const result = await revokeAllSessionsAction();
|
const result = await revokeAllSessionsAction();
|
||||||
const inner = result?.data;
|
const inner = result?.data;
|
||||||
@@ -59,71 +67,173 @@ export function ProfileSecurity({user, sessions, credentialAccount, currentSessi
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: passkeys,
|
||||||
|
isLoading: isLoadingPasskeys,
|
||||||
|
refetch: refetchPasskeys,
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ["passkeys"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const result = await getPasskeysAction();
|
||||||
|
if (result?.data?.success) {
|
||||||
|
return result.data.value;
|
||||||
|
}
|
||||||
|
throw new Error("Failed to fetch passkeys");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: revokePasskey, isPending: isRevokingPasskey } = useMutation({
|
||||||
|
mutationFn: async (id: string) => {
|
||||||
|
const result = await revokePasskeyAction({ id });
|
||||||
|
if (!result?.data?.success) {
|
||||||
|
throw new Error("Failed to revoke passkey");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Passkey revoked successfully");
|
||||||
|
refetchPasskeys();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error("Failed to revoke passkey");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: addPasskey, isPending: isAddingPasskey } = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const result = await authClient.passkey.addPasskey({
|
||||||
|
name: passkeyName || "My Passkey",
|
||||||
|
});
|
||||||
|
if (result?.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Passkey added successfully");
|
||||||
|
setIsAddPasskeyOpen(false);
|
||||||
|
setPasskeyName("");
|
||||||
|
refetchPasskeys();
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.message || "Failed to add passkey");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8 animate-in fade-in-50 duration-300">
|
<div className="space-y-8 animate-in fade-in-50 duration-300">
|
||||||
<div className="mb-6 space-y-1">
|
<div className="mb-6 space-y-1">
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">Security Settings</h2>
|
<h2 className="text-2xl font-semibold tracking-tight">Security Settings</h2>
|
||||||
<p className="text-sm text-muted-foreground">Manage your password, two-factor authentication and
|
<p className="text-sm text-muted-foreground">Manage your password, two-factor authentication and sessions.</p>
|
||||||
sessions.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h3 className="text-lg font-medium">Authentication</h3>
|
<h3 className="text-lg font-medium">Authentication</h3>
|
||||||
<div className="border rounded-lg p-4 space-y-4">
|
<div className="border rounded-lg p-4 space-y-4">
|
||||||
|
{isPasswordEnabled && (
|
||||||
|
<>
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="font-medium">Password</div>
|
<div className="font-medium">Password</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
{user.lastChangedPasswordAt
|
{user.lastChangedPasswordAt ? `Last changed ${timeAgo(new Date(user.lastChangedPasswordAt))}` : "Never changed"}
|
||||||
? `Last changed ${timeAgo(new Date(user.lastChangedPasswordAt))}`
|
|
||||||
: "Never changed"}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{credentialAccount ? (
|
{credentialAccount ? (
|
||||||
<ResetPasswordProfileProviderModal open={isPasswordDialogOpen}
|
<ResetPasswordProfileProviderModal open={isPasswordDialogOpen} onOpenChange={setIsPasswordDialogOpen} />
|
||||||
onOpenChange={setIsPasswordDialogOpen}/>
|
|
||||||
) : (
|
) : (
|
||||||
<SetPasswordProfileProviderModal open={isPasswordDialogOpen}
|
<SetPasswordProfileProviderModal open={isPasswordDialogOpen} onOpenChange={setIsPasswordDialogOpen} />
|
||||||
onOpenChange={setIsPasswordDialogOpen}/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator/>
|
<Separator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="font-medium">Two-Factor Authentication</div>
|
<div className="font-medium">Two-Factor Authentication</div>
|
||||||
{user.twoFactorEnabled && (
|
{user.twoFactorEnabled && (
|
||||||
<Badge variant="secondary"
|
<Badge variant="secondary" className="text-[10px] h-5 px-1.5 text-green-600 bg-green-500/10 border-0">
|
||||||
className="text-[10px] h-5 px-1.5 text-green-600 bg-green-500/10 border-0">
|
|
||||||
Active
|
Active
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">Enhance the security of your account by
|
<div className="text-sm text-muted-foreground">
|
||||||
requiring a second form of verification during login.
|
Enhance the security of your account by requiring a second form of verification during login.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{user.twoFactorEnabled ? (
|
{user.twoFactorEnabled ? (
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<ViewBackupCodesModal open={isBackupCodesDialogOpen}
|
<ViewBackupCodesModal open={isBackupCodesDialogOpen} onOpenChange={setIsBackupCodesDialogOpen} />
|
||||||
onOpenChange={setIsBackupCodesDialogOpen}/>
|
<Disable2FAProfileProviderModal open={isDisable2FADialogOpen} onOpenChange={setIsDisable2FADialogOpen} />
|
||||||
<Disable2FAProfileProviderModal open={isDisable2FADialogOpen}
|
|
||||||
onOpenChange={setIsDisable2FADialogOpen}/>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Setup2FAProfileProviderModal
|
<Setup2FAProfileProviderModal disabled={!credentialAccount} open={isSetup2FADialogOpen} onOpenChange={setIsSetup2FADialogOpen} />
|
||||||
disabled={!credentialAccount}
|
|
||||||
open={isSetup2FADialogOpen}
|
|
||||||
onOpenChange={setIsSetup2FADialogOpen}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isPasskeyEnabled && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h3 className="text-lg font-medium">Passkeys</h3>
|
||||||
|
<div className="text-sm text-muted-foreground">Login securely with your fingerprint, face recognition, or hardware key.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog open={isAddPasskeyOpen} onOpenChange={setIsAddPasskeyOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
Add Passkey
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Add New Passkey</DialogTitle>
|
||||||
|
<DialogDescription>Create a name for your passkey to identify it later.</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-4 py-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="name">Passkey Name</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
placeholder="e.g. MacBook Pro, iPhone, YubiKey"
|
||||||
|
value={passkeyName}
|
||||||
|
onChange={(e) => setPasskeyName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => setIsAddPasskeyOpen(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => addPasskey()} disabled={isAddingPasskey}>
|
||||||
|
{isAddingPasskey && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
Create Passkey
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border rounded-lg divide-y">
|
||||||
|
{isLoadingPasskeys ? (
|
||||||
|
<div className="flex items-center justify-center p-4">
|
||||||
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
) : passkeys && passkeys.length > 0 ? (
|
||||||
|
passkeys.map((pk: any) => <PasskeyRow key={pk.id} passkey={pk} onRevoke={(id) => revokePasskey(id)} isRevoking={isRevokingPasskey} />)
|
||||||
|
) : (
|
||||||
|
<div className="p-4 text-center text-muted-foreground">No passkeys found.</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-medium">Active Sessions</h3>
|
<h3 className="text-lg font-medium">Active Sessions</h3>
|
||||||
@@ -135,7 +245,7 @@ export function ProfileSecurity({user, sessions, credentialAccount, currentSessi
|
|||||||
onClick={() => revokeOthers()}
|
onClick={() => revokeOthers()}
|
||||||
disabled={isRevokingOthers || (sessions?.length || 0) <= 1}
|
disabled={isRevokingOthers || (sessions?.length || 0) <= 1}
|
||||||
>
|
>
|
||||||
{isRevokingOthers && <Loader2 className="mr-2 h-4 w-4 animate-spin"/>}
|
{isRevokingOthers && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
Revoke All
|
Revoke All
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -165,7 +275,7 @@ function SessionRow({
|
|||||||
onRevoke,
|
onRevoke,
|
||||||
isRevoking,
|
isRevoking,
|
||||||
currentSession,
|
currentSession,
|
||||||
}: {
|
}: {
|
||||||
session: Session;
|
session: Session;
|
||||||
onRevoke: (token: string) => void;
|
onRevoke: (token: string) => void;
|
||||||
isRevoking: boolean;
|
isRevoking: boolean;
|
||||||
@@ -177,12 +287,11 @@ function SessionRow({
|
|||||||
<div className="flex items-center justify-between p-4">
|
<div className="flex items-center justify-between p-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center text-muted-foreground">
|
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center text-muted-foreground">
|
||||||
<deviceInfo.Icon className="w-5 h-5"/>
|
<deviceInfo.Icon className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<div className="text-sm font-medium flex items-center gap-2">
|
<div className="text-sm font-medium flex items-center gap-2">
|
||||||
{deviceInfo.os} <span
|
{deviceInfo.os} <span className="text-muted-foreground font-normal">• {deviceInfo.browser}</span>
|
||||||
className="text-muted-foreground font-normal">• {deviceInfo.browser}</span>
|
|
||||||
{session.id === currentSession.id && (
|
{session.id === currentSession.id && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -193,12 +302,8 @@ function SessionRow({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-muted-foreground flex items-center gap-1">
|
<div className="text-xs text-muted-foreground flex items-center gap-1">
|
||||||
<Globe className="w-3 h-3"/> {session.ipAddress} •
|
<Globe className="w-3 h-3" /> {session.ipAddress} •
|
||||||
<span className="ml-1">
|
<span className="ml-1">{session.id === currentSession.id ? "Active now" : `Last active ${timeAgo(new Date(session.createdAt))}`}</span>
|
||||||
{session.id === currentSession.id
|
|
||||||
? "Active now"
|
|
||||||
: `Last active ${timeAgo(new Date(session.createdAt))}`}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -211,10 +316,36 @@ function SessionRow({
|
|||||||
onClick={() => onRevoke(session.token)}
|
onClick={() => onRevoke(session.token)}
|
||||||
disabled={isRevoking}
|
disabled={isRevoking}
|
||||||
>
|
>
|
||||||
{isRevoking ? <Loader2 className="h-4 w-4 animate-spin"/> : <LogOut className="w-4 h-4"/>}
|
{isRevoking ? <Loader2 className="h-4 w-4 animate-spin" /> : <LogOut className="w-4 h-4" />}
|
||||||
<span className="sr-only">Revoke</span>
|
<span className="sr-only">Revoke</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PasskeyRow({ passkey, onRevoke, isRevoking }: { passkey: any; onRevoke: (id: string) => void; isRevoking: boolean }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between p-4">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center text-muted-foreground">
|
||||||
|
<Fingerprint className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<div className="font-medium text-sm">{passkey.name || "Unnamed Passkey"}</div>
|
||||||
|
<div className="text-xs text-muted-foreground">Created {timeAgo(new Date(passkey.createdAt))}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 text-muted-foreground hover:text-destructive"
|
||||||
|
onClick={() => onRevoke(passkey.id)}
|
||||||
|
disabled={isRevoking}
|
||||||
|
>
|
||||||
|
{isRevoking ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="w-4 h-4" />}
|
||||||
|
<span className="sr-only">Revoke</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE "sso_provider" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"issuer" text NOT NULL,
|
||||||
|
"oidc_config" json,
|
||||||
|
"saml_config" json,
|
||||||
|
"user_id" uuid,
|
||||||
|
"provider_id" text NOT NULL,
|
||||||
|
"organization_id" text,
|
||||||
|
"domain" text NOT NULL,
|
||||||
|
CONSTRAINT "sso_provider_provider_id_unique" UNIQUE("provider_id")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "sso_provider" ADD CONSTRAINT "sso_provider_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
ALTER TABLE "passkey" DROP CONSTRAINT "passkey_userId_user_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" ADD COLUMN "public_key" text NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" ADD COLUMN "user_id" uuid NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" ADD COLUMN "credential_i_d" text NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" ADD COLUMN "device_type" text NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" ADD COLUMN "backed_up" boolean NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" ADD COLUMN "aaguid" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" ADD CONSTRAINT "passkey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" DROP COLUMN "publicKey";--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" DROP COLUMN "userId";--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" DROP COLUMN "credentialId";--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" DROP COLUMN "deviceType";--> statement-breakpoint
|
||||||
|
ALTER TABLE "passkey" DROP COLUMN "backedUp";
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -239,6 +239,20 @@
|
|||||||
"when": 1770923665015,
|
"when": 1770923665015,
|
||||||
"tag": "0033_handy_valeria_richards",
|
"tag": "0033_handy_valeria_richards",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 34,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1770991368921,
|
||||||
|
"tag": "0034_vengeful_blacklash",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 35,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1770993283219,
|
||||||
|
"tag": "0035_windy_shockwave",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+41
-13
@@ -1,5 +1,5 @@
|
|||||||
import {relations} from "drizzle-orm";
|
import {relations} from "drizzle-orm";
|
||||||
import {boolean, integer, pgEnum, pgTable, text, timestamp, uuid} from "drizzle-orm/pg-core";
|
import {boolean, integer, pgEnum, json, pgTable, text, timestamp, uuid} from "drizzle-orm/pg-core";
|
||||||
import {createSelectSchema} from "drizzle-zod";
|
import {createSelectSchema} from "drizzle-zod";
|
||||||
import {z} from "zod";
|
import {z} from "zod";
|
||||||
import {project} from "./06_project";
|
import {project} from "./06_project";
|
||||||
@@ -72,19 +72,19 @@ export const verification = pgTable("verification", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const passkey = pgTable("passkey", {
|
export const passkey = pgTable("passkey", {
|
||||||
id: uuid().defaultRandom().primaryKey(),
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
name: text(),
|
name: text("name"),
|
||||||
publicKey: text().notNull(),
|
publicKey: text("public_key").notNull(),
|
||||||
userId: uuid()
|
userId: uuid("user_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => user.id, {onDelete: "cascade"}),
|
.references(() => user.id, { onDelete: "cascade" }),
|
||||||
credentialId: text().notNull(),
|
credentialID: text("credential_i_d").notNull(),
|
||||||
counter: integer().notNull(),
|
counter: integer("counter").notNull(),
|
||||||
deviceType: text().notNull(),
|
deviceType: text("device_type").notNull(),
|
||||||
backedUp: boolean().notNull(),
|
backedUp: boolean("backed_up").notNull(),
|
||||||
transports: text(),
|
transports: text("transports"),
|
||||||
|
aaguid: text("aaguid"),
|
||||||
...timestamps,
|
...timestamps
|
||||||
});
|
});
|
||||||
|
|
||||||
export const twoFactor = pgTable("two_factor", {
|
export const twoFactor = pgTable("two_factor", {
|
||||||
@@ -96,11 +96,24 @@ export const twoFactor = pgTable("two_factor", {
|
|||||||
.references(() => user.id, {onDelete: "cascade"}),
|
.references(() => user.id, {onDelete: "cascade"}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ssoProvider = pgTable("sso_provider", {
|
||||||
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
issuer: text("issuer").notNull(),
|
||||||
|
oidcConfig: json("oidc_config"),
|
||||||
|
samlConfig: json("saml_config"),
|
||||||
|
userId: uuid("user_id").references(() => user.id, { onDelete: "cascade" }),
|
||||||
|
providerId: text("provider_id").notNull().unique(),
|
||||||
|
organizationId: text("organization_id"),
|
||||||
|
domain: text("domain").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
export const userRelations = relations(user, ({many}) => ({
|
export const userRelations = relations(user, ({many}) => ({
|
||||||
sessions: many(session),
|
sessions: many(session),
|
||||||
accounts: many(account),
|
accounts: many(account),
|
||||||
|
ssoProviders: many(ssoProvider),
|
||||||
memberships: many(member),
|
memberships: many(member),
|
||||||
invitations: many(invitation),
|
invitations: many(invitation),
|
||||||
|
passkeys: many(passkey),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const sessionRelations = relations(session, ({one}) => ({
|
export const sessionRelations = relations(session, ({one}) => ({
|
||||||
@@ -117,6 +130,13 @@ export const accountRelations = relations(account, ({one}) => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const ssoProviderRelations = relations(ssoProvider, ({ one }) => ({
|
||||||
|
user: one(user, {
|
||||||
|
fields: [ssoProvider.userId],
|
||||||
|
references: [user.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
export const projectRelations = relations(project, ({one}) => ({
|
export const projectRelations = relations(project, ({one}) => ({
|
||||||
organization: one(organization, {
|
organization: one(organization, {
|
||||||
fields: [project.organizationId],
|
fields: [project.organizationId],
|
||||||
@@ -124,6 +144,14 @@ export const projectRelations = relations(project, ({one}) => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const passkeyRelations = relations(passkey, ({ one }) => ({
|
||||||
|
user: one(user, {
|
||||||
|
fields: [passkey.userId],
|
||||||
|
references: [user.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
export const userSchema = createSelectSchema(user);
|
export const userSchema = createSelectSchema(user);
|
||||||
export type User = z.infer<typeof userSchema>;
|
export type User = z.infer<typeof userSchema>;
|
||||||
|
|
||||||
|
|||||||
+45
-7
@@ -1,8 +1,8 @@
|
|||||||
import {createEnv} from "@t3-oss/env-nextjs";
|
import { createEnv } from "@t3-oss/env-nextjs";
|
||||||
import {z} from "zod";
|
import { z } from "zod";
|
||||||
import packageJson from "../package.json" with {type: "json"};
|
import packageJson from "../package.json" with { type: "json" };
|
||||||
|
|
||||||
const {version} = packageJson;
|
const { version } = packageJson;
|
||||||
|
|
||||||
export const env = createEnv({
|
export const env = createEnv({
|
||||||
server: {
|
server: {
|
||||||
@@ -28,6 +28,9 @@ export const env = createEnv({
|
|||||||
AUTH_GOOGLE_SECRET: z.string().optional(),
|
AUTH_GOOGLE_SECRET: z.string().optional(),
|
||||||
AUTH_GOOGLE_METHOD: z.boolean().default(false),
|
AUTH_GOOGLE_METHOD: z.boolean().default(false),
|
||||||
|
|
||||||
|
AUTH_GITHUB_ID: z.string().optional(),
|
||||||
|
AUTH_GITHUB_SECRET: z.string().optional(),
|
||||||
|
|
||||||
S3_ENDPOINT: z.string().optional(),
|
S3_ENDPOINT: z.string().optional(),
|
||||||
S3_ACCESS_KEY: z.string().optional(),
|
S3_ACCESS_KEY: z.string().optional(),
|
||||||
S3_SECRET_KEY: z.string().optional(),
|
S3_SECRET_KEY: z.string().optional(),
|
||||||
@@ -37,10 +40,25 @@ export const env = createEnv({
|
|||||||
|
|
||||||
STORAGE_TYPE: z.enum(["local", "s3"]).optional(),
|
STORAGE_TYPE: z.enum(["local", "s3"]).optional(),
|
||||||
|
|
||||||
RETENTION_CRON: z
|
RETENTION_CRON: z.string().default(process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *"),
|
||||||
.string()
|
|
||||||
.default(process.env.NODE_ENV === "production" ? "0 7 * * *" : "* * * * *"),
|
|
||||||
|
|
||||||
|
AUTH_OIDC_ID: z.string().optional().default("oidc"),
|
||||||
|
AUTH_OIDC_TITLE: z.string().optional(),
|
||||||
|
AUTH_OIDC_DESC: z.string().optional(),
|
||||||
|
AUTH_OIDC_ICON: z.string().optional(),
|
||||||
|
AUTH_OIDC_CLIENT: z.string().optional(),
|
||||||
|
AUTH_OIDC_SECRET: z.string().optional(),
|
||||||
|
AUTH_OIDC_ISSUER_URL: z.string().optional(),
|
||||||
|
AUTH_OIDC_HOST: z.string().optional(),
|
||||||
|
AUTH_OIDC_SCOPES: z.string().optional(),
|
||||||
|
AUTH_OIDC_DISCOVERY_ENDPOINT: z.string().optional(),
|
||||||
|
AUTH_OIDC_JWKS_ENDPOINT: z.string().optional(),
|
||||||
|
AUTH_OIDC_PKCE: z.string().optional(),
|
||||||
|
ALLOWED_GROUP: z.string().optional(),
|
||||||
|
|
||||||
|
AUTH_EMAIL_PASSWORD_ENABLED: z.string().optional().default("true"),
|
||||||
|
AUTH_SIGNUP_ENABLED: z.string().optional().default("true"),
|
||||||
|
AUTH_PASSKEY_ENABLED: z.string().optional().default("true"),
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
NEXT_PUBLIC_PROJECT_VERSION: z.string().optional(),
|
||||||
@@ -66,6 +84,9 @@ export const env = createEnv({
|
|||||||
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
||||||
AUTH_GOOGLE_METHOD: process.env.AUTH_GOOGLE_METHOD === "true",
|
AUTH_GOOGLE_METHOD: process.env.AUTH_GOOGLE_METHOD === "true",
|
||||||
|
|
||||||
|
AUTH_GITHUB_ID: process.env.AUTH_GITHUB_ID,
|
||||||
|
AUTH_GITHUB_SECRET: process.env.AUTH_GITHUB_SECRET,
|
||||||
|
|
||||||
S3_ENDPOINT: process.env.S3_ENDPOINT,
|
S3_ENDPOINT: process.env.S3_ENDPOINT,
|
||||||
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
|
S3_ACCESS_KEY: process.env.S3_ACCESS_KEY,
|
||||||
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
|
S3_SECRET_KEY: process.env.S3_SECRET_KEY,
|
||||||
@@ -77,5 +98,22 @@ export const env = createEnv({
|
|||||||
|
|
||||||
RETENTION_CRON: process.env.RETENTION_CRON,
|
RETENTION_CRON: process.env.RETENTION_CRON,
|
||||||
|
|
||||||
|
AUTH_OIDC_ID: process.env.AUTH_OIDC_ID,
|
||||||
|
AUTH_OIDC_TITLE: process.env.AUTH_OIDC_TITLE,
|
||||||
|
AUTH_OIDC_DESC: process.env.AUTH_OIDC_DESC,
|
||||||
|
AUTH_OIDC_ICON: process.env.AUTH_OIDC_ICON,
|
||||||
|
AUTH_OIDC_CLIENT: process.env.AUTH_OIDC_CLIENT,
|
||||||
|
AUTH_OIDC_SECRET: process.env.AUTH_OIDC_SECRET,
|
||||||
|
AUTH_OIDC_ISSUER_URL: process.env.AUTH_OIDC_ISSUER_URL,
|
||||||
|
AUTH_OIDC_HOST: process.env.AUTH_OIDC_HOST,
|
||||||
|
AUTH_OIDC_SCOPES: process.env.AUTH_OIDC_SCOPES,
|
||||||
|
AUTH_OIDC_DISCOVERY_ENDPOINT: process.env.AUTH_OIDC_DISCOVERY_ENDPOINT,
|
||||||
|
AUTH_OIDC_JWKS_ENDPOINT: process.env.AUTH_OIDC_JWKS_ENDPOINT,
|
||||||
|
AUTH_OIDC_PKCE: process.env.AUTH_OIDC_PKCE,
|
||||||
|
ALLOWED_GROUP: process.env.ALLOWED_GROUP,
|
||||||
|
|
||||||
|
AUTH_EMAIL_PASSWORD_ENABLED: process.env.AUTH_EMAIL_PASSWORD_ENABLED,
|
||||||
|
AUTH_SIGNUP_ENABLED: process.env.AUTH_SIGNUP_ENABLED,
|
||||||
|
AUTH_PASSKEY_ENABLED: process.env.AUTH_PASSKEY_ENABLED,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import {createAuthClient} from "better-auth/react";
|
|||||||
|
|
||||||
import {adminClient, inferAdditionalFields, organizationClient, twoFactorClient} from "better-auth/client/plugins";
|
import {adminClient, inferAdditionalFields, organizationClient, twoFactorClient} from "better-auth/client/plugins";
|
||||||
import {ac, user, admin as adminRole, pending, superadmin, orgAdmin, orgMember, orgOwner} from "./permissions";
|
import {ac, user, admin as adminRole, pending, superadmin, orgAdmin, orgMember, orgOwner} from "./permissions";
|
||||||
import {auth} from "@/lib/auth/auth";
|
import type {auth} from "@/lib/auth/auth";
|
||||||
import {getServerUrl} from "@/utils/get-server-url";
|
import {getServerUrl} from "@/utils/get-server-url";
|
||||||
|
import { ssoClient } from "@better-auth/sso/client";
|
||||||
|
import { passkeyClient } from "@better-auth/passkey/client"
|
||||||
|
|
||||||
const res = await fetch(`${getServerUrl()}/api/config`);
|
const res = await fetch(`${getServerUrl()}/api/config`);
|
||||||
const {PROJECT_URL} = await res.json();
|
const {PROJECT_URL} = await res.json();
|
||||||
@@ -12,7 +14,9 @@ const {PROJECT_URL} = await res.json();
|
|||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
baseURL: PROJECT_URL,
|
baseURL: PROJECT_URL,
|
||||||
plugins: [
|
plugins: [
|
||||||
|
passkeyClient(),
|
||||||
twoFactorClient(),
|
twoFactorClient(),
|
||||||
|
ssoClient(),
|
||||||
organizationClient({
|
organizationClient({
|
||||||
ac,
|
ac,
|
||||||
roles: {
|
roles: {
|
||||||
@@ -35,4 +39,4 @@ export const authClient = createAuthClient({
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {signIn, signOut, signUp, useSession, listAccounts, admin, requestPasswordReset} = authClient;
|
export const { signIn, signOut, signUp, deleteUser, useSession, listAccounts, passkey, admin, twoFactor, requestPasswordReset, sso } = authClient;
|
||||||
+103
-7
@@ -11,12 +11,14 @@ import {count, eq} from "drizzle-orm";
|
|||||||
import {MemberWithUser, OrganizationWithMembersAndUsers} from "@/db/schema/03_organization";
|
import {MemberWithUser, OrganizationWithMembersAndUsers} from "@/db/schema/03_organization";
|
||||||
import {sendEmail} from "@/lib/email";
|
import {sendEmail} from "@/lib/email";
|
||||||
import {render} from "@react-email/render";
|
import {render} from "@react-email/render";
|
||||||
import {AuthProviderConfig, SUPPORTED_PROVIDERS} from "../../../portabase.config";
|
|
||||||
import {withUpdatedAt} from "@/db/utils";
|
import {withUpdatedAt} from "@/db/utils";
|
||||||
import EmailVerification from "@/components/emails/auth/email-verification";
|
import EmailVerification from "@/components/emails/auth/email-verification";
|
||||||
import EmailForgotPassword from "@/components/emails/auth/email-forgot-password";
|
import EmailForgotPassword from "@/components/emails/auth/email-forgot-password";
|
||||||
import {getDeviceDetails} from "@/utils/detection";
|
import {getDeviceDetails} from "@/utils/detection";
|
||||||
import EmailNewLogin from "@/components/emails/auth/email-new-login";
|
import EmailNewLogin from "@/components/emails/auth/email-new-login";
|
||||||
|
import { sso } from "@better-auth/sso";
|
||||||
|
import { AuthProviderConfig, SUPPORTED_PROVIDERS } from "@/lib/auth/config";
|
||||||
|
import { passkey } from "@better-auth/passkey";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
@@ -27,11 +29,11 @@ export const auth = betterAuth({
|
|||||||
baseURL: env.PROJECT_URL,
|
baseURL: env.PROJECT_URL,
|
||||||
secret: env.PROJECT_SECRET,
|
secret: env.PROJECT_SECRET,
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: env.AUTH_EMAIL_PASSWORD_ENABLED === "true",
|
||||||
requireEmailVerification: false,
|
requireEmailVerification: false,
|
||||||
sendResetPassword: async ({user, token}, request) => {
|
sendResetPassword: async ({user, token}, request) => {
|
||||||
|
|
||||||
const [updatedUser] = await db.update(drizzleDb.schemas.user).set(withUpdatedAt({
|
await db.update(drizzleDb.schemas.user).set(withUpdatedAt({
|
||||||
emailVerified: true,
|
emailVerified: true,
|
||||||
})).where(eq(drizzleDb.schemas.user.id, user.id)).returning();
|
})).where(eq(drizzleDb.schemas.user.id, user.id)).returning();
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ export const auth = betterAuth({
|
|||||||
socialProviders: SUPPORTED_PROVIDERS.reduce((acc: any, provider: AuthProviderConfig) => {
|
socialProviders: SUPPORTED_PROVIDERS.reduce((acc: any, provider: AuthProviderConfig) => {
|
||||||
if (!provider.isActive) return acc;
|
if (!provider.isActive) return acc;
|
||||||
if (provider.id === "credential") return acc;
|
if (provider.id === "credential") return acc;
|
||||||
|
if (provider.id === env.AUTH_OIDC_ID!) return acc;
|
||||||
if (provider.id === "google") {
|
if (provider.id === "google") {
|
||||||
acc.google = {
|
acc.google = {
|
||||||
clientId: env.AUTH_GOOGLE_ID! as string,
|
clientId: env.AUTH_GOOGLE_ID! as string,
|
||||||
@@ -86,8 +89,8 @@ export const auth = betterAuth({
|
|||||||
}
|
}
|
||||||
if (provider.id === "github") {
|
if (provider.id === "github") {
|
||||||
acc.github = {
|
acc.github = {
|
||||||
clientId: provider.credentials?.clientId,
|
// clientId: provider.credentials?.clientId,
|
||||||
clientSecret: provider.credentials?.clientSecret,
|
// clientSecret: provider.credentials?.clientSecret,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
@@ -95,12 +98,69 @@ export const auth = betterAuth({
|
|||||||
account: {
|
account: {
|
||||||
accountLinking: {
|
accountLinking: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
trustedProviders: ["google", "github", "credential"],
|
trustedProviders: ["google", "github", "credential",env.AUTH_OIDC_ID!],
|
||||||
allowDifferentEmails: false
|
allowDifferentEmails: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
sso({
|
||||||
|
defaultSSO: [{
|
||||||
|
oidcConfig: {
|
||||||
|
issuer: env.AUTH_OIDC_ISSUER_URL!,
|
||||||
|
discoveryEndpoint: env.AUTH_OIDC_DISCOVERY_ENDPOINT!,
|
||||||
|
jwksEndpoint: env.AUTH_OIDC_JWKS_ENDPOINT!,
|
||||||
|
clientId: env.AUTH_OIDC_CLIENT!,
|
||||||
|
clientSecret: env.AUTH_OIDC_SECRET!,
|
||||||
|
scopes: env.AUTH_OIDC_SCOPES?.split(" ") ?? ["openid", "profile", "email"],
|
||||||
|
pkce: env.AUTH_OIDC_PKCE === "true",
|
||||||
|
mapping: {
|
||||||
|
extraFields: {
|
||||||
|
groups: "groups"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
providerId: env.AUTH_OIDC_ID!,
|
||||||
|
domain: env.AUTH_OIDC_HOST!,
|
||||||
|
//@ts-ignore
|
||||||
|
issuer: env.AUTH_OIDC_ISSUER_URL!
|
||||||
|
}],
|
||||||
|
provisionUser: async ({ user: usr, userInfo }) => {
|
||||||
|
const allowedGroup = env.ALLOWED_GROUP;
|
||||||
|
|
||||||
|
if (!allowedGroup) return;
|
||||||
|
|
||||||
|
const rawGroups = (userInfo as any).groups || (userInfo as any).roles || [];
|
||||||
|
|
||||||
|
const userGroups: string[] = Array.isArray(rawGroups) ? rawGroups : [rawGroups];
|
||||||
|
|
||||||
|
const hasAccess = userGroups.includes(allowedGroup);
|
||||||
|
|
||||||
|
if (!hasAccess) {
|
||||||
|
throw new Error("Access Denied");
|
||||||
|
}
|
||||||
|
|
||||||
|
const userCount = (await db.select({ count: count() }).from(drizzleDb.schemas.user))[0].count;
|
||||||
|
const isSuperadmin = userCount === 0 ? "superadmin" : undefined;
|
||||||
|
|
||||||
|
const roleToAssign = allowedGroup.includes('admin') || allowedGroup.includes('superadmin') ?
|
||||||
|
isSuperadmin ? 'superadmin' : "admin" : 'pending';
|
||||||
|
|
||||||
|
const existingUser = await db.query.user.findFirst({
|
||||||
|
where: eq(drizzleDb.schemas.user.email, usr.email)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
await db.update(drizzleDb.schemas.user)
|
||||||
|
.set({ role: roleToAssign, emailVerified: true })
|
||||||
|
.where(eq(drizzleDb.schemas.user.id, existingUser.id));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...(env.AUTH_PASSKEY_ENABLED === "true" ? [passkey({
|
||||||
|
rpName: env.PROJECT_NAME || "Portabase",
|
||||||
|
rpID: env.PROJECT_URL ? new URL(env.PROJECT_URL).hostname : "localhost"
|
||||||
|
})] : []),
|
||||||
openAPI(),
|
openAPI(),
|
||||||
nextCookies(),
|
nextCookies(),
|
||||||
twoFactor(),
|
twoFactor(),
|
||||||
@@ -155,11 +215,28 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
databaseHooks: {
|
databaseHooks: {
|
||||||
user: {
|
user: {
|
||||||
|
update: {
|
||||||
|
async before(user, context) {
|
||||||
|
if (env.AUTH_EMAIL_PASSWORD_ENABLED !== "true") {
|
||||||
|
if (user.password || user.lastChangedPasswordAt) {
|
||||||
|
throw new Error("Password updates are disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data: user,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
async before(user, context) {
|
async before(user, context) {
|
||||||
const userCount = (await db.select({count: count()}).from(drizzleDb.schemas.user))[0].count;
|
const userCount = (await db.select({count: count()}).from(drizzleDb.schemas.user))[0].count;
|
||||||
|
|
||||||
|
if (env.AUTH_SIGNUP_ENABLED !== "true" && userCount > 0) {
|
||||||
|
throw new Error("Sign up is disabled");
|
||||||
|
}
|
||||||
|
|
||||||
const role = userCount === 0 ? "superadmin" : "pending";
|
const role = userCount === 0 ? "superadmin" : "pending";
|
||||||
// const role = "admin";
|
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
...user,
|
...user,
|
||||||
@@ -417,6 +494,25 @@ export const getOrganization = async ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getPasskeys = async () => {
|
||||||
|
if (env.AUTH_PASSKEY_ENABLED !== "true") return [];
|
||||||
|
const passkeys = await auth.api.listPasskeys({
|
||||||
|
headers: await headers(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return passkeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const revokePasskey = async (e: string) => {
|
||||||
|
if (env.AUTH_PASSKEY_ENABLED !== "true") return;
|
||||||
|
await auth.api.deletePasskey({
|
||||||
|
body: {
|
||||||
|
id: e,
|
||||||
|
},
|
||||||
|
headers: await headers(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const listOrganizations = async (): Promise<Organization[] | null> => {
|
export const listOrganizations = async (): Promise<Organization[] | null> => {
|
||||||
try {
|
try {
|
||||||
return await auth.api.listOrganizations({
|
return await auth.api.listOrganizations({
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { env } from "@/env.mjs";
|
||||||
|
|
||||||
|
export interface AuthProviderConfig {
|
||||||
|
id: string;
|
||||||
|
isActive: boolean;
|
||||||
|
name?: string;
|
||||||
|
icon: string;
|
||||||
|
isManual?: boolean;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
type: "social" | "sso" | "credential" | "passkey";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SUPPORTED_PROVIDERS: AuthProviderConfig[] = [
|
||||||
|
{
|
||||||
|
id: "credential",
|
||||||
|
isActive: env.AUTH_EMAIL_PASSWORD_ENABLED === "true",
|
||||||
|
name: "Password",
|
||||||
|
icon: "lucide:lock",
|
||||||
|
title: "Password",
|
||||||
|
description: "Standard email and password login.",
|
||||||
|
isManual: true,
|
||||||
|
type: "credential"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "google",
|
||||||
|
isActive: !!env.AUTH_GOOGLE_ID,
|
||||||
|
name: "Google",
|
||||||
|
icon: "logos:google-icon",
|
||||||
|
title: "Google",
|
||||||
|
description: "Sign in with your Google account.",
|
||||||
|
type: "social"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "github",
|
||||||
|
isActive: !!env.AUTH_GITHUB_ID,
|
||||||
|
name: "GitHub",
|
||||||
|
icon: "logos:github-icon",
|
||||||
|
title: "GitHub",
|
||||||
|
description: "Sign in with your GitHub account.",
|
||||||
|
type: "social"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: env.AUTH_OIDC_ID || "oidc",
|
||||||
|
isActive: !!env.AUTH_OIDC_CLIENT,
|
||||||
|
name: env.AUTH_OIDC_TITLE || "SSO",
|
||||||
|
icon: env.AUTH_OIDC_ICON || "lucide:building",
|
||||||
|
title: env.AUTH_OIDC_TITLE || "SSO",
|
||||||
|
description: env.AUTH_OIDC_DESC || "Sign in with your SSO account.",
|
||||||
|
isManual: true,
|
||||||
|
type: "sso"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "passkey",
|
||||||
|
isActive: env.AUTH_PASSKEY_ENABLED === "true",
|
||||||
|
name: "Passkey",
|
||||||
|
icon: "lucide:fingerprint",
|
||||||
|
title: "Passkey",
|
||||||
|
description: "Sign in with your passkey.",
|
||||||
|
isManual: false,
|
||||||
|
type: "passkey"
|
||||||
|
}
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user