feat: setup better-auth

This commit is contained in:
Henry
2025-05-02 23:40:34 +01:00
parent 5996ad19c3
commit 091fea6f38
23 changed files with 4696 additions and 30 deletions

View File

@@ -17,6 +17,7 @@
"@headlessui/react": "^2.2.0",
"@hookform/resolvers": "^3.3.4",
"@kan/api": "workspace:*",
"@kan/auth": "workspace:*",
"@kan/db": "workspace:^",
"@kan/shared": "workspace:^",
"@kan/supabase": "workspace:^",

View File

@@ -3,6 +3,8 @@ import { useForm } from "react-hook-form";
import { FaGoogle } from "react-icons/fa";
import { z } from "zod";
import { authClient } from "@kan/auth";
import Button from "~/components/Button";
import Input from "~/components/Input";
import { api } from "~/utils/api";
@@ -55,7 +57,12 @@ export function Auth({ setIsMagicLinkSent }: AuthProps) {
<div className="space-y-6">
<div>
<Button
onClick={() => loginWithOAuth.mutate({ provider: "google" })}
onClick={() =>
authClient.signIn.social({
provider: "google",
callbackURL: "/boards",
})
}
iconLeft={<FaGoogle />}
fullWidth
size="lg"

View File

@@ -16,6 +16,8 @@ export const env = createEnv({
server: {
POSTGRES_URL: z.string().url(),
STRIPE_SECRET_KEY: z.string().optional(),
GOOGLE_CLIENT_ID: z.string(),
GOOGLE_CLIENT_SECRET: z.string(),
},
/**

View File

@@ -0,0 +1,6 @@
import { toNodeHandler } from "better-auth/node";
import { auth } from "@kan/auth";
export const config = { api: { bodyParser: false } };
export default toNodeHandler(auth.handler);

View File

@@ -35,6 +35,7 @@
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
},
"dependencies": {
"@kan/auth": "workspace:*",
"@kan/db": "workspace:*",
"@kan/email": "workspace:^",
"@kan/shared": "workspace:^",

View File

@@ -6,18 +6,22 @@ import superjson from "superjson";
import { ZodError } from "zod";
import type { dbClient } from "@kan/db/client";
import type { Database } from "@kan/db/types/database.types";
import type { SupabaseClient } from "@kan/supabase";
import { auth } from "@kan/auth";
import { createDrizzleClient } from "@kan/db/client";
import { createNextApiClient, createTRPCClient } from "@kan/supabase";
export interface User {
id: string;
name: string;
email: string;
emailVerified: boolean;
createdAt: Date;
updatedAt: Date;
image?: string | null | undefined;
stripeCustomerId?: string | null | undefined;
}
interface CreateContextOptions {
user: User | null;
supabaseClient: SupabaseClient<Database>;
user: User | null | undefined;
db: dbClient;
}
@@ -25,28 +29,22 @@ export const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
user: opts.user,
db: opts.db,
supabaseClient: opts.supabaseClient,
};
};
export const createTRPCContext = async ({
req,
resHeaders,
}: FetchCreateContextFnOptions) => {
const supabaseClient = createTRPCClient(req, resHeaders);
const {
data: { user },
} = await supabaseClient.auth.getUser();
const session = await auth.api.getSession({
headers: req.headers,
});
const db = createDrizzleClient();
return createInnerTRPCContext({ db, user, supabaseClient: supabaseClient });
return createInnerTRPCContext({ db, user: session?.user });
};
export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
const supabaseClient = createNextApiClient(req);
const authHeader = req.headers.authorization;
const accessToken = authHeader?.startsWith("Bearer ")
? authHeader.substring(7)
@@ -55,14 +53,14 @@ export const createRESTContext = async ({ req }: CreateNextContextOptions) => {
const db = createDrizzleClient();
if (!accessToken) {
return createInnerTRPCContext({ db, user: null, supabaseClient });
return createInnerTRPCContext({ db, user: null });
}
const {
data: { user },
} = await supabaseClient.auth.getUser(accessToken);
const session = await auth.api.getSession({
headers: req.headers,
});
return createInnerTRPCContext({ db, user, supabaseClient });
return createInnerTRPCContext({ db, user: session?.user });
};
const t = initTRPC

View File

@@ -0,0 +1,9 @@
import baseConfig from "@kan/eslint-config/base";
/** @type {import('typescript-eslint').Config} */
export default [
{
ignores: [],
},
...baseConfig,
];

View File

@@ -0,0 +1,32 @@
{
"name": "@kan/auth",
"private": true,
"version": "0.1.0",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"license": "MIT",
"scripts": {
"build": "tsc",
"clean": "git clean -xdf .cache .turbo dist node_modules",
"dev": "tsc",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
},
"devDependencies": {
"@kan/db": "workspace:*",
"@kan/eslint-config": "workspace:*",
"@kan/prettier-config": "workspace:*",
"@kan/shared": "workspace:*",
"@kan/tsconfig": "workspace:*",
"eslint": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
},
"prettier": "@kan/prettier-config",
"dependencies": {
"better-auth": "^1.2.7"
}
}

52
packages/auth/src/auth.ts Normal file
View File

@@ -0,0 +1,52 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { apiKey } from "better-auth/plugins";
import { magicLink } from "better-auth/plugins/magic-link";
import { createDrizzleClient } from "@kan/db/client";
import * as schema from "@kan/db/schema";
const db = createDrizzleClient();
console.log("GOOGLE_CLIENT_ID", process.env.GOOGLE_CLIENT_ID);
console.log("GOOGLE_CLIENT_SECRET", process.env.GOOGLE_CLIENT_SECRET);
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema: {
...schema,
user: schema.users,
},
}),
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
user: {
additionalFields: {
stripeCustomerId: {
type: "string",
required: false,
defaultValue: null,
input: false,
},
},
},
plugins: [
apiKey(),
magicLink({
sendMagicLink: async ({ email, token, url }, request) => {
// send email to user
},
}),
],
advanced: {
cookiePrefix: "kan",
database: {
generateId: false,
},
},
});

View File

@@ -0,0 +1,3 @@
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient();

View File

@@ -0,0 +1,6 @@
import { auth } from "./auth";
import { authClient } from "./clients";
export const name = "auth";
export { auth, authClient };

View File

@@ -0,0 +1,6 @@
{
"extends": "@kan/tsconfig/internal-package.json",
"compilerOptions": {},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,87 @@
CREATE TABLE IF NOT EXISTS "account" (
"id" bigserial PRIMARY KEY NOT NULL,
"accountId" text NOT NULL,
"providerId" text NOT NULL,
"userId" uuid NOT NULL,
"accessToken" text,
"refreshToken" text,
"idToken" text,
"accessTokenExpiresAt" timestamp,
"refreshTokenExpiresAt" timestamp,
"scope" text,
"password" text,
"createdAt" timestamp NOT NULL,
"updatedAt" timestamp NOT NULL
);
--> statement-breakpoint
ALTER TABLE "account" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "apikey" (
"id" bigserial PRIMARY KEY NOT NULL,
"name" text,
"start" text,
"prefix" text,
"key" text NOT NULL,
"userId" uuid NOT NULL,
"refillInterval" integer,
"refillAmount" integer,
"lastRefillAt" timestamp,
"enabled" boolean,
"rateLimitEnabled" boolean,
"rateLimitTimeWindow" integer,
"rateLimitMax" integer,
"requestCount" integer,
"remaining" integer,
"lastRequest" timestamp,
"expiresAt" timestamp,
"createdAt" timestamp NOT NULL,
"updatedAt" timestamp NOT NULL,
"permissions" text,
"metadata" text
);
--> statement-breakpoint
ALTER TABLE "apikey" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "session" (
"id" bigserial PRIMARY KEY NOT NULL,
"expiresAt" timestamp NOT NULL,
"token" text NOT NULL,
"createdAt" timestamp NOT NULL,
"updatedAt" timestamp NOT NULL,
"ipAddress" text,
"userAgent" text,
"userId" uuid NOT NULL,
CONSTRAINT "session_token_unique" UNIQUE("token")
);
--> statement-breakpoint
ALTER TABLE "session" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "verification" (
"id" bigserial PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expiresAt" timestamp NOT NULL,
"createdAt" timestamp,
"updatedAt" timestamp
);
--> statement-breakpoint
ALTER TABLE "verification" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4();--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "emailVerified" SET DATA TYPE boolean;--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "emailVerified" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "createdAt" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "updatedAt" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "apikey" ADD CONSTRAINT "apikey_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -0,0 +1,8 @@
ALTER TABLE "apikey" RENAME TO "apiKey";--> statement-breakpoint
ALTER TABLE "apiKey" DROP CONSTRAINT "apikey_userId_user_id_fk";
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "apiKey" ADD CONSTRAINT "apiKey_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -71,6 +71,20 @@
"when": 1745407291997,
"tag": "0009_shallow_silver_surfer",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1746224485085,
"tag": "0010_wet_true_believers",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1746224588889,
"tag": "0011_cultured_red_skull",
"breakpoints": true
}
]
}

View File

@@ -37,6 +37,7 @@ export const create = async (
id: user.id,
email: user.email,
stripeCustomerId: user.stripeCustomerId,
emailVerified: false,
})
.returning();

View File

@@ -0,0 +1,77 @@
import {
bigserial,
boolean,
integer,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { users } from "./users";
export const session = pgTable("session", {
id: bigserial("id", { mode: "number" }).primaryKey(),
expiresAt: timestamp("expiresAt").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("createdAt").notNull(),
updatedAt: timestamp("updatedAt").notNull(),
ipAddress: text("ipAddress"),
userAgent: text("userAgent"),
userId: uuid("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
}).enableRLS();
export const account = pgTable("account", {
id: bigserial("id", { mode: "number" }).primaryKey(),
accountId: text("accountId").notNull(),
providerId: text("providerId").notNull(),
userId: uuid("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
accessToken: text("accessToken"),
refreshToken: text("refreshToken"),
idToken: text("idToken"),
accessTokenExpiresAt: timestamp("accessTokenExpiresAt"),
refreshTokenExpiresAt: timestamp("refreshTokenExpiresAt"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("createdAt").notNull(),
updatedAt: timestamp("updatedAt").notNull(),
}).enableRLS();
export const verification = pgTable("verification", {
id: bigserial("id", { mode: "number" }).primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expiresAt").notNull(),
createdAt: timestamp("createdAt"),
updatedAt: timestamp("updatedAt"),
}).enableRLS();
export const apiKey = pgTable("apiKey", {
id: bigserial("id", { mode: "number" }).primaryKey(),
name: text("name"),
start: text("start"),
prefix: text("prefix"),
key: text("key").notNull(),
userId: uuid("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
refillInterval: integer("refillInterval"),
refillAmount: integer("refillAmount"),
lastRefillAt: timestamp("lastRefillAt"),
enabled: boolean("enabled"),
rateLimitEnabled: boolean("rateLimitEnabled"),
rateLimitTimeWindow: integer("rateLimitTimeWindow"),
rateLimitMax: integer("rateLimitMax"),
requestCount: integer("requestCount"),
remaining: integer("remaining"),
lastRequest: timestamp("lastRequest"),
expiresAt: timestamp("expiresAt"),
createdAt: timestamp("createdAt").notNull(),
updatedAt: timestamp("updatedAt").notNull(),
permissions: text("permissions"),
metadata: text("metadata"),
}).enableRLS();

View File

@@ -1,4 +1,6 @@
export * from "./auth";
export * from "./boards";
export * from "./auth";
export * from "./cards";
export * from "./feedback";
export * from "./imports";

View File

@@ -1,5 +1,11 @@
import { relations } from "drizzle-orm";
import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
import { relations, sql } from "drizzle-orm";
import {
boolean,
pgTable,
timestamp,
uuid,
varchar,
} from "drizzle-orm/pg-core";
import { boards } from "./boards";
import { cards } from "./cards";
@@ -8,11 +14,16 @@ import { lists } from "./lists";
import { workspaceMembers, workspaces } from "./workspaces";
export const users = pgTable("user", {
id: uuid("id").notNull().primaryKey(),
id: uuid("id")
.notNull()
.primaryKey()
.default(sql`uuid_generate_v4()`),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull().unique(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
emailVerified: boolean("emailVerified").notNull(),
image: varchar("image", { length: 255 }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
stripeCustomerId: varchar("stripeCustomerId", { length: 255 }),
}).enableRLS();

239
pnpm-lock.yaml generated
View File

@@ -84,6 +84,9 @@ importers:
'@kan/api':
specifier: workspace:*
version: link:../../packages/api
'@kan/auth':
specifier: workspace:*
version: link:../../packages/auth
'@kan/db':
specifier: workspace:^
version: link:../../packages/db
@@ -208,6 +211,9 @@ importers:
packages/api:
dependencies:
'@kan/auth':
specifier: workspace:*
version: link:../auth
'@kan/db':
specifier: workspace:*
version: link:../db
@@ -252,6 +258,37 @@ importers:
specifier: 'catalog:'
version: 5.7.2
packages/auth:
dependencies:
better-auth:
specifier: ^1.2.7
version: 1.2.7
devDependencies:
'@kan/db':
specifier: workspace:*
version: link:../db
'@kan/eslint-config':
specifier: workspace:*
version: link:../../tooling/eslint
'@kan/prettier-config':
specifier: workspace:*
version: link:../../tooling/prettier
'@kan/shared':
specifier: workspace:*
version: link:../shared
'@kan/tsconfig':
specifier: workspace:*
version: link:../../tooling/typescript
eslint:
specifier: 'catalog:'
version: 9.16.0(jiti@1.21.6)
prettier:
specifier: 'catalog:'
version: 3.4.2
typescript:
specifier: 'catalog:'
version: 5.7.2
packages/db:
dependencies:
'@kan/shared':
@@ -265,10 +302,10 @@ importers:
version: 0.10.0(utf-8-validate@6.0.3)
drizzle-orm:
specifier: ^0.42.0
version: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)
version: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.27.6)(pg@8.13.1)(postgres@3.4.5)
drizzle-zod:
specifier: ^0.5.1
version: 0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0)
version: 0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.27.6)(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0)
zod:
specifier: 'catalog:'
version: 3.24.0
@@ -590,6 +627,12 @@ packages:
resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==}
engines: {node: '>=6.9.0'}
'@better-auth/utils@0.2.4':
resolution: {integrity: sha512-ayiX87Xd5sCHEplAdeMgwkA0FgnXsEZBgDn890XHHwSWNqqRZDYOq3uj2Ei2leTv1I2KbG5HHn60Ah1i2JWZjQ==}
'@better-fetch/fetch@1.1.18':
resolution: {integrity: sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==}
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -1094,6 +1137,9 @@ packages:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
'@hookform/resolvers@3.9.1':
resolution: {integrity: sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==}
peerDependencies:
@@ -1265,6 +1311,9 @@ packages:
'@jsdevtools/ono@7.1.3':
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
'@levischuck/tiny-cbor@0.2.11':
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
'@mintlify/cli@3.0.97':
resolution: {integrity: sha512-xxEGqYYWyMaYdAhisX/v7MO2BE3WRYlnn9y8g4VvP7aO5Yn0ETxu/j67EPgtJc8uRVVoI3+70OLeIQm1keFQhA==}
engines: {node: '>=18.0.0'}
@@ -1420,6 +1469,13 @@ packages:
cpu: [x64]
os: [win32]
'@noble/ciphers@0.6.0':
resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
'@noble/hashes@1.8.0':
resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
engines: {node: ^14.21.3 || >=16}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1492,6 +1548,21 @@ packages:
'@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
'@peculiar/asn1-android@2.3.16':
resolution: {integrity: sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==}
'@peculiar/asn1-ecc@2.3.15':
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
'@peculiar/asn1-rsa@2.3.15':
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
'@peculiar/asn1-schema@2.3.15':
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
'@peculiar/asn1-x509@2.3.15':
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -1663,6 +1734,13 @@ packages:
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
'@simplewebauthn/browser@13.1.0':
resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
'@simplewebauthn/server@13.1.1':
resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
engines: {node: '>=20.0.0'}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
@@ -2086,6 +2164,10 @@ packages:
resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
engines: {node: '>= 0.4'}
asn1js@3.0.6:
resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==}
engines: {node: '>=12.0.0'}
ast-types-flow@0.0.8:
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
@@ -2131,6 +2213,12 @@ packages:
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
better-auth@1.2.7:
resolution: {integrity: sha512-2hCB263GSrgetsMUZw8vv9O1e4S4AlYJW3P4e8bX9u3Q3idv4u9BzDFCblpTLuL4YjYovghMCN0vurAsctXOAQ==}
better-call@1.0.9:
resolution: {integrity: sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g==}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@@ -3435,6 +3523,9 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
jose@5.10.0:
resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
js-beautify@1.15.1:
resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==}
engines: {node: '>=14'}
@@ -3506,6 +3597,10 @@ packages:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
kysely@0.27.6:
resolution: {integrity: sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==}
engines: {node: '>=14.0.0'}
language-subtag-registry@0.3.23:
resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
@@ -3855,6 +3950,10 @@ packages:
engines: {node: ^18 || >=20}
hasBin: true
nanostores@0.11.4:
resolution: {integrity: sha512-k1oiVNN4hDK8NcNERSZLQiMfRzEGtfnvZvdBvey3SQbgn8Dcrk0h1I6vpxApjb10PFUflZrgJ2WEZyJQ+5v7YQ==}
engines: {node: ^18.0.0 || >=20.0.0}
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@@ -4335,6 +4434,13 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
pvtsutils@1.3.6:
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
pvutils@1.1.3:
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
engines: {node: '>=6.0.0'}
qs@6.13.1:
resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==}
engines: {node: '>=0.6'}
@@ -4524,6 +4630,9 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rou3@0.5.1:
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
@@ -4590,6 +4699,9 @@ packages:
sentence-case@2.1.1:
resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==}
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -4983,6 +5095,11 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
typescript@5.8.3:
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
engines: {node: '>=14.17'}
hasBin: true
ufo@1.5.4:
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
@@ -5221,6 +5338,9 @@ packages:
zod@3.24.0:
resolution: {integrity: sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w==}
zod@3.24.3:
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -5365,6 +5485,13 @@ snapshots:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@better-auth/utils@0.2.4':
dependencies:
typescript: 5.8.3
uncrypto: 0.1.3
'@better-fetch/fetch@1.1.18': {}
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
@@ -5673,6 +5800,8 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@hexagon/base64@1.1.28': {}
'@hookform/resolvers@3.9.1(react-hook-form@7.54.0(react@18.3.1))':
dependencies:
react-hook-form: 7.54.0(react@18.3.1)
@@ -5811,6 +5940,8 @@ snapshots:
'@jsdevtools/ono@7.1.3': {}
'@levischuck/tiny-cbor@0.2.11': {}
'@mintlify/cli@3.0.97':
dependencies:
'@apidevtools/swagger-parser': 10.1.0(openapi-types@12.1.3)
@@ -5982,6 +6113,10 @@ snapshots:
'@next/swc-win32-x64-msvc@15.0.4':
optional: true
'@noble/ciphers@0.6.0': {}
'@noble/hashes@1.8.0': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -6077,6 +6212,39 @@ snapshots:
'@one-ini/wasm@0.1.1': {}
'@peculiar/asn1-android@2.3.16':
dependencies:
'@peculiar/asn1-schema': 2.3.15
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-ecc@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-rsa@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.6
tslib: 2.8.1
'@peculiar/asn1-schema@2.3.15':
dependencies:
asn1js: 3.0.6
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/asn1-x509@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
asn1js: 3.0.6
pvtsutils: 1.3.6
tslib: 2.8.1
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -6242,6 +6410,18 @@ snapshots:
domhandler: 5.0.3
selderee: 0.11.0
'@simplewebauthn/browser@13.1.0': {}
'@simplewebauthn/server@13.1.1':
dependencies:
'@hexagon/base64': 1.1.28
'@levischuck/tiny-cbor': 0.2.11
'@peculiar/asn1-android': 2.3.16
'@peculiar/asn1-ecc': 2.3.15
'@peculiar/asn1-rsa': 2.3.15
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
'@socket.io/component-emitter@3.1.2': {}
'@supabase/auth-js@2.66.1':
@@ -6759,6 +6939,12 @@ snapshots:
is-array-buffer: 3.0.4
is-shared-array-buffer: 1.0.3
asn1js@3.0.6:
dependencies:
pvtsutils: 1.3.6
pvutils: 1.1.3
tslib: 2.8.1
ast-types-flow@0.0.8: {}
ast-types@0.13.4:
@@ -6795,6 +6981,28 @@ snapshots:
before-after-hook@2.2.3: {}
better-auth@1.2.7:
dependencies:
'@better-auth/utils': 0.2.4
'@better-fetch/fetch': 1.1.18
'@noble/ciphers': 0.6.0
'@noble/hashes': 1.8.0
'@simplewebauthn/browser': 13.1.0
'@simplewebauthn/server': 13.1.1
better-call: 1.0.9
defu: 6.1.4
jose: 5.10.0
kysely: 0.27.6
nanostores: 0.11.4
zod: 3.24.3
better-call@1.0.9:
dependencies:
'@better-fetch/fetch': 1.1.18
rou3: 0.5.1
set-cookie-parser: 2.7.1
uncrypto: 0.1.3
binary-extensions@2.3.0: {}
bl@4.1.0:
@@ -7228,17 +7436,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5):
drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.27.6)(pg@8.13.1)(postgres@3.4.5):
optionalDependencies:
'@neondatabase/serverless': 0.10.4
'@types/pg': 8.11.6
'@vercel/postgres': 0.10.0(utf-8-validate@6.0.3)
kysely: 0.27.6
pg: 8.13.1
postgres: 3.4.5
drizzle-zod@0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0):
drizzle-zod@0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.27.6)(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0):
dependencies:
drizzle-orm: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)
drizzle-orm: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(kysely@0.27.6)(pg@8.13.1)(postgres@3.4.5)
zod: 3.24.0
dunder-proto@1.0.0:
@@ -8271,6 +8480,8 @@ snapshots:
jiti@1.21.6: {}
jose@5.10.0: {}
js-beautify@1.15.1:
dependencies:
config-chain: 1.1.13
@@ -8335,6 +8546,8 @@ snapshots:
kleur@4.1.5: {}
kysely@0.27.6: {}
language-subtag-registry@0.3.23: {}
language-tags@1.0.9:
@@ -8914,6 +9127,8 @@ snapshots:
nanoid@5.0.9: {}
nanostores@0.11.4: {}
natural-compare@1.4.0: {}
negotiator@0.6.3: {}
@@ -9382,6 +9597,12 @@ snapshots:
punycode@2.3.1: {}
pvtsutils@1.3.6:
dependencies:
tslib: 2.8.1
pvutils@1.1.3: {}
qs@6.13.1:
dependencies:
side-channel: 1.0.6
@@ -9643,6 +9864,8 @@ snapshots:
dependencies:
glob: 7.2.3
rou3@0.5.1: {}
run-async@2.4.1: {}
run-async@3.0.0: {}
@@ -9706,6 +9929,8 @@ snapshots:
no-case: 2.3.2
upper-case-first: 1.1.2
set-cookie-parser@2.7.1: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -10185,6 +10410,8 @@ snapshots:
typescript@5.7.2: {}
typescript@5.8.3: {}
ufo@1.5.4: {}
uglify-js@3.19.3:
@@ -10442,4 +10669,6 @@ snapshots:
zod@3.24.0: {}
zod@3.24.3: {}
zwitch@2.0.4: {}

View File

@@ -48,6 +48,8 @@
"globalEnv": [
"POSTGRES_URL",
"WEBSITE_URL",
"GOOGLE_CLIENT_ID",
"GOOGLE_CLIENT_SECRET",
"EMAIL_FROM",
"EMAIL_URL",
"EMAIL_TOKEN",