diff --git a/apps/web/src/app.d.ts b/apps/web/src/app.d.ts
index da08e6d..24b0b67 100644
--- a/apps/web/src/app.d.ts
+++ b/apps/web/src/app.d.ts
@@ -1,13 +1,7 @@
-// See https://svelte.dev/docs/kit/types#app.d.ts
-// for information about these interfaces
declare global {
- namespace App {
- // interface Error {}
- // interface Locals {}
- // interface PageData {}
- // interface PageState {}
- // interface Platform {}
- }
+ namespace App {
+ interface Locals {
+ token: string | undefined;
+ }
+ }
}
-
-export {};
diff --git a/apps/web/src/hooks.server.ts b/apps/web/src/hooks.server.ts
index de58268..bb90b27 100644
--- a/apps/web/src/hooks.server.ts
+++ b/apps/web/src/hooks.server.ts
@@ -1,9 +1,9 @@
import type { Handle } from "@sveltejs/kit";
-
-// Middleware desabilitado - proteção de rotas feita no lado do cliente
-// para compatibilidade com localStorage do authStore
+import { getToken } from "@mmailaender/convex-better-auth-svelte/sveltekit";
+import { createAuth } from "@sgse-app/backend/convex/auth";
export const handle: Handle = async ({ event, resolve }) => {
+ event.locals.token = await getToken(createAuth, event.cookies);
+
return resolve(event);
};
-
diff --git a/apps/web/src/lib/auth.ts b/apps/web/src/lib/auth.ts
index 4c4b828..7881eb4 100644
--- a/apps/web/src/lib/auth.ts
+++ b/apps/web/src/lib/auth.ts
@@ -1,21 +1,13 @@
/**
* Cliente Better Auth para frontend SvelteKit
- *
+ *
* Configurado para trabalhar com Convex via plugin convexClient.
* Este cliente será usado para autenticação quando Better Auth estiver ativo.
*/
+
import { createAuthClient } from "better-auth/svelte";
import { convexClient } from "@convex-dev/better-auth/client/plugins";
export const authClient = createAuthClient({
- // Base URL da API Better Auth (mesma do app)
- baseURL: typeof window !== "undefined"
- ? window.location.origin // Usar origem atual em produção
- : "http://localhost:5173", // Fallback para desenvolvimento
- plugins: [
- // Plugin Convex integra Better Auth com Convex backend
- convexClient({
- convexUrl: import.meta.env.PUBLIC_CONVEX_URL || "",
- }),
- ],
+ plugins: [convexClient()],
});
diff --git a/apps/web/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte
index d399c63..9a5c7ba 100644
--- a/apps/web/src/routes/+layout.svelte
+++ b/apps/web/src/routes/+layout.svelte
@@ -1,117 +1,118 @@
diff --git a/bun.lock b/bun.lock
index 16b3183..79be3e8 100644
--- a/bun.lock
+++ b/bun.lock
@@ -67,6 +67,7 @@
"dependencies": {
"@convex-dev/better-auth": "^0.9.7",
"@dicebear/avataaars": "^9.2.4",
+ "better-auth": "1.3.27",
"convex": "catalog:",
"nodemailer": "^7.0.10",
},
@@ -152,7 +153,7 @@
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
- "@better-auth/core": ["@better-auth/core@1.3.34", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.0.19", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-rt/Bgl0Xa8OQ2DUMKCZEJ8vL9kUw4NCJsBP9Sj9uRhbsK8NEMPiznUOFMkUY2FvrslvfKN7H/fivwyHz9c7HzQ=="],
+ "@better-auth/core": ["@better-auth/core@1.3.27", "", { "dependencies": { "better-call": "1.0.19", "zod": "^4.1.5" } }, "sha512-3Sfdax6MQyronY+znx7bOsfQHI6m1SThvJWb0RDscFEAhfqLy95k1sl+/PgGyg0cwc2cUXoEiAOSqYdFYrg3vA=="],
"@better-auth/telemetry": ["@better-auth/telemetry@1.3.34", "", { "dependencies": { "@better-auth/core": "1.3.34", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18" } }, "sha512-aQZ3wN90YMqV49diWxAMe1k7s2qb55KCsedCZne5PlgCjU4s3YtnqyjC5FEpzw2KY8l8rvR7DMAsDl13NjObKA=="],
@@ -574,7 +575,7 @@
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q=="],
- "better-auth": ["better-auth@1.3.34", "", { "dependencies": { "@better-auth/core": "1.3.34", "@better-auth/telemetry": "1.3.34", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "better-call": "1.0.19", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.5" } }, "sha512-LWA52SlvnUBJRbN8VLSTLILPomZY3zZAiLxVJCeSQ5uVmaIKkMBhERitkfJcXB9RJcfl4uP+3EqKkb6hX1/uiw=="],
+ "better-auth": ["better-auth@1.3.27", "", { "dependencies": { "@better-auth/core": "1.3.27", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "better-call": "1.0.19", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.5" } }, "sha512-SwiGAJ7yU6dBhNg0NdV1h5M8T5sa7/AszZVc4vBfMDrLLmvUfbt9JoJ0uRUJUEdKRAAxTyl9yA+F3+GhtAD80w=="],
"better-call": ["better-call@1.0.19", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1", "uncrypto": "^0.1.3" } }, "sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw=="],
@@ -840,6 +841,8 @@
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
+ "@better-auth/telemetry/@better-auth/core": ["@better-auth/core@1.3.34", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.0.19", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-rt/Bgl0Xa8OQ2DUMKCZEJ8vL9kUw4NCJsBP9Sj9uRhbsK8NEMPiznUOFMkUY2FvrslvfKN7H/fivwyHz9c7HzQ=="],
+
"@convex-dev/better-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@sveltejs/kit/@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
@@ -862,6 +865,8 @@
"tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
+ "web/better-auth": ["better-auth@1.3.34", "", { "dependencies": { "@better-auth/core": "1.3.34", "@better-auth/telemetry": "1.3.34", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "better-call": "1.0.19", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.5" } }, "sha512-LWA52SlvnUBJRbN8VLSTLILPomZY3zZAiLxVJCeSQ5uVmaIKkMBhERitkfJcXB9RJcfl4uP+3EqKkb6hX1/uiw=="],
+
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
@@ -916,6 +921,8 @@
"convex/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
+ "web/better-auth/@better-auth/core": ["@better-auth/core@1.3.34", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.0.19", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-rt/Bgl0Xa8OQ2DUMKCZEJ8vL9kUw4NCJsBP9Sj9uRhbsK8NEMPiznUOFMkUY2FvrslvfKN7H/fivwyHz9c7HzQ=="],
+
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
diff --git a/packages/backend/convex/_generated/api.d.ts b/packages/backend/convex/_generated/api.d.ts
index da4cdcc..98e34cf 100644
--- a/packages/backend/convex/_generated/api.d.ts
+++ b/packages/backend/convex/_generated/api.d.ts
@@ -17,6 +17,11 @@ import type * as atestadosLicencas from "../atestadosLicencas.js";
import type * as ausencias from "../ausencias.js";
import type * as autenticacao from "../autenticacao.js";
import type * as auth_utils from "../auth/utils.js";
+import type * as auth from "../auth.js";
+import type * as betterAuth__generated_api from "../betterAuth/_generated/api.js";
+import type * as betterAuth__generated_server from "../betterAuth/_generated/server.js";
+import type * as betterAuth_adapter from "../betterAuth/adapter.js";
+import type * as betterAuth_auth from "../betterAuth/auth.js";
import type * as chat from "../chat.js";
import type * as configuracaoEmail from "../configuracaoEmail.js";
import type * as crons from "../crons.js";
@@ -72,6 +77,11 @@ declare const fullApi: ApiFromModules<{
ausencias: typeof ausencias;
autenticacao: typeof autenticacao;
"auth/utils": typeof auth_utils;
+ auth: typeof auth;
+ "betterAuth/_generated/api": typeof betterAuth__generated_api;
+ "betterAuth/_generated/server": typeof betterAuth__generated_server;
+ "betterAuth/adapter": typeof betterAuth_adapter;
+ "betterAuth/auth": typeof betterAuth_auth;
chat: typeof chat;
configuracaoEmail: typeof configuracaoEmail;
crons: typeof crons;
@@ -125,18 +135,12 @@ export declare const components: {
| {
data: {
createdAt: number;
- displayUsername?: null | string;
email: string;
emailVerified: boolean;
image?: null | string;
- isAnonymous?: null | boolean;
name: string;
- phoneNumber?: null | string;
- phoneNumberVerified?: null | boolean;
- twoFactorEnabled?: null | boolean;
updatedAt: number;
userId?: null | string;
- username?: null | string;
};
model: "user";
}
@@ -179,66 +183,6 @@ export declare const components: {
};
model: "verification";
}
- | {
- data: { backupCodes: string; secret: string; userId: string };
- model: "twoFactor";
- }
- | {
- data: {
- aaguid?: null | string;
- backedUp: boolean;
- counter: number;
- createdAt?: null | number;
- credentialID: string;
- deviceType: string;
- name?: null | string;
- publicKey: string;
- transports?: null | string;
- userId: string;
- };
- model: "passkey";
- }
- | {
- data: {
- clientId?: null | string;
- clientSecret?: null | string;
- createdAt?: null | number;
- disabled?: null | boolean;
- icon?: null | string;
- metadata?: null | string;
- name?: null | string;
- redirectURLs?: null | string;
- type?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- model: "oauthApplication";
- }
- | {
- data: {
- accessToken?: null | string;
- accessTokenExpiresAt?: null | number;
- clientId?: null | string;
- createdAt?: null | number;
- refreshToken?: null | string;
- refreshTokenExpiresAt?: null | number;
- scopes?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- model: "oauthAccessToken";
- }
- | {
- data: {
- clientId?: null | string;
- consentGiven?: null | boolean;
- createdAt?: null | number;
- scopes?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- model: "oauthConsent";
- }
| {
data: {
createdAt: number;
@@ -246,18 +190,6 @@ export declare const components: {
publicKey: string;
};
model: "jwks";
- }
- | {
- data: {
- count?: null | number;
- key?: null | string;
- lastRequest?: null | number;
- };
- model: "rateLimit";
- }
- | {
- data: { count: number; key: string; lastRequest: number };
- model: "ratelimit";
};
onCreateHandle?: string;
select?: Array
;
@@ -280,12 +212,6 @@ export declare const components: {
| "image"
| "createdAt"
| "updatedAt"
- | "twoFactorEnabled"
- | "isAnonymous"
- | "username"
- | "displayUsername"
- | "phoneNumber"
- | "phoneNumberVerified"
| "userId"
| "_id";
operator?:
@@ -414,176 +340,6 @@ export declare const components: {
| null;
}>;
}
- | {
- model: "twoFactor";
- where?: Array<{
- connector?: "AND" | "OR";
- field: "secret" | "backupCodes" | "userId" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "passkey";
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "name"
- | "publicKey"
- | "userId"
- | "credentialID"
- | "counter"
- | "deviceType"
- | "backedUp"
- | "transports"
- | "createdAt"
- | "aaguid"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthApplication";
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "name"
- | "icon"
- | "metadata"
- | "clientId"
- | "clientSecret"
- | "redirectURLs"
- | "type"
- | "disabled"
- | "userId"
- | "createdAt"
- | "updatedAt"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthAccessToken";
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "accessToken"
- | "refreshToken"
- | "accessTokenExpiresAt"
- | "refreshTokenExpiresAt"
- | "clientId"
- | "userId"
- | "scopes"
- | "createdAt"
- | "updatedAt"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthConsent";
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "clientId"
- | "userId"
- | "scopes"
- | "createdAt"
- | "updatedAt"
- | "consentGiven"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
| {
model: "jwks";
where?: Array<{
@@ -609,58 +365,6 @@ export declare const components: {
| Array
| null;
}>;
- }
- | {
- model: "rateLimit";
- where?: Array<{
- connector?: "AND" | "OR";
- field: "key" | "count" | "lastRequest" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "ratelimit";
- where?: Array<{
- connector?: "AND" | "OR";
- field: "key" | "count" | "lastRequest" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
};
onDeleteHandle?: string;
paginationOpts: {
@@ -690,12 +394,6 @@ export declare const components: {
| "image"
| "createdAt"
| "updatedAt"
- | "twoFactorEnabled"
- | "isAnonymous"
- | "username"
- | "displayUsername"
- | "phoneNumber"
- | "phoneNumberVerified"
| "userId"
| "_id";
operator?:
@@ -824,176 +522,6 @@ export declare const components: {
| null;
}>;
}
- | {
- model: "twoFactor";
- where?: Array<{
- connector?: "AND" | "OR";
- field: "secret" | "backupCodes" | "userId" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "passkey";
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "name"
- | "publicKey"
- | "userId"
- | "credentialID"
- | "counter"
- | "deviceType"
- | "backedUp"
- | "transports"
- | "createdAt"
- | "aaguid"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthApplication";
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "name"
- | "icon"
- | "metadata"
- | "clientId"
- | "clientSecret"
- | "redirectURLs"
- | "type"
- | "disabled"
- | "userId"
- | "createdAt"
- | "updatedAt"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthAccessToken";
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "accessToken"
- | "refreshToken"
- | "accessTokenExpiresAt"
- | "refreshTokenExpiresAt"
- | "clientId"
- | "userId"
- | "scopes"
- | "createdAt"
- | "updatedAt"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthConsent";
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "clientId"
- | "userId"
- | "scopes"
- | "createdAt"
- | "updatedAt"
- | "consentGiven"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
| {
model: "jwks";
where?: Array<{
@@ -1019,58 +547,6 @@ export declare const components: {
| Array
| null;
}>;
- }
- | {
- model: "rateLimit";
- where?: Array<{
- connector?: "AND" | "OR";
- field: "key" | "count" | "lastRequest" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "ratelimit";
- where?: Array<{
- connector?: "AND" | "OR";
- field: "key" | "count" | "lastRequest" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
};
onDeleteHandle?: string;
},
@@ -1081,19 +557,7 @@ export declare const components: {
"internal",
{
limit?: number;
- model:
- | "user"
- | "session"
- | "account"
- | "verification"
- | "twoFactor"
- | "passkey"
- | "oauthApplication"
- | "oauthAccessToken"
- | "oauthConsent"
- | "jwks"
- | "rateLimit"
- | "ratelimit";
+ model: "user" | "session" | "account" | "verification" | "jwks";
offset?: number;
paginationOpts: {
cursor: string | null;
@@ -1134,19 +598,7 @@ export declare const components: {
"query",
"internal",
{
- model:
- | "user"
- | "session"
- | "account"
- | "verification"
- | "twoFactor"
- | "passkey"
- | "oauthApplication"
- | "oauthAccessToken"
- | "oauthConsent"
- | "jwks"
- | "rateLimit"
- | "ratelimit";
+ model: "user" | "session" | "account" | "verification" | "jwks";
select?: Array;
where?: Array<{
connector?: "AND" | "OR";
@@ -1174,12 +626,6 @@ export declare const components: {
},
any
>;
- migrationRemoveUserId: FunctionReference<
- "mutation",
- "internal",
- { userId: string },
- any
- >;
updateMany: FunctionReference<
"mutation",
"internal",
@@ -1189,18 +635,12 @@ export declare const components: {
model: "user";
update: {
createdAt?: number;
- displayUsername?: null | string;
email?: string;
emailVerified?: boolean;
image?: null | string;
- isAnonymous?: null | boolean;
name?: string;
- phoneNumber?: null | string;
- phoneNumberVerified?: null | boolean;
- twoFactorEnabled?: null | boolean;
updatedAt?: number;
userId?: null | string;
- username?: null | string;
};
where?: Array<{
connector?: "AND" | "OR";
@@ -1211,12 +651,6 @@ export declare const components: {
| "image"
| "createdAt"
| "updatedAt"
- | "twoFactorEnabled"
- | "isAnonymous"
- | "username"
- | "displayUsername"
- | "phoneNumber"
- | "phoneNumberVerified"
| "userId"
| "_id";
operator?:
@@ -1375,225 +809,6 @@ export declare const components: {
| null;
}>;
}
- | {
- model: "twoFactor";
- update: {
- backupCodes?: string;
- secret?: string;
- userId?: string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field: "secret" | "backupCodes" | "userId" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "passkey";
- update: {
- aaguid?: null | string;
- backedUp?: boolean;
- counter?: number;
- createdAt?: null | number;
- credentialID?: string;
- deviceType?: string;
- name?: null | string;
- publicKey?: string;
- transports?: null | string;
- userId?: string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "name"
- | "publicKey"
- | "userId"
- | "credentialID"
- | "counter"
- | "deviceType"
- | "backedUp"
- | "transports"
- | "createdAt"
- | "aaguid"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthApplication";
- update: {
- clientId?: null | string;
- clientSecret?: null | string;
- createdAt?: null | number;
- disabled?: null | boolean;
- icon?: null | string;
- metadata?: null | string;
- name?: null | string;
- redirectURLs?: null | string;
- type?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "name"
- | "icon"
- | "metadata"
- | "clientId"
- | "clientSecret"
- | "redirectURLs"
- | "type"
- | "disabled"
- | "userId"
- | "createdAt"
- | "updatedAt"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthAccessToken";
- update: {
- accessToken?: null | string;
- accessTokenExpiresAt?: null | number;
- clientId?: null | string;
- createdAt?: null | number;
- refreshToken?: null | string;
- refreshTokenExpiresAt?: null | number;
- scopes?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "accessToken"
- | "refreshToken"
- | "accessTokenExpiresAt"
- | "refreshTokenExpiresAt"
- | "clientId"
- | "userId"
- | "scopes"
- | "createdAt"
- | "updatedAt"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthConsent";
- update: {
- clientId?: null | string;
- consentGiven?: null | boolean;
- createdAt?: null | number;
- scopes?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "clientId"
- | "userId"
- | "scopes"
- | "createdAt"
- | "updatedAt"
- | "consentGiven"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
| {
model: "jwks";
update: {
@@ -1624,64 +839,6 @@ export declare const components: {
| Array
| null;
}>;
- }
- | {
- model: "rateLimit";
- update: {
- count?: null | number;
- key?: null | string;
- lastRequest?: null | number;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field: "key" | "count" | "lastRequest" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "ratelimit";
- update: { count?: number; key?: string; lastRequest?: number };
- where?: Array<{
- connector?: "AND" | "OR";
- field: "key" | "count" | "lastRequest" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
};
onUpdateHandle?: string;
paginationOpts: {
@@ -1704,18 +861,12 @@ export declare const components: {
model: "user";
update: {
createdAt?: number;
- displayUsername?: null | string;
email?: string;
emailVerified?: boolean;
image?: null | string;
- isAnonymous?: null | boolean;
name?: string;
- phoneNumber?: null | string;
- phoneNumberVerified?: null | boolean;
- twoFactorEnabled?: null | boolean;
updatedAt?: number;
userId?: null | string;
- username?: null | string;
};
where?: Array<{
connector?: "AND" | "OR";
@@ -1726,12 +877,6 @@ export declare const components: {
| "image"
| "createdAt"
| "updatedAt"
- | "twoFactorEnabled"
- | "isAnonymous"
- | "username"
- | "displayUsername"
- | "phoneNumber"
- | "phoneNumberVerified"
| "userId"
| "_id";
operator?:
@@ -1890,225 +1035,6 @@ export declare const components: {
| null;
}>;
}
- | {
- model: "twoFactor";
- update: {
- backupCodes?: string;
- secret?: string;
- userId?: string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field: "secret" | "backupCodes" | "userId" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "passkey";
- update: {
- aaguid?: null | string;
- backedUp?: boolean;
- counter?: number;
- createdAt?: null | number;
- credentialID?: string;
- deviceType?: string;
- name?: null | string;
- publicKey?: string;
- transports?: null | string;
- userId?: string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "name"
- | "publicKey"
- | "userId"
- | "credentialID"
- | "counter"
- | "deviceType"
- | "backedUp"
- | "transports"
- | "createdAt"
- | "aaguid"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthApplication";
- update: {
- clientId?: null | string;
- clientSecret?: null | string;
- createdAt?: null | number;
- disabled?: null | boolean;
- icon?: null | string;
- metadata?: null | string;
- name?: null | string;
- redirectURLs?: null | string;
- type?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "name"
- | "icon"
- | "metadata"
- | "clientId"
- | "clientSecret"
- | "redirectURLs"
- | "type"
- | "disabled"
- | "userId"
- | "createdAt"
- | "updatedAt"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthAccessToken";
- update: {
- accessToken?: null | string;
- accessTokenExpiresAt?: null | number;
- clientId?: null | string;
- createdAt?: null | number;
- refreshToken?: null | string;
- refreshTokenExpiresAt?: null | number;
- scopes?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "accessToken"
- | "refreshToken"
- | "accessTokenExpiresAt"
- | "refreshTokenExpiresAt"
- | "clientId"
- | "userId"
- | "scopes"
- | "createdAt"
- | "updatedAt"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "oauthConsent";
- update: {
- clientId?: null | string;
- consentGiven?: null | boolean;
- createdAt?: null | number;
- scopes?: null | string;
- updatedAt?: null | number;
- userId?: null | string;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field:
- | "clientId"
- | "userId"
- | "scopes"
- | "createdAt"
- | "updatedAt"
- | "consentGiven"
- | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
| {
model: "jwks";
update: {
@@ -2139,79 +1065,11 @@ export declare const components: {
| Array
| null;
}>;
- }
- | {
- model: "rateLimit";
- update: {
- count?: null | number;
- key?: null | string;
- lastRequest?: null | number;
- };
- where?: Array<{
- connector?: "AND" | "OR";
- field: "key" | "count" | "lastRequest" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
- }
- | {
- model: "ratelimit";
- update: { count?: number; key?: string; lastRequest?: number };
- where?: Array<{
- connector?: "AND" | "OR";
- field: "key" | "count" | "lastRequest" | "_id";
- operator?:
- | "lt"
- | "lte"
- | "gt"
- | "gte"
- | "eq"
- | "in"
- | "not_in"
- | "ne"
- | "contains"
- | "starts_with"
- | "ends_with";
- value:
- | string
- | number
- | boolean
- | Array
- | Array
- | null;
- }>;
};
onUpdateHandle?: string;
},
any
>;
};
- adapterTest: {
- count: FunctionReference<"query", "internal", any, any>;
- create: FunctionReference<"mutation", "internal", any, any>;
- delete: FunctionReference<"mutation", "internal", any, any>;
- deleteMany: FunctionReference<"mutation", "internal", any, any>;
- findMany: FunctionReference<"query", "internal", any, any>;
- findOne: FunctionReference<"query", "internal", any, any>;
- update: FunctionReference<"mutation", "internal", any, any>;
- updateMany: FunctionReference<"mutation", "internal", any, any>;
- };
};
};
diff --git a/packages/backend/convex/auth.config.ts b/packages/backend/convex/auth.config.ts
new file mode 100644
index 0000000..f4eb564
--- /dev/null
+++ b/packages/backend/convex/auth.config.ts
@@ -0,0 +1,8 @@
+export default {
+ providers: [
+ {
+ domain: process.env.CONVEX_SITE_URL,
+ applicationID: "convex",
+ },
+ ],
+};
diff --git a/packages/backend/convex/auth.ts b/packages/backend/convex/auth.ts
new file mode 100644
index 0000000..94ffdd1
--- /dev/null
+++ b/packages/backend/convex/auth.ts
@@ -0,0 +1,53 @@
+import { createClient, type GenericCtx } from "@convex-dev/better-auth";
+import { convex } from "@convex-dev/better-auth/plugins";
+import { components } from "./_generated/api";
+import { type DataModel } from "./_generated/dataModel";
+import { query } from "./_generated/server";
+import { betterAuth } from "better-auth";
+import authSchema from "./betterAuth/schema";
+
+const siteUrl = process.env.SITE_URL!;
+
+// The component client has methods needed for integrating Convex with Better Auth,
+// as well as helper methods for general use.
+export const authComponent = createClient(
+ components.betterAuth,
+ {
+ local: {
+ schema: authSchema,
+ },
+ }
+);
+
+export const createAuth = (
+ ctx: GenericCtx,
+ { optionsOnly } = { optionsOnly: false }
+) => {
+ return betterAuth({
+ // disable logging when createAuth is called just to generate options.
+ // this is not required, but there's a lot of noise in logs without it.
+ logger: {
+ disabled: optionsOnly,
+ },
+ baseURL: siteUrl,
+ database: authComponent.adapter(ctx),
+ // Configure simple, non-verified email/password to get started
+ emailAndPassword: {
+ enabled: true,
+ requireEmailVerification: false,
+ },
+ plugins: [
+ // The Convex plugin is required for Convex compatibility
+ convex(),
+ ],
+ });
+};
+
+// Example function for getting the current user
+// Feel free to edit, omit, etc.
+export const getCurrentUser = query({
+ args: {},
+ handler: async (ctx) => {
+ return authComponent.getAuthUser(ctx);
+ },
+});
diff --git a/packages/backend/convex/betterAuth/_generated/api.d.ts b/packages/backend/convex/betterAuth/_generated/api.d.ts
new file mode 100644
index 0000000..385ea79
--- /dev/null
+++ b/packages/backend/convex/betterAuth/_generated/api.d.ts
@@ -0,0 +1,993 @@
+/* eslint-disable */
+/**
+ * Generated `api` utility.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import type * as adapter from "../adapter.js";
+import type * as auth from "../auth.js";
+
+import type {
+ ApiFromModules,
+ FilterApi,
+ FunctionReference,
+} from "convex/server";
+
+/**
+ * A utility for referencing Convex functions in your app's API.
+ *
+ * Usage:
+ * ```js
+ * const myFunctionReference = api.myModule.myFunction;
+ * ```
+ */
+declare const fullApi: ApiFromModules<{
+ adapter: typeof adapter;
+ auth: typeof auth;
+}>;
+export type Mounts = {
+ adapter: {
+ create: FunctionReference<
+ "mutation",
+ "public",
+ {
+ input:
+ | {
+ data: {
+ createdAt: number;
+ email: string;
+ emailVerified: boolean;
+ image?: null | string;
+ name: string;
+ updatedAt: number;
+ userId?: null | string;
+ };
+ model: "user";
+ }
+ | {
+ data: {
+ createdAt: number;
+ expiresAt: number;
+ ipAddress?: null | string;
+ token: string;
+ updatedAt: number;
+ userAgent?: null | string;
+ userId: string;
+ };
+ model: "session";
+ }
+ | {
+ data: {
+ accessToken?: null | string;
+ accessTokenExpiresAt?: null | number;
+ accountId: string;
+ createdAt: number;
+ idToken?: null | string;
+ password?: null | string;
+ providerId: string;
+ refreshToken?: null | string;
+ refreshTokenExpiresAt?: null | number;
+ scope?: null | string;
+ updatedAt: number;
+ userId: string;
+ };
+ model: "account";
+ }
+ | {
+ data: {
+ createdAt: number;
+ expiresAt: number;
+ identifier: string;
+ updatedAt: number;
+ value: string;
+ };
+ model: "verification";
+ }
+ | {
+ data: {
+ createdAt: number;
+ privateKey: string;
+ publicKey: string;
+ };
+ model: "jwks";
+ };
+ onCreateHandle?: string;
+ select?: Array;
+ },
+ any
+ >;
+ deleteMany: FunctionReference<
+ "mutation",
+ "public",
+ {
+ input:
+ | {
+ model: "user";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "name"
+ | "email"
+ | "emailVerified"
+ | "image"
+ | "createdAt"
+ | "updatedAt"
+ | "userId"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "session";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "expiresAt"
+ | "token"
+ | "createdAt"
+ | "updatedAt"
+ | "ipAddress"
+ | "userAgent"
+ | "userId"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "account";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "accountId"
+ | "providerId"
+ | "userId"
+ | "accessToken"
+ | "refreshToken"
+ | "idToken"
+ | "accessTokenExpiresAt"
+ | "refreshTokenExpiresAt"
+ | "scope"
+ | "password"
+ | "createdAt"
+ | "updatedAt"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "verification";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "identifier"
+ | "value"
+ | "expiresAt"
+ | "createdAt"
+ | "updatedAt"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "jwks";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field: "publicKey" | "privateKey" | "createdAt" | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ };
+ onDeleteHandle?: string;
+ paginationOpts: {
+ cursor: string | null;
+ endCursor?: string | null;
+ id?: number;
+ maximumBytesRead?: number;
+ maximumRowsRead?: number;
+ numItems: number;
+ };
+ },
+ any
+ >;
+ deleteOne: FunctionReference<
+ "mutation",
+ "public",
+ {
+ input:
+ | {
+ model: "user";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "name"
+ | "email"
+ | "emailVerified"
+ | "image"
+ | "createdAt"
+ | "updatedAt"
+ | "userId"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "session";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "expiresAt"
+ | "token"
+ | "createdAt"
+ | "updatedAt"
+ | "ipAddress"
+ | "userAgent"
+ | "userId"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "account";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "accountId"
+ | "providerId"
+ | "userId"
+ | "accessToken"
+ | "refreshToken"
+ | "idToken"
+ | "accessTokenExpiresAt"
+ | "refreshTokenExpiresAt"
+ | "scope"
+ | "password"
+ | "createdAt"
+ | "updatedAt"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "verification";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "identifier"
+ | "value"
+ | "expiresAt"
+ | "createdAt"
+ | "updatedAt"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "jwks";
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field: "publicKey" | "privateKey" | "createdAt" | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ };
+ onDeleteHandle?: string;
+ },
+ any
+ >;
+ findMany: FunctionReference<
+ "query",
+ "public",
+ {
+ limit?: number;
+ model: "user" | "session" | "account" | "verification" | "jwks";
+ offset?: number;
+ paginationOpts: {
+ cursor: string | null;
+ endCursor?: string | null;
+ id?: number;
+ maximumBytesRead?: number;
+ maximumRowsRead?: number;
+ numItems: number;
+ };
+ sortBy?: { direction: "asc" | "desc"; field: string };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field: string;
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ },
+ any
+ >;
+ findOne: FunctionReference<
+ "query",
+ "public",
+ {
+ model: "user" | "session" | "account" | "verification" | "jwks";
+ select?: Array;
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field: string;
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ },
+ any
+ >;
+ updateMany: FunctionReference<
+ "mutation",
+ "public",
+ {
+ input:
+ | {
+ model: "user";
+ update: {
+ createdAt?: number;
+ email?: string;
+ emailVerified?: boolean;
+ image?: null | string;
+ name?: string;
+ updatedAt?: number;
+ userId?: null | string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "name"
+ | "email"
+ | "emailVerified"
+ | "image"
+ | "createdAt"
+ | "updatedAt"
+ | "userId"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "session";
+ update: {
+ createdAt?: number;
+ expiresAt?: number;
+ ipAddress?: null | string;
+ token?: string;
+ updatedAt?: number;
+ userAgent?: null | string;
+ userId?: string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "expiresAt"
+ | "token"
+ | "createdAt"
+ | "updatedAt"
+ | "ipAddress"
+ | "userAgent"
+ | "userId"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "account";
+ update: {
+ accessToken?: null | string;
+ accessTokenExpiresAt?: null | number;
+ accountId?: string;
+ createdAt?: number;
+ idToken?: null | string;
+ password?: null | string;
+ providerId?: string;
+ refreshToken?: null | string;
+ refreshTokenExpiresAt?: null | number;
+ scope?: null | string;
+ updatedAt?: number;
+ userId?: string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "accountId"
+ | "providerId"
+ | "userId"
+ | "accessToken"
+ | "refreshToken"
+ | "idToken"
+ | "accessTokenExpiresAt"
+ | "refreshTokenExpiresAt"
+ | "scope"
+ | "password"
+ | "createdAt"
+ | "updatedAt"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "verification";
+ update: {
+ createdAt?: number;
+ expiresAt?: number;
+ identifier?: string;
+ updatedAt?: number;
+ value?: string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "identifier"
+ | "value"
+ | "expiresAt"
+ | "createdAt"
+ | "updatedAt"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "jwks";
+ update: {
+ createdAt?: number;
+ privateKey?: string;
+ publicKey?: string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field: "publicKey" | "privateKey" | "createdAt" | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ };
+ onUpdateHandle?: string;
+ paginationOpts: {
+ cursor: string | null;
+ endCursor?: string | null;
+ id?: number;
+ maximumBytesRead?: number;
+ maximumRowsRead?: number;
+ numItems: number;
+ };
+ },
+ any
+ >;
+ updateOne: FunctionReference<
+ "mutation",
+ "public",
+ {
+ input:
+ | {
+ model: "user";
+ update: {
+ createdAt?: number;
+ email?: string;
+ emailVerified?: boolean;
+ image?: null | string;
+ name?: string;
+ updatedAt?: number;
+ userId?: null | string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "name"
+ | "email"
+ | "emailVerified"
+ | "image"
+ | "createdAt"
+ | "updatedAt"
+ | "userId"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "session";
+ update: {
+ createdAt?: number;
+ expiresAt?: number;
+ ipAddress?: null | string;
+ token?: string;
+ updatedAt?: number;
+ userAgent?: null | string;
+ userId?: string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "expiresAt"
+ | "token"
+ | "createdAt"
+ | "updatedAt"
+ | "ipAddress"
+ | "userAgent"
+ | "userId"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "account";
+ update: {
+ accessToken?: null | string;
+ accessTokenExpiresAt?: null | number;
+ accountId?: string;
+ createdAt?: number;
+ idToken?: null | string;
+ password?: null | string;
+ providerId?: string;
+ refreshToken?: null | string;
+ refreshTokenExpiresAt?: null | number;
+ scope?: null | string;
+ updatedAt?: number;
+ userId?: string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "accountId"
+ | "providerId"
+ | "userId"
+ | "accessToken"
+ | "refreshToken"
+ | "idToken"
+ | "accessTokenExpiresAt"
+ | "refreshTokenExpiresAt"
+ | "scope"
+ | "password"
+ | "createdAt"
+ | "updatedAt"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "verification";
+ update: {
+ createdAt?: number;
+ expiresAt?: number;
+ identifier?: string;
+ updatedAt?: number;
+ value?: string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field:
+ | "identifier"
+ | "value"
+ | "expiresAt"
+ | "createdAt"
+ | "updatedAt"
+ | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ }
+ | {
+ model: "jwks";
+ update: {
+ createdAt?: number;
+ privateKey?: string;
+ publicKey?: string;
+ };
+ where?: Array<{
+ connector?: "AND" | "OR";
+ field: "publicKey" | "privateKey" | "createdAt" | "_id";
+ operator?:
+ | "lt"
+ | "lte"
+ | "gt"
+ | "gte"
+ | "eq"
+ | "in"
+ | "not_in"
+ | "ne"
+ | "contains"
+ | "starts_with"
+ | "ends_with";
+ value:
+ | string
+ | number
+ | boolean
+ | Array
+ | Array
+ | null;
+ }>;
+ };
+ onUpdateHandle?: string;
+ },
+ any
+ >;
+ };
+};
+// For now fullApiWithMounts is only fullApi which provides
+// jump-to-definition in component client code.
+// Use Mounts for the same type without the inference.
+declare const fullApiWithMounts: typeof fullApi;
+
+export declare const api: FilterApi<
+ typeof fullApiWithMounts,
+ FunctionReference
+>;
+export declare const internal: FilterApi<
+ typeof fullApiWithMounts,
+ FunctionReference
+>;
+
+export declare const components: {};
diff --git a/packages/backend/convex/betterAuth/_generated/api.js b/packages/backend/convex/betterAuth/_generated/api.js
new file mode 100644
index 0000000..44bf985
--- /dev/null
+++ b/packages/backend/convex/betterAuth/_generated/api.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+/**
+ * Generated `api` utility.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import { anyApi, componentsGeneric } from "convex/server";
+
+/**
+ * A utility for referencing Convex functions in your app's API.
+ *
+ * Usage:
+ * ```js
+ * const myFunctionReference = api.myModule.myFunction;
+ * ```
+ */
+export const api = anyApi;
+export const internal = anyApi;
+export const components = componentsGeneric();
diff --git a/packages/backend/convex/betterAuth/_generated/dataModel.d.ts b/packages/backend/convex/betterAuth/_generated/dataModel.d.ts
new file mode 100644
index 0000000..8541f31
--- /dev/null
+++ b/packages/backend/convex/betterAuth/_generated/dataModel.d.ts
@@ -0,0 +1,60 @@
+/* eslint-disable */
+/**
+ * Generated data model types.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import type {
+ DataModelFromSchemaDefinition,
+ DocumentByName,
+ TableNamesInDataModel,
+ SystemTableNames,
+} from "convex/server";
+import type { GenericId } from "convex/values";
+import schema from "../schema.js";
+
+/**
+ * The names of all of your Convex tables.
+ */
+export type TableNames = TableNamesInDataModel;
+
+/**
+ * The type of a document stored in Convex.
+ *
+ * @typeParam TableName - A string literal type of the table name (like "users").
+ */
+export type Doc = DocumentByName<
+ DataModel,
+ TableName
+>;
+
+/**
+ * An identifier for a document in Convex.
+ *
+ * Convex documents are uniquely identified by their `Id`, which is accessible
+ * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
+ *
+ * Documents can be loaded using `db.get(id)` in query and mutation functions.
+ *
+ * IDs are just strings at runtime, but this type can be used to distinguish them from other
+ * strings when type checking.
+ *
+ * @typeParam TableName - A string literal type of the table name (like "users").
+ */
+export type Id =
+ GenericId;
+
+/**
+ * A type describing your Convex data model.
+ *
+ * This type includes information about what tables you have, the type of
+ * documents stored in those tables, and the indexes defined on them.
+ *
+ * This type is used to parameterize methods like `queryGeneric` and
+ * `mutationGeneric` to make them type-safe.
+ */
+export type DataModel = DataModelFromSchemaDefinition;
diff --git a/packages/backend/convex/betterAuth/_generated/server.d.ts b/packages/backend/convex/betterAuth/_generated/server.d.ts
new file mode 100644
index 0000000..b5c6828
--- /dev/null
+++ b/packages/backend/convex/betterAuth/_generated/server.d.ts
@@ -0,0 +1,149 @@
+/* eslint-disable */
+/**
+ * Generated utilities for implementing server-side Convex query and mutation functions.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import {
+ ActionBuilder,
+ AnyComponents,
+ HttpActionBuilder,
+ MutationBuilder,
+ QueryBuilder,
+ GenericActionCtx,
+ GenericMutationCtx,
+ GenericQueryCtx,
+ GenericDatabaseReader,
+ GenericDatabaseWriter,
+ FunctionReference,
+} from "convex/server";
+import type { DataModel } from "./dataModel.js";
+
+type GenericCtx =
+ | GenericActionCtx
+ | GenericMutationCtx
+ | GenericQueryCtx;
+
+/**
+ * Define a query in this Convex app's public API.
+ *
+ * This function will be allowed to read your Convex database and will be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export declare const query: QueryBuilder;
+
+/**
+ * Define a query that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalQuery: QueryBuilder;
+
+/**
+ * Define a mutation in this Convex app's public API.
+ *
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export declare const mutation: MutationBuilder;
+
+/**
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalMutation: MutationBuilder;
+
+/**
+ * Define an action in this Convex app's public API.
+ *
+ * An action is a function which can execute any JavaScript code, including non-deterministic
+ * code and code with side-effects, like calling third-party services.
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
+ *
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
+ */
+export declare const action: ActionBuilder;
+
+/**
+ * Define an action that is only accessible from other Convex functions (but not from the client).
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalAction: ActionBuilder;
+
+/**
+ * Define an HTTP action.
+ *
+ * This function will be used to respond to HTTP requests received by a Convex
+ * deployment if the requests matches the path and method where this action
+ * is routed. Be sure to route your action in `convex/http.js`.
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
+ */
+export declare const httpAction: HttpActionBuilder;
+
+/**
+ * A set of services for use within Convex query functions.
+ *
+ * The query context is passed as the first argument to any Convex query
+ * function run on the server.
+ *
+ * This differs from the {@link MutationCtx} because all of the services are
+ * read-only.
+ */
+export type QueryCtx = GenericQueryCtx;
+
+/**
+ * A set of services for use within Convex mutation functions.
+ *
+ * The mutation context is passed as the first argument to any Convex mutation
+ * function run on the server.
+ */
+export type MutationCtx = GenericMutationCtx;
+
+/**
+ * A set of services for use within Convex action functions.
+ *
+ * The action context is passed as the first argument to any Convex action
+ * function run on the server.
+ */
+export type ActionCtx = GenericActionCtx;
+
+/**
+ * An interface to read from the database within Convex query functions.
+ *
+ * The two entry points are {@link DatabaseReader.get}, which fetches a single
+ * document by its {@link Id}, or {@link DatabaseReader.query}, which starts
+ * building a query.
+ */
+export type DatabaseReader = GenericDatabaseReader;
+
+/**
+ * An interface to read from and write to the database within Convex mutation
+ * functions.
+ *
+ * Convex guarantees that all writes within a single mutation are
+ * executed atomically, so you never have to worry about partial writes leaving
+ * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
+ * for the guarantees Convex provides your functions.
+ */
+export type DatabaseWriter = GenericDatabaseWriter;
diff --git a/packages/backend/convex/betterAuth/_generated/server.js b/packages/backend/convex/betterAuth/_generated/server.js
new file mode 100644
index 0000000..4a21df4
--- /dev/null
+++ b/packages/backend/convex/betterAuth/_generated/server.js
@@ -0,0 +1,90 @@
+/* eslint-disable */
+/**
+ * Generated utilities for implementing server-side Convex query and mutation functions.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import {
+ actionGeneric,
+ httpActionGeneric,
+ queryGeneric,
+ mutationGeneric,
+ internalActionGeneric,
+ internalMutationGeneric,
+ internalQueryGeneric,
+ componentsGeneric,
+} from "convex/server";
+
+/**
+ * Define a query in this Convex app's public API.
+ *
+ * This function will be allowed to read your Convex database and will be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export const query = queryGeneric;
+
+/**
+ * Define a query that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export const internalQuery = internalQueryGeneric;
+
+/**
+ * Define a mutation in this Convex app's public API.
+ *
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export const mutation = mutationGeneric;
+
+/**
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export const internalMutation = internalMutationGeneric;
+
+/**
+ * Define an action in this Convex app's public API.
+ *
+ * An action is a function which can execute any JavaScript code, including non-deterministic
+ * code and code with side-effects, like calling third-party services.
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
+ *
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
+ */
+export const action = actionGeneric;
+
+/**
+ * Define an action that is only accessible from other Convex functions (but not from the client).
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
+ */
+export const internalAction = internalActionGeneric;
+
+/**
+ * Define a Convex HTTP action.
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
+ * as its second.
+ * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
+ */
+export const httpAction = httpActionGeneric;
diff --git a/packages/backend/convex/betterAuth/adapter.ts b/packages/backend/convex/betterAuth/adapter.ts
new file mode 100644
index 0000000..0741d37
--- /dev/null
+++ b/packages/backend/convex/betterAuth/adapter.ts
@@ -0,0 +1,13 @@
+import { createApi } from "@convex-dev/better-auth";
+import schema from "./schema";
+import { createAuth } from "../auth";
+
+export const {
+ create,
+ findOne,
+ findMany,
+ updateOne,
+ updateMany,
+ deleteOne,
+ deleteMany,
+} = createApi(schema, createAuth);
diff --git a/packages/backend/convex/betterAuth/auth.ts b/packages/backend/convex/betterAuth/auth.ts
new file mode 100644
index 0000000..be7e455
--- /dev/null
+++ b/packages/backend/convex/betterAuth/auth.ts
@@ -0,0 +1,5 @@
+import { createAuth } from "../auth";
+import { getStaticAuth } from "@convex-dev/better-auth";
+
+// Export a static instance for Better Auth schema generation
+export const auth = getStaticAuth(createAuth);
diff --git a/packages/backend/convex/betterAuth/convex.config.ts b/packages/backend/convex/betterAuth/convex.config.ts
new file mode 100644
index 0000000..fe8c88e
--- /dev/null
+++ b/packages/backend/convex/betterAuth/convex.config.ts
@@ -0,0 +1,5 @@
+import { defineComponent } from "convex/server";
+
+const component = defineComponent("betterAuth");
+
+export default component;
diff --git a/packages/backend/convex/betterAuth/schema.ts b/packages/backend/convex/betterAuth/schema.ts
new file mode 100644
index 0000000..167d19f
--- /dev/null
+++ b/packages/backend/convex/betterAuth/schema.ts
@@ -0,0 +1,70 @@
+// This file is auto-generated. Do not edit this file manually.
+// To regenerate the schema, run:
+// `npx @better-auth/cli generate --output undefined -y`
+
+import { defineSchema, defineTable } from "convex/server";
+import { v } from "convex/values";
+
+export const tables = {
+ user: defineTable({
+ name: v.string(),
+ email: v.string(),
+ emailVerified: v.boolean(),
+ image: v.optional(v.union(v.null(), v.string())),
+ createdAt: v.number(),
+ updatedAt: v.number(),
+ userId: v.optional(v.union(v.null(), v.string())),
+ })
+ .index("email_name", ["email","name"])
+ .index("name", ["name"])
+ .index("userId", ["userId"]),
+ session: defineTable({
+ expiresAt: v.number(),
+ token: v.string(),
+ createdAt: v.number(),
+ updatedAt: v.number(),
+ ipAddress: v.optional(v.union(v.null(), v.string())),
+ userAgent: v.optional(v.union(v.null(), v.string())),
+ userId: v.string(),
+ })
+ .index("expiresAt", ["expiresAt"])
+ .index("expiresAt_userId", ["expiresAt","userId"])
+ .index("token", ["token"])
+ .index("userId", ["userId"]),
+ account: defineTable({
+ accountId: v.string(),
+ providerId: v.string(),
+ userId: v.string(),
+ accessToken: v.optional(v.union(v.null(), v.string())),
+ refreshToken: v.optional(v.union(v.null(), v.string())),
+ idToken: v.optional(v.union(v.null(), v.string())),
+ accessTokenExpiresAt: v.optional(v.union(v.null(), v.number())),
+ refreshTokenExpiresAt: v.optional(v.union(v.null(), v.number())),
+ scope: v.optional(v.union(v.null(), v.string())),
+ password: v.optional(v.union(v.null(), v.string())),
+ createdAt: v.number(),
+ updatedAt: v.number(),
+ })
+ .index("accountId", ["accountId"])
+ .index("accountId_providerId", ["accountId","providerId"])
+ .index("providerId_userId", ["providerId","userId"])
+ .index("userId", ["userId"]),
+ verification: defineTable({
+ identifier: v.string(),
+ value: v.string(),
+ expiresAt: v.number(),
+ createdAt: v.number(),
+ updatedAt: v.number(),
+ })
+ .index("expiresAt", ["expiresAt"])
+ .index("identifier", ["identifier"]),
+ jwks: defineTable({
+ publicKey: v.string(),
+ privateKey: v.string(),
+ createdAt: v.number(),
+ }),
+};
+
+const schema = defineSchema(tables);
+
+export default schema;
diff --git a/packages/backend/convex/convex.config.ts b/packages/backend/convex/convex.config.ts
index 316587d..f2d05fb 100644
--- a/packages/backend/convex/convex.config.ts
+++ b/packages/backend/convex/convex.config.ts
@@ -1,72 +1,7 @@
-import { defineApp, defineAuth } from "convex/server";
-import betterAuth from "@convex-dev/better-auth/convex.config";
+import { defineApp } from "convex/server";
+import betterAuth from "./betterAuth/convex.config";
-/**
- * Custom Auth Provider para aceitar tokens customizados
- *
- * Este provider funciona junto com Better Auth para suportar:
- * 1. Tokens customizados do sistema atual (via sessoes)
- * 2. Tokens do Better Auth (quando configurado)
- */
-const customAuth = defineAuth({
- getToken: async (request: Request): Promise => {
- // Tentar obter token de várias fontes
- // 1. Authorization header (Bearer token)
- const authHeader = request.headers.get("authorization");
- if (authHeader?.startsWith("Bearer ")) {
- return authHeader.substring(7);
- }
-
- // 2. x-auth-token header
- const xAuthToken = request.headers.get("x-auth-token");
- if (xAuthToken) {
- return xAuthToken;
- }
-
- // 3. Query parameter (para WebSocket)
- const url = new URL(request.url);
- const queryToken = url.searchParams.get("authToken");
- if (queryToken) {
- return queryToken;
- }
-
- return null;
- },
-
- getIdentity: async (ctx, token: string) => {
- if (!token) return null;
-
- // Buscar sessão ativa por token
- const sessao = await ctx.db
- .query("sessoes")
- .withIndex("by_token", (q) => q.eq("token", token))
- .filter((q) => q.eq(q.field("ativo"), true))
- .first();
-
- if (!sessao) return null;
-
- // Buscar usuário da sessão
- const usuario = await ctx.db.get(sessao.usuarioId);
- if (!usuario || !usuario.ativo) return null;
-
- // Retornar identity compatível com Better Auth
- return {
- subject: usuario._id,
- email: usuario.email,
- emailVerified: true,
- name: usuario.nome,
- };
- },
-});
-
-/**
- * Configuração Better Auth para Convex
- *
- * Usando Better Auth oficialmente integrado com Convex.
- * O Custom Auth Provider acima funciona junto com Better Auth para suportar tokens customizados.
- */
const app = defineApp();
-app.use(customAuth);
app.use(betterAuth);
export default app;
diff --git a/packages/backend/convex/http.ts b/packages/backend/convex/http.ts
index a4cf87c..16aaafb 100644
--- a/packages/backend/convex/http.ts
+++ b/packages/backend/convex/http.ts
@@ -3,6 +3,7 @@ import { httpAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { getClientIP } from "./utils/getClientIP";
import { v } from "convex/values";
+import { authComponent, createAuth } from "./auth";
const http = httpRouter();
@@ -18,9 +19,9 @@ http.route({
request.headers.forEach((value, key) => {
headers[key] = value;
});
-
+
const ip = getClientIP(request);
-
+
return new Response(
JSON.stringify({
headers,
@@ -53,11 +54,11 @@ http.route({
});
console.log("Headers:", headersEntries.join(", "));
console.log("Request URL:", request.url);
-
+
// Extrair IP do cliente do request
let clientIP = getClientIP(request);
console.log("IP extraído:", clientIP);
-
+
// Se não encontrou IP, tentar obter do URL ou usar valor padrão
if (!clientIP) {
try {
@@ -77,13 +78,13 @@ http.route({
console.warn("Erro ao processar URL para IP");
}
}
-
+
// Extrair User-Agent
const userAgent = request.headers.get("user-agent") || undefined;
-
+
// Ler body da requisição
const body = await request.json();
-
+
if (!body.matriculaOuEmail || !body.senha) {
return new Response(
JSON.stringify({
@@ -96,15 +97,18 @@ http.route({
}
);
}
-
+
// Chamar a mutation de login interna com IP e userAgent
- const resultado = await ctx.runMutation(internal.autenticacao.loginComIP, {
- matriculaOuEmail: body.matriculaOuEmail,
- senha: body.senha,
- ipAddress: clientIP,
- userAgent: userAgent,
- });
-
+ const resultado = await ctx.runMutation(
+ internal.autenticacao.loginComIP,
+ {
+ matriculaOuEmail: body.matriculaOuEmail,
+ senha: body.senha,
+ ipAddress: clientIP,
+ userAgent: userAgent,
+ }
+ );
+
return new Response(JSON.stringify(resultado), {
status: 200,
headers: {
@@ -118,7 +122,8 @@ http.route({
return new Response(
JSON.stringify({
sucesso: false,
- erro: error instanceof Error ? error.message : "Erro ao processar login",
+ erro:
+ error instanceof Error ? error.message : "Erro ao processar login",
}),
{
status: 500,
@@ -147,4 +152,6 @@ http.route({
}),
});
+authComponent.registerRoutes(http, createAuth);
+
export default http;
diff --git a/packages/backend/package.json b/packages/backend/package.json
index b563e84..8a1d530 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -22,6 +22,7 @@
"dependencies": {
"@convex-dev/better-auth": "^0.9.7",
"@dicebear/avataaars": "^9.2.4",
+ "better-auth": "1.3.27",
"convex": "catalog:",
"nodemailer": "^7.0.10"
}