@objectstack/plugin-auth 4.0.3 → 4.0.5
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/README.md +4 -1
- package/dist/index.d.mts +345 -19928
- package/dist/index.d.ts +345 -19928
- package/dist/index.js +411 -857
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +415 -837
- package/dist/index.mjs.map +1 -1
- package/package.json +35 -12
- package/.turbo/turbo-build.log +0 -78
- package/ARCHITECTURE.md +0 -176
- package/CHANGELOG.md +0 -325
- package/IMPLEMENTATION_SUMMARY.md +0 -192
- package/examples/basic-usage.ts +0 -107
- package/objectstack.config.ts +0 -24
- package/src/auth-manager.test.ts +0 -758
- package/src/auth-manager.ts +0 -338
- package/src/auth-plugin.test.ts +0 -443
- package/src/auth-plugin.ts +0 -292
- package/src/auth-schema-config.ts +0 -339
- package/src/index.ts +0 -16
- package/src/objectql-adapter.test.ts +0 -281
- package/src/objectql-adapter.ts +0 -279
- package/src/objects/auth-account.object.ts +0 -7
- package/src/objects/auth-session.object.ts +0 -7
- package/src/objects/auth-user.object.ts +0 -7
- package/src/objects/auth-verification.object.ts +0 -7
- package/src/objects/index.ts +0 -40
- package/src/objects/sys-account.object.ts +0 -111
- package/src/objects/sys-api-key.object.ts +0 -104
- package/src/objects/sys-invitation.object.ts +0 -93
- package/src/objects/sys-member.object.ts +0 -68
- package/src/objects/sys-organization.object.ts +0 -82
- package/src/objects/sys-session.object.ts +0 -84
- package/src/objects/sys-team-member.object.ts +0 -61
- package/src/objects/sys-team.object.ts +0 -69
- package/src/objects/sys-two-factor.object.ts +0 -73
- package/src/objects/sys-user-preference.object.ts +0 -82
- package/src/objects/sys-user.object.ts +0 -91
- package/src/objects/sys-verification.object.ts +0 -75
- package/tsconfig.json +0 -18
package/src/auth-manager.ts
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { betterAuth } from 'better-auth';
|
|
4
|
-
import type { Auth, BetterAuthOptions } from 'better-auth';
|
|
5
|
-
import { organization } from 'better-auth/plugins/organization';
|
|
6
|
-
import { twoFactor } from 'better-auth/plugins/two-factor';
|
|
7
|
-
import { magicLink } from 'better-auth/plugins/magic-link';
|
|
8
|
-
import type { AuthConfig } from '@objectstack/spec/system';
|
|
9
|
-
import type { IDataEngine } from '@objectstack/core';
|
|
10
|
-
import { createObjectQLAdapterFactory } from './objectql-adapter.js';
|
|
11
|
-
import {
|
|
12
|
-
AUTH_USER_CONFIG,
|
|
13
|
-
AUTH_SESSION_CONFIG,
|
|
14
|
-
AUTH_ACCOUNT_CONFIG,
|
|
15
|
-
AUTH_VERIFICATION_CONFIG,
|
|
16
|
-
buildOrganizationPluginSchema,
|
|
17
|
-
buildTwoFactorPluginSchema,
|
|
18
|
-
} from './auth-schema-config.js';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Extended options for AuthManager
|
|
22
|
-
*/
|
|
23
|
-
export interface AuthManagerOptions extends Partial<AuthConfig> {
|
|
24
|
-
/**
|
|
25
|
-
* Better-Auth instance (for advanced use cases)
|
|
26
|
-
* If not provided, one will be created from config
|
|
27
|
-
*/
|
|
28
|
-
authInstance?: Auth<any>;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* ObjectQL Data Engine instance
|
|
32
|
-
* Required for database operations using ObjectQL instead of third-party ORMs
|
|
33
|
-
*/
|
|
34
|
-
dataEngine?: IDataEngine;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Base path for auth routes
|
|
38
|
-
* Forwarded to better-auth's basePath option so it can match incoming
|
|
39
|
-
* request URLs without manual path rewriting.
|
|
40
|
-
* @default '/api/v1/auth'
|
|
41
|
-
*/
|
|
42
|
-
basePath?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Authentication Manager
|
|
47
|
-
*
|
|
48
|
-
* Wraps better-auth and provides authentication services for ObjectStack.
|
|
49
|
-
* Supports multiple authentication methods:
|
|
50
|
-
* - Email/password
|
|
51
|
-
* - OAuth providers (Google, GitHub, etc.)
|
|
52
|
-
* - Magic links
|
|
53
|
-
* - Two-factor authentication
|
|
54
|
-
* - Passkeys
|
|
55
|
-
* - Organization/teams
|
|
56
|
-
*/
|
|
57
|
-
export class AuthManager {
|
|
58
|
-
private auth: Auth<any> | null = null;
|
|
59
|
-
private config: AuthManagerOptions;
|
|
60
|
-
|
|
61
|
-
constructor(config: AuthManagerOptions) {
|
|
62
|
-
this.config = config;
|
|
63
|
-
|
|
64
|
-
// Use provided auth instance
|
|
65
|
-
if (config.authInstance) {
|
|
66
|
-
this.auth = config.authInstance;
|
|
67
|
-
}
|
|
68
|
-
// Don't create auth instance automatically to avoid database initialization errors
|
|
69
|
-
// It will be created lazily when needed
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get or create the better-auth instance (lazy initialization)
|
|
74
|
-
*/
|
|
75
|
-
private getOrCreateAuth(): Auth<any> {
|
|
76
|
-
if (!this.auth) {
|
|
77
|
-
this.auth = this.createAuthInstance();
|
|
78
|
-
}
|
|
79
|
-
return this.auth;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Create a better-auth instance from configuration
|
|
84
|
-
*/
|
|
85
|
-
private createAuthInstance(): Auth<any> {
|
|
86
|
-
const betterAuthConfig: BetterAuthOptions = {
|
|
87
|
-
// Base configuration
|
|
88
|
-
secret: this.config.secret || this.generateSecret(),
|
|
89
|
-
baseURL: this.config.baseUrl || 'http://localhost:3000',
|
|
90
|
-
basePath: this.config.basePath || '/api/v1/auth',
|
|
91
|
-
|
|
92
|
-
// Database adapter configuration
|
|
93
|
-
database: this.createDatabaseConfig(),
|
|
94
|
-
|
|
95
|
-
// Model/field mapping: camelCase (better-auth) → snake_case (ObjectStack)
|
|
96
|
-
// These declarations tell better-auth the actual table/column names used
|
|
97
|
-
// by ObjectStack's protocol layer, enabling automatic transformation via
|
|
98
|
-
// createAdapterFactory.
|
|
99
|
-
user: {
|
|
100
|
-
...AUTH_USER_CONFIG,
|
|
101
|
-
},
|
|
102
|
-
account: {
|
|
103
|
-
...AUTH_ACCOUNT_CONFIG,
|
|
104
|
-
},
|
|
105
|
-
verification: {
|
|
106
|
-
...AUTH_VERIFICATION_CONFIG,
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
// Social / OAuth providers
|
|
110
|
-
...(this.config.socialProviders ? { socialProviders: this.config.socialProviders as any } : {}),
|
|
111
|
-
|
|
112
|
-
// Email and password configuration
|
|
113
|
-
emailAndPassword: {
|
|
114
|
-
enabled: this.config.emailAndPassword?.enabled ?? true,
|
|
115
|
-
...(this.config.emailAndPassword?.disableSignUp != null
|
|
116
|
-
? { disableSignUp: this.config.emailAndPassword.disableSignUp } : {}),
|
|
117
|
-
...(this.config.emailAndPassword?.requireEmailVerification != null
|
|
118
|
-
? { requireEmailVerification: this.config.emailAndPassword.requireEmailVerification } : {}),
|
|
119
|
-
...(this.config.emailAndPassword?.minPasswordLength != null
|
|
120
|
-
? { minPasswordLength: this.config.emailAndPassword.minPasswordLength } : {}),
|
|
121
|
-
...(this.config.emailAndPassword?.maxPasswordLength != null
|
|
122
|
-
? { maxPasswordLength: this.config.emailAndPassword.maxPasswordLength } : {}),
|
|
123
|
-
...(this.config.emailAndPassword?.resetPasswordTokenExpiresIn != null
|
|
124
|
-
? { resetPasswordTokenExpiresIn: this.config.emailAndPassword.resetPasswordTokenExpiresIn } : {}),
|
|
125
|
-
...(this.config.emailAndPassword?.autoSignIn != null
|
|
126
|
-
? { autoSignIn: this.config.emailAndPassword.autoSignIn } : {}),
|
|
127
|
-
...(this.config.emailAndPassword?.revokeSessionsOnPasswordReset != null
|
|
128
|
-
? { revokeSessionsOnPasswordReset: this.config.emailAndPassword.revokeSessionsOnPasswordReset } : {}),
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
// Email verification
|
|
132
|
-
...(this.config.emailVerification ? {
|
|
133
|
-
emailVerification: {
|
|
134
|
-
...(this.config.emailVerification.sendOnSignUp != null
|
|
135
|
-
? { sendOnSignUp: this.config.emailVerification.sendOnSignUp } : {}),
|
|
136
|
-
...(this.config.emailVerification.sendOnSignIn != null
|
|
137
|
-
? { sendOnSignIn: this.config.emailVerification.sendOnSignIn } : {}),
|
|
138
|
-
...(this.config.emailVerification.autoSignInAfterVerification != null
|
|
139
|
-
? { autoSignInAfterVerification: this.config.emailVerification.autoSignInAfterVerification } : {}),
|
|
140
|
-
...(this.config.emailVerification.expiresIn != null
|
|
141
|
-
? { expiresIn: this.config.emailVerification.expiresIn } : {}),
|
|
142
|
-
},
|
|
143
|
-
} : {}),
|
|
144
|
-
|
|
145
|
-
// Session configuration
|
|
146
|
-
session: {
|
|
147
|
-
...AUTH_SESSION_CONFIG,
|
|
148
|
-
expiresIn: this.config.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days default
|
|
149
|
-
updateAge: this.config.session?.updateAge || 60 * 60 * 24, // 1 day default
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
// better-auth plugins — registered based on AuthPluginConfig flags
|
|
153
|
-
plugins: this.buildPluginList(),
|
|
154
|
-
|
|
155
|
-
// Trusted origins for CSRF protection (supports wildcards like "https://*.example.com")
|
|
156
|
-
...(this.config.trustedOrigins?.length ? { trustedOrigins: this.config.trustedOrigins } : {}),
|
|
157
|
-
|
|
158
|
-
// Advanced options (cross-subdomain cookies, secure cookies, CSRF, etc.)
|
|
159
|
-
...(this.config.advanced ? {
|
|
160
|
-
advanced: {
|
|
161
|
-
...(this.config.advanced.crossSubDomainCookies
|
|
162
|
-
? { crossSubDomainCookies: this.config.advanced.crossSubDomainCookies } : {}),
|
|
163
|
-
...(this.config.advanced.useSecureCookies != null
|
|
164
|
-
? { useSecureCookies: this.config.advanced.useSecureCookies } : {}),
|
|
165
|
-
...(this.config.advanced.disableCSRFCheck != null
|
|
166
|
-
? { disableCSRFCheck: this.config.advanced.disableCSRFCheck } : {}),
|
|
167
|
-
...(this.config.advanced.cookiePrefix != null
|
|
168
|
-
? { cookiePrefix: this.config.advanced.cookiePrefix } : {}),
|
|
169
|
-
},
|
|
170
|
-
} : {}),
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
return betterAuth(betterAuthConfig);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Build the list of better-auth plugins based on AuthPluginConfig flags.
|
|
178
|
-
*
|
|
179
|
-
* Each plugin that introduces its own database tables is configured with
|
|
180
|
-
* a `schema` option containing the appropriate snake_case field mappings,
|
|
181
|
-
* so that `createAdapterFactory` transforms them automatically.
|
|
182
|
-
*/
|
|
183
|
-
private buildPluginList(): any[] {
|
|
184
|
-
const pluginConfig = this.config.plugins;
|
|
185
|
-
const plugins: any[] = [];
|
|
186
|
-
|
|
187
|
-
if (pluginConfig?.organization) {
|
|
188
|
-
plugins.push(organization({
|
|
189
|
-
schema: buildOrganizationPluginSchema(),
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (pluginConfig?.twoFactor) {
|
|
194
|
-
plugins.push(twoFactor({
|
|
195
|
-
schema: buildTwoFactorPluginSchema(),
|
|
196
|
-
}));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (pluginConfig?.magicLink) {
|
|
200
|
-
// magic-link reuses the `verification` table — no extra schema mapping needed.
|
|
201
|
-
// The sendMagicLink callback must be provided by the application at a higher level.
|
|
202
|
-
// Here we provide a no-op default that logs a warning; real applications should
|
|
203
|
-
// override this via AuthManagerOptions or a config extension point.
|
|
204
|
-
plugins.push(magicLink({
|
|
205
|
-
sendMagicLink: async ({ email, url }) => {
|
|
206
|
-
console.warn(
|
|
207
|
-
`[AuthManager] Magic-link requested for ${email} but no sendMagicLink handler configured. URL: ${url}`,
|
|
208
|
-
);
|
|
209
|
-
},
|
|
210
|
-
}));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return plugins;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Create database configuration using ObjectQL adapter
|
|
218
|
-
*
|
|
219
|
-
* better-auth resolves the `database` option as follows:
|
|
220
|
-
* - `undefined` → in-memory adapter
|
|
221
|
-
* - `typeof fn === "function"` → treated as `DBAdapterInstance`, called with `(options)`
|
|
222
|
-
* - otherwise → forwarded to Kysely adapter factory (pool/dialect)
|
|
223
|
-
*
|
|
224
|
-
* A raw `CustomAdapter` object would fall into the third branch and fail
|
|
225
|
-
* silently. We therefore wrap the ObjectQL adapter in a factory function
|
|
226
|
-
* so it is correctly recognised as a `DBAdapterInstance`.
|
|
227
|
-
*/
|
|
228
|
-
private createDatabaseConfig(): any {
|
|
229
|
-
// Use ObjectQL adapter factory if dataEngine is provided
|
|
230
|
-
if (this.config.dataEngine) {
|
|
231
|
-
// createObjectQLAdapterFactory returns an AdapterFactory
|
|
232
|
-
// (options => DBAdapter) which better-auth invokes via getBaseAdapter().
|
|
233
|
-
// The factory is created by better-auth's createAdapterFactory and
|
|
234
|
-
// automatically applies modelName/fields transformations declared in
|
|
235
|
-
// the betterAuth config above.
|
|
236
|
-
return createObjectQLAdapterFactory(this.config.dataEngine);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Fallback warning if no dataEngine is provided
|
|
240
|
-
console.warn(
|
|
241
|
-
'⚠️ WARNING: No dataEngine provided to AuthManager! ' +
|
|
242
|
-
'Using in-memory storage. This is NOT suitable for production. ' +
|
|
243
|
-
'Please provide a dataEngine instance (e.g., ObjectQL) in AuthManagerOptions.'
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
// Return a minimal in-memory configuration as fallback
|
|
247
|
-
// This allows the system to work in development/testing without a real database
|
|
248
|
-
return undefined; // better-auth will use its default in-memory adapter
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Generate a secure secret if not provided
|
|
253
|
-
*/
|
|
254
|
-
private generateSecret(): string {
|
|
255
|
-
const envSecret = process.env.AUTH_SECRET;
|
|
256
|
-
|
|
257
|
-
if (!envSecret) {
|
|
258
|
-
// In production, a secret MUST be provided
|
|
259
|
-
// For development/testing, we'll use a fallback but warn about it
|
|
260
|
-
const fallbackSecret = 'dev-secret-' + Date.now();
|
|
261
|
-
|
|
262
|
-
console.warn(
|
|
263
|
-
'⚠️ WARNING: No AUTH_SECRET environment variable set! ' +
|
|
264
|
-
'Using a temporary development secret. ' +
|
|
265
|
-
'This is NOT secure for production use. ' +
|
|
266
|
-
'Please set AUTH_SECRET in your environment variables.'
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
return fallbackSecret;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return envSecret;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Update the base URL at runtime.
|
|
277
|
-
*
|
|
278
|
-
* This **must** be called before the first request triggers lazy
|
|
279
|
-
* initialisation of the better-auth instance — typically from a
|
|
280
|
-
* `kernel:ready` hook where the actual server port is known.
|
|
281
|
-
*
|
|
282
|
-
* If the auth instance has already been created this is a no-op and
|
|
283
|
-
* a warning is emitted.
|
|
284
|
-
*/
|
|
285
|
-
setRuntimeBaseUrl(url: string): void {
|
|
286
|
-
if (this.auth) {
|
|
287
|
-
console.warn(
|
|
288
|
-
'[AuthManager] setRuntimeBaseUrl() called after the auth instance was already created — ignoring. ' +
|
|
289
|
-
'Ensure this method is called before the first request.',
|
|
290
|
-
);
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
this.config = { ...this.config, baseUrl: url };
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Get the underlying better-auth instance
|
|
298
|
-
* Useful for advanced use cases
|
|
299
|
-
*/
|
|
300
|
-
getAuthInstance(): Auth<any> {
|
|
301
|
-
return this.getOrCreateAuth();
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Handle an authentication request
|
|
306
|
-
* Forwards the request directly to better-auth's universal handler
|
|
307
|
-
*
|
|
308
|
-
* better-auth catches internal errors (database / adapter / ORM) and
|
|
309
|
-
* returns a 500 Response instead of throwing. We therefore inspect the
|
|
310
|
-
* response status and log server errors so they are not silently swallowed.
|
|
311
|
-
*
|
|
312
|
-
* @param request - Web standard Request object
|
|
313
|
-
* @returns Web standard Response object
|
|
314
|
-
*/
|
|
315
|
-
async handleRequest(request: Request): Promise<Response> {
|
|
316
|
-
const auth = this.getOrCreateAuth();
|
|
317
|
-
const response = await auth.handler(request);
|
|
318
|
-
|
|
319
|
-
if (response.status >= 500) {
|
|
320
|
-
try {
|
|
321
|
-
const body = await response.clone().text();
|
|
322
|
-
console.error('[AuthManager] better-auth returned error:', response.status, body);
|
|
323
|
-
} catch {
|
|
324
|
-
console.error('[AuthManager] better-auth returned error:', response.status, '(unable to read body)');
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return response;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Get the better-auth API for programmatic access
|
|
333
|
-
* Use this for server-side operations (e.g., creating users, checking sessions)
|
|
334
|
-
*/
|
|
335
|
-
get api() {
|
|
336
|
-
return this.getOrCreateAuth().api;
|
|
337
|
-
}
|
|
338
|
-
}
|