@qwickapps/server 1.3.0 → 1.3.1

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 (132) hide show
  1. package/README.md +154 -0
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +30 -2
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/plugin-registry.d.ts +36 -0
  6. package/dist/core/plugin-registry.d.ts.map +1 -1
  7. package/dist/core/plugin-registry.js +26 -0
  8. package/dist/core/plugin-registry.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/plugins/auth/adapters/index.d.ts +1 -0
  14. package/dist/plugins/auth/adapters/index.d.ts.map +1 -1
  15. package/dist/plugins/auth/adapters/index.js +1 -0
  16. package/dist/plugins/auth/adapters/index.js.map +1 -1
  17. package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -1
  18. package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -1
  19. package/dist/plugins/auth/adapters/supertokens-adapter.d.ts +18 -0
  20. package/dist/plugins/auth/adapters/supertokens-adapter.d.ts.map +1 -0
  21. package/dist/plugins/auth/adapters/supertokens-adapter.js +267 -0
  22. package/dist/plugins/auth/adapters/supertokens-adapter.js.map +1 -0
  23. package/dist/plugins/auth/env-config.d.ts +88 -0
  24. package/dist/plugins/auth/env-config.d.ts.map +1 -0
  25. package/dist/plugins/auth/env-config.js +489 -0
  26. package/dist/plugins/auth/env-config.js.map +1 -0
  27. package/dist/plugins/auth/index.d.ts +3 -1
  28. package/dist/plugins/auth/index.d.ts.map +1 -1
  29. package/dist/plugins/auth/index.js +3 -0
  30. package/dist/plugins/auth/index.js.map +1 -1
  31. package/dist/plugins/auth/supertokens-adapter.test.d.ts +10 -0
  32. package/dist/plugins/auth/supertokens-adapter.test.d.ts.map +1 -0
  33. package/dist/plugins/auth/supertokens-adapter.test.js +486 -0
  34. package/dist/plugins/auth/supertokens-adapter.test.js.map +1 -0
  35. package/dist/plugins/auth/types.d.ts +70 -0
  36. package/dist/plugins/auth/types.d.ts.map +1 -1
  37. package/dist/plugins/auth/types.js.map +1 -1
  38. package/dist/plugins/cache-plugin.test.js +3 -0
  39. package/dist/plugins/cache-plugin.test.js.map +1 -1
  40. package/dist/plugins/index.d.ts +4 -2
  41. package/dist/plugins/index.d.ts.map +1 -1
  42. package/dist/plugins/index.js +3 -1
  43. package/dist/plugins/index.js.map +1 -1
  44. package/dist/plugins/postgres-plugin.test.js +3 -0
  45. package/dist/plugins/postgres-plugin.test.js.map +1 -1
  46. package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts +7 -0
  47. package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts.map +1 -0
  48. package/dist/plugins/preferences/__tests__/deep-merge.test.js +215 -0
  49. package/dist/plugins/preferences/__tests__/deep-merge.test.js.map +1 -0
  50. package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts +7 -0
  51. package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts.map +1 -0
  52. package/dist/plugins/preferences/__tests__/preferences-plugin.test.js +265 -0
  53. package/dist/plugins/preferences/__tests__/preferences-plugin.test.js.map +1 -0
  54. package/dist/plugins/preferences/index.d.ts +12 -0
  55. package/dist/plugins/preferences/index.d.ts.map +1 -0
  56. package/dist/plugins/preferences/index.js +13 -0
  57. package/dist/plugins/preferences/index.js.map +1 -0
  58. package/dist/plugins/preferences/preferences-plugin.d.ts +39 -0
  59. package/dist/plugins/preferences/preferences-plugin.d.ts.map +1 -0
  60. package/dist/plugins/preferences/preferences-plugin.js +226 -0
  61. package/dist/plugins/preferences/preferences-plugin.js.map +1 -0
  62. package/dist/plugins/preferences/stores/index.d.ts +9 -0
  63. package/dist/plugins/preferences/stores/index.d.ts.map +1 -0
  64. package/dist/plugins/preferences/stores/index.js +9 -0
  65. package/dist/plugins/preferences/stores/index.js.map +1 -0
  66. package/dist/plugins/preferences/stores/postgres-store.d.ts +41 -0
  67. package/dist/plugins/preferences/stores/postgres-store.d.ts.map +1 -0
  68. package/dist/plugins/preferences/stores/postgres-store.js +181 -0
  69. package/dist/plugins/preferences/stores/postgres-store.js.map +1 -0
  70. package/dist/plugins/preferences/types.d.ts +91 -0
  71. package/dist/plugins/preferences/types.d.ts.map +1 -0
  72. package/dist/plugins/preferences/types.js +10 -0
  73. package/dist/plugins/preferences/types.js.map +1 -0
  74. package/dist/plugins/users/__tests__/users-plugin.test.d.ts +9 -0
  75. package/dist/plugins/users/__tests__/users-plugin.test.d.ts.map +1 -0
  76. package/dist/plugins/users/__tests__/users-plugin.test.js +546 -0
  77. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -0
  78. package/dist/plugins/users/index.d.ts +2 -2
  79. package/dist/plugins/users/index.d.ts.map +1 -1
  80. package/dist/plugins/users/index.js +1 -1
  81. package/dist/plugins/users/index.js.map +1 -1
  82. package/dist/plugins/users/types.d.ts +36 -0
  83. package/dist/plugins/users/types.d.ts.map +1 -1
  84. package/dist/plugins/users/users-plugin.d.ts +8 -2
  85. package/dist/plugins/users/users-plugin.d.ts.map +1 -1
  86. package/dist/plugins/users/users-plugin.js +122 -5
  87. package/dist/plugins/users/users-plugin.js.map +1 -1
  88. package/dist-ui/assets/{index-Bsp2ntcw.js → index-BY8OxNgO.js} +112 -112
  89. package/dist-ui/assets/index-BY8OxNgO.js.map +1 -0
  90. package/dist-ui/index.html +1 -1
  91. package/dist-ui-lib/api/controlPanelApi.d.ts +53 -7
  92. package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +9 -5
  93. package/dist-ui-lib/dashboard/builtInWidgets.d.ts +7 -1
  94. package/dist-ui-lib/index.js +2382 -3651
  95. package/dist-ui-lib/index.js.map +1 -1
  96. package/dist-ui-lib/pages/AuthPage.d.ts +1 -0
  97. package/dist-ui-lib/pages/PluginsPage.d.ts +1 -0
  98. package/package.json +7 -2
  99. package/src/core/control-panel.ts +33 -2
  100. package/src/core/plugin-registry.ts +63 -0
  101. package/src/index.ts +7 -0
  102. package/src/plugins/auth/adapters/index.ts +1 -0
  103. package/src/plugins/auth/adapters/supabase-adapter.ts +22 -14
  104. package/src/plugins/auth/adapters/supertokens-adapter.ts +326 -0
  105. package/src/plugins/auth/env-config.ts +572 -0
  106. package/src/plugins/auth/index.ts +9 -0
  107. package/src/plugins/auth/supertokens-adapter.test.ts +621 -0
  108. package/src/plugins/auth/types.ts +80 -0
  109. package/src/plugins/cache-plugin.test.ts +3 -0
  110. package/src/plugins/index.ts +26 -0
  111. package/src/plugins/postgres-plugin.test.ts +3 -0
  112. package/src/plugins/preferences/__tests__/deep-merge.test.ts +242 -0
  113. package/src/plugins/preferences/__tests__/preferences-plugin.test.ts +350 -0
  114. package/src/plugins/preferences/index.ts +30 -0
  115. package/src/plugins/preferences/preferences-plugin.ts +270 -0
  116. package/src/plugins/preferences/stores/index.ts +9 -0
  117. package/src/plugins/preferences/stores/postgres-store.ts +252 -0
  118. package/src/plugins/preferences/types.ts +100 -0
  119. package/src/plugins/users/__tests__/users-plugin.test.ts +690 -0
  120. package/src/plugins/users/index.ts +3 -0
  121. package/src/plugins/users/types.ts +38 -0
  122. package/src/plugins/users/users-plugin.ts +142 -5
  123. package/ui/src/App.tsx +4 -1
  124. package/ui/src/api/controlPanelApi.ts +100 -1
  125. package/ui/src/components/ControlPanelApp.tsx +3 -0
  126. package/ui/src/dashboard/PluginWidgetRenderer.tsx +13 -10
  127. package/ui/src/dashboard/WidgetComponentRegistry.tsx +13 -9
  128. package/ui/src/dashboard/builtInWidgets.tsx +8 -2
  129. package/ui/src/pages/AuthPage.tsx +259 -0
  130. package/ui/src/pages/PluginsPage.tsx +394 -0
  131. package/ui/vite.lib.config.ts +5 -0
  132. package/dist-ui/assets/index-Bsp2ntcw.js.map +0 -1
@@ -0,0 +1,572 @@
1
+ /**
2
+ * Auth Plugin Environment Configuration
3
+ *
4
+ * Factory function and utilities for configuring auth adapters via environment variables.
5
+ * Supports all adapters: Auth0, Supabase, Supertokens, Basic.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import type { Request, Response } from 'express';
11
+ import type { Plugin, PluginConfig, PluginRegistry } from '../../core/plugin-registry.js';
12
+ import type {
13
+ AuthPluginConfig,
14
+ Auth0AdapterConfig,
15
+ SupabaseAdapterConfig,
16
+ SupertokensAdapterConfig,
17
+ BasicAdapterConfig,
18
+ AuthEnvPluginOptions,
19
+ AuthConfigStatus,
20
+ AuthPluginState,
21
+ } from './types.js';
22
+ import { createAuthPlugin } from './auth-plugin.js';
23
+ import { auth0Adapter } from './adapters/auth0-adapter.js';
24
+ import { supabaseAdapter } from './adapters/supabase-adapter.js';
25
+ import { supertokensAdapter } from './adapters/supertokens-adapter.js';
26
+ import { basicAdapter } from './adapters/basic-adapter.js';
27
+
28
+ // ═══════════════════════════════════════════════════════════════════════════
29
+ // Module State
30
+ // ═══════════════════════════════════════════════════════════════════════════
31
+
32
+ let currentStatus: AuthConfigStatus = {
33
+ state: 'disabled',
34
+ adapter: null,
35
+ };
36
+
37
+ // ═══════════════════════════════════════════════════════════════════════════
38
+ // Environment Variable Helpers
39
+ // ═══════════════════════════════════════════════════════════════════════════
40
+
41
+ /**
42
+ * Get an environment variable, treating empty strings as undefined
43
+ */
44
+ function getEnv(key: string): string | undefined {
45
+ const value = process.env[key];
46
+ if (value === undefined || value === null || value.trim() === '') {
47
+ return undefined;
48
+ }
49
+ return value.trim();
50
+ }
51
+
52
+ /**
53
+ * Parse a boolean environment variable
54
+ * Supports: true/false, 1/0, yes/no (case-insensitive)
55
+ */
56
+ function getEnvBool(key: string, defaultValue: boolean): boolean {
57
+ const value = getEnv(key);
58
+ if (value === undefined) {
59
+ return defaultValue;
60
+ }
61
+ const lower = value.toLowerCase();
62
+ if (['true', '1', 'yes'].includes(lower)) {
63
+ return true;
64
+ }
65
+ if (['false', '0', 'no'].includes(lower)) {
66
+ return false;
67
+ }
68
+ return defaultValue;
69
+ }
70
+
71
+ /**
72
+ * Parse a comma-separated list environment variable
73
+ */
74
+ function getEnvList(key: string): string[] | undefined {
75
+ const value = getEnv(key);
76
+ if (value === undefined) {
77
+ return undefined;
78
+ }
79
+ return value
80
+ .split(',')
81
+ .map((s) => s.trim())
82
+ .filter((s) => s.length > 0);
83
+ }
84
+
85
+ /**
86
+ * Mask a sensitive value for display
87
+ */
88
+ function maskValue(value: string): string {
89
+ if (value.length <= 4) {
90
+ return '****';
91
+ }
92
+ return value.substring(0, 2) + '*'.repeat(Math.min(value.length - 4, 20)) + value.substring(value.length - 2);
93
+ }
94
+
95
+ // ═══════════════════════════════════════════════════════════════════════════
96
+ // Environment Parsers
97
+ // ═══════════════════════════════════════════════════════════════════════════
98
+
99
+ interface EnvParseResult<T> {
100
+ config: T | null;
101
+ errors: string[];
102
+ }
103
+
104
+ /**
105
+ * Parse Supertokens configuration from environment variables
106
+ */
107
+ function parseSupertokensEnv(): EnvParseResult<SupertokensAdapterConfig> {
108
+ const errors: string[] = [];
109
+
110
+ const connectionUri = getEnv('SUPERTOKENS_CONNECTION_URI');
111
+ const appName = getEnv('SUPERTOKENS_APP_NAME');
112
+ const apiDomain = getEnv('SUPERTOKENS_API_DOMAIN');
113
+ const websiteDomain = getEnv('SUPERTOKENS_WEBSITE_DOMAIN');
114
+
115
+ // Check required vars
116
+ if (!connectionUri) errors.push('SUPERTOKENS_CONNECTION_URI');
117
+ if (!appName) errors.push('SUPERTOKENS_APP_NAME');
118
+ if (!apiDomain) errors.push('SUPERTOKENS_API_DOMAIN');
119
+ if (!websiteDomain) errors.push('SUPERTOKENS_WEBSITE_DOMAIN');
120
+
121
+ if (errors.length > 0) {
122
+ return { config: null, errors };
123
+ }
124
+
125
+ // Build config
126
+ const config: SupertokensAdapterConfig = {
127
+ connectionUri: connectionUri!,
128
+ appName: appName!,
129
+ apiDomain: apiDomain!,
130
+ websiteDomain: websiteDomain!,
131
+ apiKey: getEnv('SUPERTOKENS_API_KEY'),
132
+ apiBasePath: getEnv('SUPERTOKENS_API_BASE_PATH') ?? '/auth',
133
+ websiteBasePath: getEnv('SUPERTOKENS_WEBSITE_BASE_PATH') ?? '/auth',
134
+ enableEmailPassword: getEnvBool('SUPERTOKENS_ENABLE_EMAIL_PASSWORD', true),
135
+ };
136
+
137
+ // Parse social providers
138
+ const googleClientId = getEnv('SUPERTOKENS_GOOGLE_CLIENT_ID');
139
+ const googleClientSecret = getEnv('SUPERTOKENS_GOOGLE_CLIENT_SECRET');
140
+ const githubClientId = getEnv('SUPERTOKENS_GITHUB_CLIENT_ID');
141
+ const githubClientSecret = getEnv('SUPERTOKENS_GITHUB_CLIENT_SECRET');
142
+ const appleClientId = getEnv('SUPERTOKENS_APPLE_CLIENT_ID');
143
+ const appleClientSecret = getEnv('SUPERTOKENS_APPLE_CLIENT_SECRET');
144
+ const appleKeyId = getEnv('SUPERTOKENS_APPLE_KEY_ID');
145
+ const appleTeamId = getEnv('SUPERTOKENS_APPLE_TEAM_ID');
146
+
147
+ // Only add social providers if both client ID and secret are provided
148
+ if (googleClientId && googleClientSecret) {
149
+ config.socialProviders = config.socialProviders || {};
150
+ config.socialProviders.google = {
151
+ clientId: googleClientId,
152
+ clientSecret: googleClientSecret,
153
+ };
154
+ }
155
+
156
+ if (githubClientId && githubClientSecret) {
157
+ config.socialProviders = config.socialProviders || {};
158
+ config.socialProviders.github = {
159
+ clientId: githubClientId,
160
+ clientSecret: githubClientSecret,
161
+ };
162
+ }
163
+
164
+ if (appleClientId && appleClientSecret && appleKeyId && appleTeamId) {
165
+ config.socialProviders = config.socialProviders || {};
166
+ config.socialProviders.apple = {
167
+ clientId: appleClientId,
168
+ clientSecret: appleClientSecret,
169
+ keyId: appleKeyId,
170
+ teamId: appleTeamId,
171
+ };
172
+ }
173
+
174
+ return { config, errors: [] };
175
+ }
176
+
177
+ /**
178
+ * Parse Auth0 configuration from environment variables
179
+ */
180
+ function parseAuth0Env(): EnvParseResult<Auth0AdapterConfig> {
181
+ const errors: string[] = [];
182
+
183
+ const domain = getEnv('AUTH0_DOMAIN');
184
+ const clientId = getEnv('AUTH0_CLIENT_ID');
185
+ const clientSecret = getEnv('AUTH0_CLIENT_SECRET');
186
+ const baseUrl = getEnv('AUTH0_BASE_URL');
187
+ const secret = getEnv('AUTH0_SECRET');
188
+
189
+ // Check required vars
190
+ if (!domain) errors.push('AUTH0_DOMAIN');
191
+ if (!clientId) errors.push('AUTH0_CLIENT_ID');
192
+ if (!clientSecret) errors.push('AUTH0_CLIENT_SECRET');
193
+ if (!baseUrl) errors.push('AUTH0_BASE_URL');
194
+ if (!secret) errors.push('AUTH0_SECRET');
195
+
196
+ if (errors.length > 0) {
197
+ return { config: null, errors };
198
+ }
199
+
200
+ // Build config
201
+ const config: Auth0AdapterConfig = {
202
+ domain: domain!,
203
+ clientId: clientId!,
204
+ clientSecret: clientSecret!,
205
+ baseUrl: baseUrl!,
206
+ secret: secret!,
207
+ audience: getEnv('AUTH0_AUDIENCE'),
208
+ scopes: getEnvList('AUTH0_SCOPES') ?? ['openid', 'profile', 'email'],
209
+ allowedRoles: getEnvList('AUTH0_ALLOWED_ROLES'),
210
+ allowedDomains: getEnvList('AUTH0_ALLOWED_DOMAINS'),
211
+ exposeAccessToken: getEnvBool('AUTH0_EXPOSE_ACCESS_TOKEN', false),
212
+ routes: {
213
+ login: getEnv('AUTH0_LOGIN_PATH') ?? '/login',
214
+ logout: getEnv('AUTH0_LOGOUT_PATH') ?? '/logout',
215
+ callback: getEnv('AUTH0_CALLBACK_PATH') ?? '/callback',
216
+ },
217
+ };
218
+
219
+ return { config, errors: [] };
220
+ }
221
+
222
+ /**
223
+ * Parse Supabase configuration from environment variables
224
+ */
225
+ function parseSupabaseEnv(): EnvParseResult<SupabaseAdapterConfig> {
226
+ const errors: string[] = [];
227
+
228
+ const url = getEnv('SUPABASE_URL');
229
+ const anonKey = getEnv('SUPABASE_ANON_KEY');
230
+
231
+ // Check required vars
232
+ if (!url) errors.push('SUPABASE_URL');
233
+ if (!anonKey) errors.push('SUPABASE_ANON_KEY');
234
+
235
+ if (errors.length > 0) {
236
+ return { config: null, errors };
237
+ }
238
+
239
+ const config: SupabaseAdapterConfig = {
240
+ url: url!,
241
+ anonKey: anonKey!,
242
+ };
243
+
244
+ return { config, errors: [] };
245
+ }
246
+
247
+ /**
248
+ * Parse Basic Auth configuration from environment variables
249
+ */
250
+ function parseBasicAuthEnv(): EnvParseResult<BasicAdapterConfig> {
251
+ const errors: string[] = [];
252
+
253
+ const username = getEnv('BASIC_AUTH_USERNAME');
254
+ const password = getEnv('BASIC_AUTH_PASSWORD');
255
+
256
+ // Check required vars
257
+ if (!username) errors.push('BASIC_AUTH_USERNAME');
258
+ if (!password) errors.push('BASIC_AUTH_PASSWORD');
259
+
260
+ if (errors.length > 0) {
261
+ return { config: null, errors };
262
+ }
263
+
264
+ const config: BasicAdapterConfig = {
265
+ username: username!,
266
+ password: password!,
267
+ realm: getEnv('BASIC_AUTH_REALM') ?? 'Protected',
268
+ };
269
+
270
+ return { config, errors: [] };
271
+ }
272
+
273
+ // ═══════════════════════════════════════════════════════════════════════════
274
+ // Factory Function
275
+ // ═══════════════════════════════════════════════════════════════════════════
276
+
277
+ /**
278
+ * Valid adapter names
279
+ */
280
+ const VALID_ADAPTERS = ['supertokens', 'auth0', 'supabase', 'basic'] as const;
281
+ type AdapterName = (typeof VALID_ADAPTERS)[number];
282
+
283
+ /**
284
+ * Create an auth plugin configured from environment variables.
285
+ *
286
+ * The plugin state depends on environment configuration:
287
+ * - **disabled**: AUTH_ADAPTER not set - no authentication middleware is applied
288
+ * - **enabled**: Valid configuration - adapter is active and working
289
+ * - **error**: Invalid configuration - plugin is disabled with error details
290
+ *
291
+ * @example
292
+ * ```typescript
293
+ * // Zero-config setup - reads everything from env vars
294
+ * const authPlugin = createAuthPluginFromEnv();
295
+ *
296
+ * // With overrides
297
+ * const authPlugin = createAuthPluginFromEnv({
298
+ * excludePaths: ['/health', '/metrics'],
299
+ * authRequired: true,
300
+ * });
301
+ * ```
302
+ *
303
+ * @param options - Optional overrides (env vars take precedence for adapter config)
304
+ * @returns A Plugin instance
305
+ */
306
+ export function createAuthPluginFromEnv(options?: AuthEnvPluginOptions): Plugin {
307
+ const adapterName = getEnv('AUTH_ADAPTER')?.toLowerCase();
308
+
309
+ // No adapter specified - return disabled plugin
310
+ if (!adapterName) {
311
+ currentStatus = {
312
+ state: 'disabled',
313
+ adapter: null,
314
+ };
315
+ return createDisabledPlugin();
316
+ }
317
+
318
+ // Validate adapter name
319
+ if (!VALID_ADAPTERS.includes(adapterName as AdapterName)) {
320
+ const error = `Invalid AUTH_ADAPTER value: "${adapterName}". Valid options: ${VALID_ADAPTERS.join(', ')}`;
321
+ currentStatus = {
322
+ state: 'error',
323
+ adapter: null,
324
+ error,
325
+ };
326
+ return createErrorPlugin(error);
327
+ }
328
+
329
+ // Parse adapter-specific configuration
330
+ let parseResult: EnvParseResult<
331
+ Auth0AdapterConfig | SupabaseAdapterConfig | SupertokensAdapterConfig | BasicAdapterConfig
332
+ >;
333
+
334
+ switch (adapterName as AdapterName) {
335
+ case 'supertokens':
336
+ parseResult = parseSupertokensEnv();
337
+ break;
338
+ case 'auth0':
339
+ parseResult = parseAuth0Env();
340
+ break;
341
+ case 'supabase':
342
+ parseResult = parseSupabaseEnv();
343
+ break;
344
+ case 'basic':
345
+ parseResult = parseBasicAuthEnv();
346
+ break;
347
+ }
348
+
349
+ // Check for parsing errors
350
+ if (parseResult.errors.length > 0) {
351
+ const error = `Missing required environment variables for ${adapterName} adapter: ${parseResult.errors.join(', ')}`;
352
+ currentStatus = {
353
+ state: 'error',
354
+ adapter: adapterName,
355
+ error,
356
+ missingVars: parseResult.errors,
357
+ };
358
+ return createErrorPlugin(error);
359
+ }
360
+
361
+ // Create the adapter
362
+ let adapter;
363
+ switch (adapterName as AdapterName) {
364
+ case 'supertokens':
365
+ adapter = supertokensAdapter(parseResult.config as SupertokensAdapterConfig);
366
+ break;
367
+ case 'auth0':
368
+ adapter = auth0Adapter(parseResult.config as Auth0AdapterConfig);
369
+ break;
370
+ case 'supabase':
371
+ adapter = supabaseAdapter(parseResult.config as SupabaseAdapterConfig);
372
+ break;
373
+ case 'basic':
374
+ adapter = basicAdapter(parseResult.config as BasicAdapterConfig);
375
+ break;
376
+ }
377
+
378
+ // Build plugin configuration
379
+ const excludePaths = options?.excludePaths ?? getEnvList('AUTH_EXCLUDE_PATHS') ?? [];
380
+ const authRequired = options?.authRequired ?? getEnvBool('AUTH_REQUIRED', true);
381
+ const debug = options?.debug ?? getEnvBool('AUTH_DEBUG', false);
382
+
383
+ const pluginConfig: AuthPluginConfig = {
384
+ adapter,
385
+ excludePaths,
386
+ authRequired,
387
+ debug,
388
+ onUnauthorized: options?.onUnauthorized,
389
+ };
390
+
391
+ // Update status
392
+ currentStatus = {
393
+ state: 'enabled',
394
+ adapter: adapterName,
395
+ config: getMaskedConfig(adapterName as AdapterName),
396
+ };
397
+
398
+ // Create the plugin
399
+ const basePlugin = createAuthPlugin(pluginConfig);
400
+
401
+ // Wrap to add config status routes
402
+ return {
403
+ ...basePlugin,
404
+ async onStart(pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
405
+ // Call base plugin onStart
406
+ await basePlugin.onStart?.(pluginConfig, registry);
407
+
408
+ // Add config status routes
409
+ registerConfigRoutes(registry);
410
+ },
411
+ };
412
+ }
413
+
414
+ // ═══════════════════════════════════════════════════════════════════════════
415
+ // Status & Config API
416
+ // ═══════════════════════════════════════════════════════════════════════════
417
+
418
+ /**
419
+ * Get current auth plugin status
420
+ */
421
+ export function getAuthStatus(): AuthConfigStatus {
422
+ return { ...currentStatus };
423
+ }
424
+
425
+ /**
426
+ * Get masked configuration for the current adapter
427
+ */
428
+ function getMaskedConfig(adapter: AdapterName): Record<string, string> {
429
+ const config: Record<string, string> = {};
430
+
431
+ // Sensitive keys that should be masked
432
+ const sensitiveKeys = [
433
+ 'SECRET',
434
+ 'PASSWORD',
435
+ 'KEY',
436
+ 'TOKEN',
437
+ 'CREDENTIAL',
438
+ 'ANON_KEY',
439
+ 'API_KEY',
440
+ 'CLIENT_SECRET',
441
+ ];
442
+
443
+ const isSensitive = (key: string): boolean => {
444
+ const upper = key.toUpperCase();
445
+ return sensitiveKeys.some((s) => upper.includes(s));
446
+ };
447
+
448
+ // Get all env vars for the adapter
449
+ const prefixes: Record<AdapterName, string[]> = {
450
+ supertokens: ['SUPERTOKENS_'],
451
+ auth0: ['AUTH0_'],
452
+ supabase: ['SUPABASE_'],
453
+ basic: ['BASIC_AUTH_'],
454
+ };
455
+
456
+ for (const [key, value] of Object.entries(process.env)) {
457
+ const matchesPrefix = prefixes[adapter].some((p) => key.startsWith(p));
458
+ if (matchesPrefix && value) {
459
+ config[key] = isSensitive(key) ? maskValue(value) : value;
460
+ }
461
+ }
462
+
463
+ // Add general auth vars
464
+ const generalVars = ['AUTH_ADAPTER', 'AUTH_REQUIRED', 'AUTH_EXCLUDE_PATHS', 'AUTH_DEBUG'];
465
+ for (const key of generalVars) {
466
+ const value = process.env[key];
467
+ if (value) {
468
+ config[key] = value;
469
+ }
470
+ }
471
+
472
+ return config;
473
+ }
474
+
475
+ /**
476
+ * Register config API routes
477
+ */
478
+ function registerConfigRoutes(registry: PluginRegistry): void {
479
+ // GET /api/auth/config/status - Get current auth status
480
+ registry.addRoute({
481
+ method: 'get',
482
+ path: '/api/auth/config/status',
483
+ pluginId: 'auth',
484
+ handler: (_req: Request, res: Response) => {
485
+ res.json(getAuthStatus());
486
+ },
487
+ });
488
+
489
+ // GET /api/auth/config - Get current configuration (masked)
490
+ registry.addRoute({
491
+ method: 'get',
492
+ path: '/api/auth/config',
493
+ pluginId: 'auth',
494
+ handler: (_req: Request, res: Response) => {
495
+ const status = getAuthStatus();
496
+ res.json({
497
+ state: status.state,
498
+ adapter: status.adapter,
499
+ config: status.config || {},
500
+ error: status.error,
501
+ missingVars: status.missingVars,
502
+ });
503
+ },
504
+ });
505
+ }
506
+
507
+ // ═══════════════════════════════════════════════════════════════════════════
508
+ // Disabled & Error Plugins
509
+ // ═══════════════════════════════════════════════════════════════════════════
510
+
511
+ /**
512
+ * Create a disabled plugin (no auth middleware)
513
+ */
514
+ function createDisabledPlugin(): Plugin {
515
+ return {
516
+ id: 'auth',
517
+ name: 'Auth Plugin (Disabled)',
518
+ version: '1.0.0',
519
+
520
+ async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
521
+ const logger = registry.getLogger('auth');
522
+ logger.info('Auth plugin disabled - AUTH_ADAPTER not set');
523
+
524
+ // Register status routes even when disabled
525
+ registerConfigRoutes(registry);
526
+ },
527
+
528
+ async onStop(): Promise<void> {
529
+ // Nothing to cleanup
530
+ },
531
+ };
532
+ }
533
+
534
+ /**
535
+ * Create an error plugin (auth disabled due to configuration error)
536
+ */
537
+ function createErrorPlugin(error: string): Plugin {
538
+ return {
539
+ id: 'auth',
540
+ name: 'Auth Plugin (Error)',
541
+ version: '1.0.0',
542
+
543
+ async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
544
+ const logger = registry.getLogger('auth');
545
+ logger.error(`Auth plugin error: ${error}`);
546
+
547
+ // Register status routes so admin can see the error
548
+ registerConfigRoutes(registry);
549
+ },
550
+
551
+ async onStop(): Promise<void> {
552
+ // Nothing to cleanup
553
+ },
554
+ };
555
+ }
556
+
557
+ // ═══════════════════════════════════════════════════════════════════════════
558
+ // Exports for Testing
559
+ // ═══════════════════════════════════════════════════════════════════════════
560
+
561
+ // Export internal functions for testing
562
+ export const __testing = {
563
+ parseSupertokensEnv,
564
+ parseAuth0Env,
565
+ parseSupabaseEnv,
566
+ parseBasicAuthEnv,
567
+ getEnv,
568
+ getEnvBool,
569
+ getEnvList,
570
+ maskValue,
571
+ VALID_ADAPTERS,
572
+ };
@@ -15,6 +15,9 @@ export {
15
15
  requireAnyRole,
16
16
  } from './auth-plugin.js';
17
17
 
18
+ // Environment-based configuration
19
+ export { createAuthPluginFromEnv, getAuthStatus } from './env-config.js';
20
+
18
21
  // Types
19
22
  export type {
20
23
  AuthPluginConfig,
@@ -24,6 +27,11 @@ export type {
24
27
  Auth0AdapterConfig,
25
28
  SupabaseAdapterConfig,
26
29
  BasicAdapterConfig,
30
+ SupertokensAdapterConfig,
31
+ // Environment config types
32
+ AuthPluginState,
33
+ AuthEnvPluginOptions,
34
+ AuthConfigStatus,
27
35
  } from './types.js';
28
36
  export { isAuthenticatedRequest } from './types.js';
29
37
 
@@ -31,3 +39,4 @@ export { isAuthenticatedRequest } from './types.js';
31
39
  export { auth0Adapter } from './adapters/auth0-adapter.js';
32
40
  export { basicAdapter } from './adapters/basic-adapter.js';
33
41
  export { supabaseAdapter } from './adapters/supabase-adapter.js';
42
+ export { supertokensAdapter } from './adapters/supertokens-adapter.js';