@wopr-network/platform-core 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,10 +11,55 @@ import { betterAuth } from "better-auth";
11
11
  import type { Pool } from "pg";
12
12
  import type { PlatformDb } from "../db/index.js";
13
13
  import { PgEmailVerifier } from "../email/verification.js";
14
+ /** OAuth provider credentials. */
15
+ export interface OAuthProvider {
16
+ clientId: string;
17
+ clientSecret: string;
18
+ }
19
+ /** Rate limit rule for a specific auth endpoint. */
20
+ export interface AuthRateLimitRule {
21
+ window: number;
22
+ max: number;
23
+ }
14
24
  /** Configuration for initializing Better Auth in platform-core. */
15
25
  export interface BetterAuthConfig {
16
26
  pool: Pool;
17
27
  db: PlatformDb;
28
+ /** HMAC secret for session tokens. Falls back to BETTER_AUTH_SECRET env var. */
29
+ secret?: string;
30
+ /** Base URL for OAuth callbacks. Falls back to BETTER_AUTH_URL env var. */
31
+ baseURL?: string;
32
+ /** Route prefix. Default: "/api/auth" */
33
+ basePath?: string;
34
+ /** Email+password config. Default: enabled with 12-char min. */
35
+ emailAndPassword?: {
36
+ enabled: boolean;
37
+ minPasswordLength?: number;
38
+ };
39
+ /** OAuth providers. Default: reads GITHUB/DISCORD/GOOGLE env vars. */
40
+ socialProviders?: {
41
+ github?: OAuthProvider;
42
+ discord?: OAuthProvider;
43
+ google?: OAuthProvider;
44
+ };
45
+ /** Trusted providers for account linking. Default: ["github", "google"] */
46
+ trustedProviders?: string[];
47
+ /** Enable 2FA plugin. Default: true */
48
+ twoFactor?: boolean;
49
+ /** Cookie cache max age in seconds. Default: 300 (5 min) */
50
+ sessionCacheMaxAge?: number;
51
+ /** Cookie prefix. Default: "better-auth" */
52
+ cookiePrefix?: string;
53
+ /** Cookie domain (e.g., ".wopr.bot"). Falls back to COOKIE_DOMAIN env var. */
54
+ cookieDomain?: string;
55
+ /** Global rate limit window in seconds. Default: 60 */
56
+ rateLimitWindow?: number;
57
+ /** Global rate limit max requests. Default: 100 */
58
+ rateLimitMax?: number;
59
+ /** Per-endpoint rate limit overrides. Default: sign-in/sign-up/reset limits. */
60
+ rateLimitRules?: Record<string, AuthRateLimitRule>;
61
+ /** Trusted origins for CORS. Falls back to UI_ORIGIN env var. */
62
+ trustedOrigins?: string[];
18
63
  /** Called after a new user signs up (e.g., create personal tenant). */
19
64
  onUserCreated?: (userId: string, userName: string, email: string) => Promise<void>;
20
65
  }
@@ -16,8 +16,11 @@ import { getEmailClient } from "../email/client.js";
16
16
  import { passwordResetEmailTemplate, verifyEmailTemplate } from "../email/templates.js";
17
17
  import { generateVerificationToken, initVerificationSchema, PgEmailVerifier } from "../email/verification.js";
18
18
  import { createUserCreator } from "./user-creator.js";
19
- const BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET || "";
20
- const BETTER_AUTH_URL = process.env.BETTER_AUTH_URL || "http://localhost:3100";
19
+ const DEFAULT_RATE_LIMIT_RULES = {
20
+ "/sign-in/email": { window: 900, max: 5 },
21
+ "/sign-up/email": { window: 3600, max: 10 },
22
+ "/request-password-reset": { window: 3600, max: 3 },
23
+ };
21
24
  let _config = null;
22
25
  let _userCreator = null;
23
26
  let _userCreatorPromise = null;
@@ -34,32 +37,57 @@ async function getUserCreator() {
34
37
  }
35
38
  return _userCreatorPromise;
36
39
  }
37
- function authOptions(pool) {
40
+ /** Resolve OAuth providers from config or env vars. */
41
+ function resolveSocialProviders(cfg) {
42
+ if (cfg.socialProviders)
43
+ return cfg.socialProviders;
44
+ return {
45
+ ...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
46
+ ? { github: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET } }
47
+ : {}),
48
+ ...(process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET
49
+ ? { discord: { clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET } }
50
+ : {}),
51
+ ...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
52
+ ? { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET } }
53
+ : {}),
54
+ };
55
+ }
56
+ function authOptions(cfg) {
57
+ const pool = cfg.pool;
58
+ const secret = cfg.secret || process.env.BETTER_AUTH_SECRET;
59
+ if (!secret) {
60
+ if (process.env.NODE_ENV === "production") {
61
+ throw new Error("BETTER_AUTH_SECRET is required in production");
62
+ }
63
+ logger.warn("BetterAuth secret not configured — sessions may be insecure");
64
+ }
65
+ const baseURL = cfg.baseURL || process.env.BETTER_AUTH_URL || "http://localhost:3100";
66
+ const basePath = cfg.basePath || "/api/auth";
67
+ const cookieDomain = cfg.cookieDomain || process.env.COOKIE_DOMAIN;
68
+ const trustedOrigins = cfg.trustedOrigins ||
69
+ (process.env.UI_ORIGIN || "http://localhost:3001")
70
+ .split(",")
71
+ .map((o) => o.trim())
72
+ .filter(Boolean);
73
+ // Default minPasswordLength: 12 — caller must explicitly override, not accidentally omit
74
+ const emailAndPassword = cfg.emailAndPassword
75
+ ? { minPasswordLength: 12, ...cfg.emailAndPassword }
76
+ : { enabled: true, minPasswordLength: 12 };
38
77
  return {
39
78
  database: pool,
40
- secret: BETTER_AUTH_SECRET,
41
- baseURL: BETTER_AUTH_URL,
42
- basePath: "/api/auth",
43
- socialProviders: {
44
- ...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
45
- ? { github: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET } }
46
- : {}),
47
- ...(process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET
48
- ? { discord: { clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET } }
49
- : {}),
50
- ...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
51
- ? { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET } }
52
- : {}),
53
- },
79
+ secret: secret || "",
80
+ baseURL,
81
+ basePath,
82
+ socialProviders: resolveSocialProviders(cfg),
54
83
  account: {
55
84
  accountLinking: {
56
85
  enabled: true,
57
- trustedProviders: ["github", "google"],
86
+ trustedProviders: cfg.trustedProviders ?? ["github", "google"],
58
87
  },
59
88
  },
60
89
  emailAndPassword: {
61
- enabled: true,
62
- minPasswordLength: 12,
90
+ ...emailAndPassword,
63
91
  sendResetPassword: async ({ user, url }) => {
64
92
  try {
65
93
  const emailClient = getEmailClient();
@@ -87,12 +115,20 @@ function authOptions(pool) {
87
115
  catch (error) {
88
116
  logger.error("Failed to run user creator:", error);
89
117
  }
118
+ if (cfg.onUserCreated) {
119
+ try {
120
+ await cfg.onUserCreated(user.id, user.name || user.email, user.email);
121
+ }
122
+ catch (error) {
123
+ logger.error("Failed to run onUserCreated callback:", error);
124
+ }
125
+ }
90
126
  if (user.emailVerified)
91
127
  return;
92
128
  try {
93
129
  await initVerificationSchema(pool);
94
130
  const { token } = await generateVerificationToken(pool, user.id);
95
- const verifyUrl = `${BETTER_AUTH_URL}/auth/verify?token=${token}`;
131
+ const verifyUrl = `${baseURL}${basePath}/verify?token=${token}`;
96
132
  const emailClient = getEmailClient();
97
133
  const template = verifyEmailTemplate(verifyUrl, user.email);
98
134
  await emailClient.send({
@@ -105,45 +141,30 @@ function authOptions(pool) {
105
141
  catch (error) {
106
142
  logger.error("Failed to send verification email:", error);
107
143
  }
108
- // Delegate personal tenant creation to the consumer
109
- if (_config?.onUserCreated) {
110
- try {
111
- await _config.onUserCreated(user.id, user.name || user.email, user.email);
112
- }
113
- catch (error) {
114
- logger.error("Failed to run onUserCreated callback:", error);
115
- }
116
- }
117
144
  },
118
145
  },
119
146
  },
120
147
  },
121
148
  session: {
122
- cookieCache: { enabled: true, maxAge: 5 * 60 },
149
+ cookieCache: { enabled: true, maxAge: cfg.sessionCacheMaxAge ?? 300 },
123
150
  },
124
151
  advanced: {
125
- cookiePrefix: "better-auth",
152
+ cookiePrefix: cfg.cookiePrefix || "better-auth",
126
153
  cookies: {
127
154
  session_token: {
128
- attributes: {
129
- domain: process.env.COOKIE_DOMAIN || ".wopr.bot",
130
- },
155
+ attributes: cookieDomain ? { domain: cookieDomain } : {},
131
156
  },
132
157
  },
133
158
  },
134
- plugins: [twoFactor()],
159
+ plugins: cfg.twoFactor !== false ? [twoFactor()] : [],
135
160
  rateLimit: {
136
161
  enabled: true,
137
- window: 60,
138
- max: 100,
139
- customRules: {
140
- "/sign-in/email": { window: 900, max: 5 },
141
- "/sign-up/email": { window: 3600, max: 10 },
142
- "/request-password-reset": { window: 3600, max: 3 },
143
- },
162
+ window: cfg.rateLimitWindow ?? 60,
163
+ max: cfg.rateLimitMax ?? 100,
164
+ customRules: { ...DEFAULT_RATE_LIMIT_RULES, ...cfg.rateLimitRules },
144
165
  storage: "memory",
145
166
  },
146
- trustedOrigins: (process.env.UI_ORIGIN || "http://localhost:3001").split(","),
167
+ trustedOrigins,
147
168
  };
148
169
  }
149
170
  /** Initialize Better Auth with the given config. Must be called before getAuth(). */
@@ -158,9 +179,11 @@ export async function runAuthMigrations() {
158
179
  if (!_config)
159
180
  throw new Error("BetterAuth not initialized — call initBetterAuth() first");
160
181
  const { getMigrations } = (await import("better-auth/db"));
161
- const { runMigrations } = await getMigrations(authOptions(_config.pool));
182
+ const { runMigrations } = await getMigrations(authOptions(_config));
162
183
  await runMigrations();
163
- await initTwoFactorSchema(_config.pool);
184
+ if (_config.twoFactor !== false) {
185
+ await initTwoFactorSchema(_config.pool);
186
+ }
164
187
  }
165
188
  let _auth = null;
166
189
  /**
@@ -171,7 +194,7 @@ export function getAuth() {
171
194
  if (!_auth) {
172
195
  if (!_config)
173
196
  throw new Error("BetterAuth not initialized — call initBetterAuth() first");
174
- _auth = betterAuth(authOptions(_config.pool));
197
+ _auth = betterAuth(authOptions(_config));
175
198
  }
176
199
  return _auth;
177
200
  }
@@ -184,3 +184,15 @@ export declare function validateTenantOwnership<T>(c: Context, resource: T | nul
184
184
  * @returns true if the user has access, false otherwise.
185
185
  */
186
186
  export declare function validateTenantAccess(userId: string, requestedTenantId: string | undefined, orgMemberRepo: IOrgMemberRepository): Promise<boolean>;
187
+ export type { IApiKeyRepository } from "./api-key-repository.js";
188
+ export { DrizzleApiKeyRepository } from "./api-key-repository.js";
189
+ export type { Auth, AuthRateLimitRule, BetterAuthConfig, OAuthProvider, } from "./better-auth.js";
190
+ export { getAuth, getEmailVerifier, initBetterAuth, resetAuth, resetUserCreator, runAuthMigrations, setAuth, } from "./better-auth.js";
191
+ export type { ILoginHistoryRepository, LoginHistoryEntry } from "./login-history-repository.js";
192
+ export { BetterAuthLoginHistoryRepository } from "./login-history-repository.js";
193
+ export type { SessionAuthEnv } from "./middleware.js";
194
+ export { dualAuth, sessionAuth } from "./middleware.js";
195
+ export type { IUserCreator } from "./user-creator.js";
196
+ export { createUserCreator } from "./user-creator.js";
197
+ export type { IUserRoleRepository } from "./user-role-repository.js";
198
+ export { DrizzleUserRoleRepository } from "./user-role-repository.js";
@@ -420,3 +420,10 @@ export async function validateTenantAccess(userId, requestedTenantId, orgMemberR
420
420
  const member = await orgMemberRepo.findMember(requestedTenantId, userId);
421
421
  return member !== null;
422
422
  }
423
+ export { DrizzleApiKeyRepository } from "./api-key-repository.js";
424
+ // Test utilities — do not call in production code
425
+ export { getAuth, getEmailVerifier, initBetterAuth, resetAuth, resetUserCreator, runAuthMigrations, setAuth, } from "./better-auth.js";
426
+ export { BetterAuthLoginHistoryRepository } from "./login-history-repository.js";
427
+ export { dualAuth, sessionAuth } from "./middleware.js";
428
+ export { createUserCreator } from "./user-creator.js";
429
+ export { DrizzleUserRoleRepository } from "./user-role-repository.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,16 +20,75 @@ import { passwordResetEmailTemplate, verifyEmailTemplate } from "../email/templa
20
20
  import { generateVerificationToken, initVerificationSchema, PgEmailVerifier } from "../email/verification.js";
21
21
  import { createUserCreator, type IUserCreator } from "./user-creator.js";
22
22
 
23
+ /** OAuth provider credentials. */
24
+ export interface OAuthProvider {
25
+ clientId: string;
26
+ clientSecret: string;
27
+ }
28
+
29
+ /** Rate limit rule for a specific auth endpoint. */
30
+ export interface AuthRateLimitRule {
31
+ window: number;
32
+ max: number;
33
+ }
34
+
23
35
  /** Configuration for initializing Better Auth in platform-core. */
24
36
  export interface BetterAuthConfig {
25
37
  pool: Pool;
26
38
  db: PlatformDb;
39
+
40
+ // --- Required ---
41
+ /** HMAC secret for session tokens. Falls back to BETTER_AUTH_SECRET env var. */
42
+ secret?: string;
43
+ /** Base URL for OAuth callbacks. Falls back to BETTER_AUTH_URL env var. */
44
+ baseURL?: string;
45
+
46
+ // --- Auth features ---
47
+ /** Route prefix. Default: "/api/auth" */
48
+ basePath?: string;
49
+ /** Email+password config. Default: enabled with 12-char min. */
50
+ emailAndPassword?: { enabled: boolean; minPasswordLength?: number };
51
+ /** OAuth providers. Default: reads GITHUB/DISCORD/GOOGLE env vars. */
52
+ socialProviders?: {
53
+ github?: OAuthProvider;
54
+ discord?: OAuthProvider;
55
+ google?: OAuthProvider;
56
+ };
57
+ /** Trusted providers for account linking. Default: ["github", "google"] */
58
+ trustedProviders?: string[];
59
+ /** Enable 2FA plugin. Default: true */
60
+ twoFactor?: boolean;
61
+
62
+ // --- Session & cookies ---
63
+ /** Cookie cache max age in seconds. Default: 300 (5 min) */
64
+ sessionCacheMaxAge?: number;
65
+ /** Cookie prefix. Default: "better-auth" */
66
+ cookiePrefix?: string;
67
+ /** Cookie domain (e.g., ".wopr.bot"). Falls back to COOKIE_DOMAIN env var. */
68
+ cookieDomain?: string;
69
+
70
+ // --- Rate limiting ---
71
+ /** Global rate limit window in seconds. Default: 60 */
72
+ rateLimitWindow?: number;
73
+ /** Global rate limit max requests. Default: 100 */
74
+ rateLimitMax?: number;
75
+ /** Per-endpoint rate limit overrides. Default: sign-in/sign-up/reset limits. */
76
+ rateLimitRules?: Record<string, AuthRateLimitRule>;
77
+
78
+ // --- Origins ---
79
+ /** Trusted origins for CORS. Falls back to UI_ORIGIN env var. */
80
+ trustedOrigins?: string[];
81
+
82
+ // --- Lifecycle hooks ---
27
83
  /** Called after a new user signs up (e.g., create personal tenant). */
28
84
  onUserCreated?: (userId: string, userName: string, email: string) => Promise<void>;
29
85
  }
30
86
 
31
- const BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET || "";
32
- const BETTER_AUTH_URL = process.env.BETTER_AUTH_URL || "http://localhost:3100";
87
+ const DEFAULT_RATE_LIMIT_RULES: Record<string, AuthRateLimitRule> = {
88
+ "/sign-in/email": { window: 900, max: 5 },
89
+ "/sign-up/email": { window: 3600, max: 10 },
90
+ "/request-password-reset": { window: 3600, max: 3 },
91
+ };
33
92
 
34
93
  let _config: BetterAuthConfig | null = null;
35
94
  let _userCreator: IUserCreator | null = null;
@@ -47,32 +106,59 @@ async function getUserCreator(): Promise<IUserCreator> {
47
106
  return _userCreatorPromise;
48
107
  }
49
108
 
50
- function authOptions(pool: Pool): BetterAuthOptions {
109
+ /** Resolve OAuth providers from config or env vars. */
110
+ function resolveSocialProviders(cfg: BetterAuthConfig): BetterAuthOptions["socialProviders"] {
111
+ if (cfg.socialProviders) return cfg.socialProviders;
112
+ return {
113
+ ...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
114
+ ? { github: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET } }
115
+ : {}),
116
+ ...(process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET
117
+ ? { discord: { clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET } }
118
+ : {}),
119
+ ...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
120
+ ? { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET } }
121
+ : {}),
122
+ };
123
+ }
124
+
125
+ function authOptions(cfg: BetterAuthConfig): BetterAuthOptions {
126
+ const pool = cfg.pool;
127
+ const secret = cfg.secret || process.env.BETTER_AUTH_SECRET;
128
+ if (!secret) {
129
+ if (process.env.NODE_ENV === "production") {
130
+ throw new Error("BETTER_AUTH_SECRET is required in production");
131
+ }
132
+ logger.warn("BetterAuth secret not configured — sessions may be insecure");
133
+ }
134
+ const baseURL = cfg.baseURL || process.env.BETTER_AUTH_URL || "http://localhost:3100";
135
+ const basePath = cfg.basePath || "/api/auth";
136
+ const cookieDomain = cfg.cookieDomain || process.env.COOKIE_DOMAIN;
137
+ const trustedOrigins =
138
+ cfg.trustedOrigins ||
139
+ (process.env.UI_ORIGIN || "http://localhost:3001")
140
+ .split(",")
141
+ .map((o) => o.trim())
142
+ .filter(Boolean);
143
+ // Default minPasswordLength: 12 — caller must explicitly override, not accidentally omit
144
+ const emailAndPassword = cfg.emailAndPassword
145
+ ? { minPasswordLength: 12, ...cfg.emailAndPassword }
146
+ : { enabled: true, minPasswordLength: 12 };
147
+
51
148
  return {
52
149
  database: pool,
53
- secret: BETTER_AUTH_SECRET,
54
- baseURL: BETTER_AUTH_URL,
55
- basePath: "/api/auth",
56
- socialProviders: {
57
- ...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
58
- ? { github: { clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET } }
59
- : {}),
60
- ...(process.env.DISCORD_CLIENT_ID && process.env.DISCORD_CLIENT_SECRET
61
- ? { discord: { clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET } }
62
- : {}),
63
- ...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
64
- ? { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET } }
65
- : {}),
66
- },
150
+ secret: secret || "",
151
+ baseURL,
152
+ basePath,
153
+ socialProviders: resolveSocialProviders(cfg),
67
154
  account: {
68
155
  accountLinking: {
69
156
  enabled: true,
70
- trustedProviders: ["github", "google"],
157
+ trustedProviders: cfg.trustedProviders ?? ["github", "google"],
71
158
  },
72
159
  },
73
160
  emailAndPassword: {
74
- enabled: true,
75
- minPasswordLength: 12,
161
+ ...emailAndPassword,
76
162
  sendResetPassword: async ({ user, url }) => {
77
163
  try {
78
164
  const emailClient = getEmailClient();
@@ -99,12 +185,20 @@ function authOptions(pool: Pool): BetterAuthOptions {
99
185
  logger.error("Failed to run user creator:", error);
100
186
  }
101
187
 
188
+ if (cfg.onUserCreated) {
189
+ try {
190
+ await cfg.onUserCreated(user.id, user.name || user.email, user.email);
191
+ } catch (error) {
192
+ logger.error("Failed to run onUserCreated callback:", error);
193
+ }
194
+ }
195
+
102
196
  if (user.emailVerified) return;
103
197
 
104
198
  try {
105
199
  await initVerificationSchema(pool);
106
200
  const { token } = await generateVerificationToken(pool, user.id);
107
- const verifyUrl = `${BETTER_AUTH_URL}/auth/verify?token=${token}`;
201
+ const verifyUrl = `${baseURL}${basePath}/verify?token=${token}`;
108
202
  const emailClient = getEmailClient();
109
203
  const template = verifyEmailTemplate(verifyUrl, user.email);
110
204
  await emailClient.send({
@@ -116,45 +210,30 @@ function authOptions(pool: Pool): BetterAuthOptions {
116
210
  } catch (error) {
117
211
  logger.error("Failed to send verification email:", error);
118
212
  }
119
-
120
- // Delegate personal tenant creation to the consumer
121
- if (_config?.onUserCreated) {
122
- try {
123
- await _config.onUserCreated(user.id, user.name || user.email, user.email);
124
- } catch (error) {
125
- logger.error("Failed to run onUserCreated callback:", error);
126
- }
127
- }
128
213
  },
129
214
  },
130
215
  },
131
216
  },
132
217
  session: {
133
- cookieCache: { enabled: true, maxAge: 5 * 60 },
218
+ cookieCache: { enabled: true, maxAge: cfg.sessionCacheMaxAge ?? 300 },
134
219
  },
135
220
  advanced: {
136
- cookiePrefix: "better-auth",
221
+ cookiePrefix: cfg.cookiePrefix || "better-auth",
137
222
  cookies: {
138
223
  session_token: {
139
- attributes: {
140
- domain: process.env.COOKIE_DOMAIN || ".wopr.bot",
141
- },
224
+ attributes: cookieDomain ? { domain: cookieDomain } : {},
142
225
  },
143
226
  },
144
227
  },
145
- plugins: [twoFactor()],
228
+ plugins: cfg.twoFactor !== false ? [twoFactor()] : [],
146
229
  rateLimit: {
147
230
  enabled: true,
148
- window: 60,
149
- max: 100,
150
- customRules: {
151
- "/sign-in/email": { window: 900, max: 5 },
152
- "/sign-up/email": { window: 3600, max: 10 },
153
- "/request-password-reset": { window: 3600, max: 3 },
154
- },
231
+ window: cfg.rateLimitWindow ?? 60,
232
+ max: cfg.rateLimitMax ?? 100,
233
+ customRules: { ...DEFAULT_RATE_LIMIT_RULES, ...cfg.rateLimitRules },
155
234
  storage: "memory",
156
235
  },
157
- trustedOrigins: (process.env.UI_ORIGIN || "http://localhost:3001").split(","),
236
+ trustedOrigins,
158
237
  };
159
238
  }
160
239
 
@@ -174,9 +253,11 @@ export async function runAuthMigrations(): Promise<void> {
174
253
  if (!_config) throw new Error("BetterAuth not initialized — call initBetterAuth() first");
175
254
  type DbModule = { getMigrations: (opts: BetterAuthOptions) => Promise<{ runMigrations: () => Promise<void> }> };
176
255
  const { getMigrations } = (await import("better-auth/db")) as unknown as DbModule;
177
- const { runMigrations } = await getMigrations(authOptions(_config.pool));
256
+ const { runMigrations } = await getMigrations(authOptions(_config));
178
257
  await runMigrations();
179
- await initTwoFactorSchema(_config.pool);
258
+ if (_config.twoFactor !== false) {
259
+ await initTwoFactorSchema(_config.pool);
260
+ }
180
261
  }
181
262
 
182
263
  let _auth: Auth | null = null;
@@ -188,7 +269,7 @@ let _auth: Auth | null = null;
188
269
  export function getAuth(): Auth {
189
270
  if (!_auth) {
190
271
  if (!_config) throw new Error("BetterAuth not initialized — call initBetterAuth() first");
191
- _auth = betterAuth(authOptions(_config.pool));
272
+ _auth = betterAuth(authOptions(_config));
192
273
  }
193
274
  return _auth;
194
275
  }
package/src/auth/index.ts CHANGED
@@ -518,3 +518,34 @@ export async function validateTenantAccess(
518
518
  const member = await orgMemberRepo.findMember(requestedTenantId, userId);
519
519
  return member !== null;
520
520
  }
521
+
522
+ // ---------------------------------------------------------------------------
523
+ // Re-exports — Better Auth factory, middleware, and repositories
524
+ // ---------------------------------------------------------------------------
525
+
526
+ export type { IApiKeyRepository } from "./api-key-repository.js";
527
+ export { DrizzleApiKeyRepository } from "./api-key-repository.js";
528
+ export type {
529
+ Auth,
530
+ AuthRateLimitRule,
531
+ BetterAuthConfig,
532
+ OAuthProvider,
533
+ } from "./better-auth.js";
534
+ // Test utilities — do not call in production code
535
+ export {
536
+ getAuth,
537
+ getEmailVerifier,
538
+ initBetterAuth,
539
+ resetAuth,
540
+ resetUserCreator,
541
+ runAuthMigrations,
542
+ setAuth,
543
+ } from "./better-auth.js";
544
+ export type { ILoginHistoryRepository, LoginHistoryEntry } from "./login-history-repository.js";
545
+ export { BetterAuthLoginHistoryRepository } from "./login-history-repository.js";
546
+ export type { SessionAuthEnv } from "./middleware.js";
547
+ export { dualAuth, sessionAuth } from "./middleware.js";
548
+ export type { IUserCreator } from "./user-creator.js";
549
+ export { createUserCreator } from "./user-creator.js";
550
+ export type { IUserRoleRepository } from "./user-role-repository.js";
551
+ export { DrizzleUserRoleRepository } from "./user-role-repository.js";