@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.
Files changed (40) hide show
  1. package/README.md +4 -1
  2. package/dist/index.d.mts +345 -19928
  3. package/dist/index.d.ts +345 -19928
  4. package/dist/index.js +411 -857
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +415 -837
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +35 -12
  9. package/.turbo/turbo-build.log +0 -78
  10. package/ARCHITECTURE.md +0 -176
  11. package/CHANGELOG.md +0 -325
  12. package/IMPLEMENTATION_SUMMARY.md +0 -192
  13. package/examples/basic-usage.ts +0 -107
  14. package/objectstack.config.ts +0 -24
  15. package/src/auth-manager.test.ts +0 -758
  16. package/src/auth-manager.ts +0 -338
  17. package/src/auth-plugin.test.ts +0 -443
  18. package/src/auth-plugin.ts +0 -292
  19. package/src/auth-schema-config.ts +0 -339
  20. package/src/index.ts +0 -16
  21. package/src/objectql-adapter.test.ts +0 -281
  22. package/src/objectql-adapter.ts +0 -279
  23. package/src/objects/auth-account.object.ts +0 -7
  24. package/src/objects/auth-session.object.ts +0 -7
  25. package/src/objects/auth-user.object.ts +0 -7
  26. package/src/objects/auth-verification.object.ts +0 -7
  27. package/src/objects/index.ts +0 -40
  28. package/src/objects/sys-account.object.ts +0 -111
  29. package/src/objects/sys-api-key.object.ts +0 -104
  30. package/src/objects/sys-invitation.object.ts +0 -93
  31. package/src/objects/sys-member.object.ts +0 -68
  32. package/src/objects/sys-organization.object.ts +0 -82
  33. package/src/objects/sys-session.object.ts +0 -84
  34. package/src/objects/sys-team-member.object.ts +0 -61
  35. package/src/objects/sys-team.object.ts +0 -69
  36. package/src/objects/sys-two-factor.object.ts +0 -73
  37. package/src/objects/sys-user-preference.object.ts +0 -82
  38. package/src/objects/sys-user.object.ts +0 -91
  39. package/src/objects/sys-verification.object.ts +0 -75
  40. package/tsconfig.json +0 -18
@@ -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
- }