@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.
- package/dist/auth/better-auth.d.ts +45 -0
- package/dist/auth/better-auth.js +70 -47
- package/dist/auth/index.d.ts +12 -0
- package/dist/auth/index.js +7 -0
- package/package.json +1 -1
- package/src/auth/better-auth.ts +128 -47
- package/src/auth/index.ts +31 -0
|
@@ -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
|
}
|
package/dist/auth/better-auth.js
CHANGED
|
@@ -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
|
|
20
|
-
|
|
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
|
-
|
|
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:
|
|
41
|
-
baseURL
|
|
42
|
-
basePath
|
|
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
|
-
|
|
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 = `${
|
|
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:
|
|
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
|
|
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
|
|
182
|
+
const { runMigrations } = await getMigrations(authOptions(_config));
|
|
162
183
|
await runMigrations();
|
|
163
|
-
|
|
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
|
|
197
|
+
_auth = betterAuth(authOptions(_config));
|
|
175
198
|
}
|
|
176
199
|
return _auth;
|
|
177
200
|
}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/auth/index.js
CHANGED
|
@@ -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
package/src/auth/better-auth.ts
CHANGED
|
@@ -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
|
|
32
|
-
|
|
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
|
-
|
|
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:
|
|
54
|
-
baseURL
|
|
55
|
-
basePath
|
|
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
|
-
|
|
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 = `${
|
|
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:
|
|
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
|
|
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
|
|
256
|
+
const { runMigrations } = await getMigrations(authOptions(_config));
|
|
178
257
|
await runMigrations();
|
|
179
|
-
|
|
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
|
|
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";
|