@harperfast/oauth 1.2.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 (63) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +219 -0
  3. package/assets/test.html +321 -0
  4. package/config.yaml +23 -0
  5. package/dist/index.d.ts +43 -0
  6. package/dist/index.js +241 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/lib/CSRFTokenManager.d.ts +32 -0
  9. package/dist/lib/CSRFTokenManager.js +90 -0
  10. package/dist/lib/CSRFTokenManager.js.map +1 -0
  11. package/dist/lib/OAuthProvider.d.ts +59 -0
  12. package/dist/lib/OAuthProvider.js +370 -0
  13. package/dist/lib/OAuthProvider.js.map +1 -0
  14. package/dist/lib/config.d.ts +31 -0
  15. package/dist/lib/config.js +138 -0
  16. package/dist/lib/config.js.map +1 -0
  17. package/dist/lib/handlers.d.ts +56 -0
  18. package/dist/lib/handlers.js +386 -0
  19. package/dist/lib/handlers.js.map +1 -0
  20. package/dist/lib/hookManager.d.ts +52 -0
  21. package/dist/lib/hookManager.js +114 -0
  22. package/dist/lib/hookManager.js.map +1 -0
  23. package/dist/lib/providers/auth0.d.ts +8 -0
  24. package/dist/lib/providers/auth0.js +34 -0
  25. package/dist/lib/providers/auth0.js.map +1 -0
  26. package/dist/lib/providers/azure.d.ts +7 -0
  27. package/dist/lib/providers/azure.js +33 -0
  28. package/dist/lib/providers/azure.js.map +1 -0
  29. package/dist/lib/providers/generic.d.ts +7 -0
  30. package/dist/lib/providers/generic.js +20 -0
  31. package/dist/lib/providers/generic.js.map +1 -0
  32. package/dist/lib/providers/github.d.ts +7 -0
  33. package/dist/lib/providers/github.js +73 -0
  34. package/dist/lib/providers/github.js.map +1 -0
  35. package/dist/lib/providers/google.d.ts +7 -0
  36. package/dist/lib/providers/google.js +27 -0
  37. package/dist/lib/providers/google.js.map +1 -0
  38. package/dist/lib/providers/index.d.ts +17 -0
  39. package/dist/lib/providers/index.js +49 -0
  40. package/dist/lib/providers/index.js.map +1 -0
  41. package/dist/lib/providers/okta.d.ts +8 -0
  42. package/dist/lib/providers/okta.js +45 -0
  43. package/dist/lib/providers/okta.js.map +1 -0
  44. package/dist/lib/providers/validation.d.ts +67 -0
  45. package/dist/lib/providers/validation.js +156 -0
  46. package/dist/lib/providers/validation.js.map +1 -0
  47. package/dist/lib/resource.d.ts +102 -0
  48. package/dist/lib/resource.js +368 -0
  49. package/dist/lib/resource.js.map +1 -0
  50. package/dist/lib/sessionValidator.d.ts +38 -0
  51. package/dist/lib/sessionValidator.js +162 -0
  52. package/dist/lib/sessionValidator.js.map +1 -0
  53. package/dist/lib/tenantManager.d.ts +102 -0
  54. package/dist/lib/tenantManager.js +177 -0
  55. package/dist/lib/tenantManager.js.map +1 -0
  56. package/dist/lib/withOAuthValidation.d.ts +64 -0
  57. package/dist/lib/withOAuthValidation.js +188 -0
  58. package/dist/lib/withOAuthValidation.js.map +1 -0
  59. package/dist/types.d.ts +326 -0
  60. package/dist/types.js +5 -0
  61. package/dist/types.js.map +1 -0
  62. package/package.json +89 -0
  63. package/schema/oauth.graphql +21 -0
@@ -0,0 +1,321 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Harper OAuth Test</title>
5
+ <style>
6
+ body {
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8
+ max-width: 800px;
9
+ margin: 50px auto;
10
+ padding: 20px;
11
+ }
12
+ .user-info {
13
+ background: #f5f5f5;
14
+ padding: 20px;
15
+ border-radius: 8px;
16
+ margin: 20px 0;
17
+ }
18
+ .providers {
19
+ margin: 20px 0;
20
+ }
21
+ .provider-button {
22
+ display: inline-block;
23
+ padding: 10px 20px;
24
+ margin: 10px 10px 10px 0;
25
+ background: #0366d6;
26
+ color: white;
27
+ text-decoration: none;
28
+ border-radius: 6px;
29
+ border: none;
30
+ cursor: pointer;
31
+ font-size: 14px;
32
+ }
33
+ .provider-button.github {
34
+ background: #24292e;
35
+ }
36
+ .provider-button.github:hover {
37
+ background: #1a1e22;
38
+ }
39
+ .provider-button.google {
40
+ background: #4285f4;
41
+ }
42
+ .provider-button.google:hover {
43
+ background: #357ae8;
44
+ }
45
+ .provider-button.azure,
46
+ .provider-button.microsoft {
47
+ background: #0078d4;
48
+ }
49
+ .provider-button.azure:hover,
50
+ .provider-button.microsoft:hover {
51
+ background: #006cc1;
52
+ }
53
+ .provider-button.auth0 {
54
+ background: #eb5424;
55
+ }
56
+ .provider-button.auth0:hover {
57
+ background: #d44820;
58
+ }
59
+ .provider-button.okta {
60
+ background: #007dc1;
61
+ }
62
+ .provider-button.okta:hover {
63
+ background: #006ba1;
64
+ }
65
+ .button {
66
+ display: inline-block;
67
+ padding: 10px 20px;
68
+ margin: 10px 10px 10px 0;
69
+ background: #6c757d;
70
+ color: white;
71
+ text-decoration: none;
72
+ border-radius: 6px;
73
+ border: none;
74
+ cursor: pointer;
75
+ font-size: 14px;
76
+ }
77
+ .button:hover {
78
+ background: #5a6268;
79
+ }
80
+ pre {
81
+ background: #f6f8fa;
82
+ padding: 10px;
83
+ border-radius: 6px;
84
+ overflow-x: auto;
85
+ }
86
+ .error {
87
+ color: #d73a49;
88
+ background: #ffeef0;
89
+ padding: 10px;
90
+ border-radius: 6px;
91
+ margin: 10px 0;
92
+ }
93
+ .success {
94
+ color: #28a745;
95
+ background: #d4edda;
96
+ padding: 10px;
97
+ border-radius: 6px;
98
+ margin: 10px 0;
99
+ }
100
+ .loading {
101
+ color: #666;
102
+ font-style: italic;
103
+ }
104
+ </style>
105
+ </head>
106
+ <body>
107
+ <h1>Harper OAuth Test</h1>
108
+
109
+ <div id="user-info" class="user-info">
110
+ <h2>Current User</h2>
111
+ <div id="user-status" class="loading">Loading...</div>
112
+ </div>
113
+
114
+ <div class="providers">
115
+ <h2>Login Options</h2>
116
+ <div id="provider-buttons" class="loading">Loading providers...</div>
117
+ </div>
118
+
119
+ <div id="actions">
120
+ <button onclick="logout()" class="button">Logout</button>
121
+ <button onclick="checkUser()" class="button">Refresh Status</button>
122
+ </div>
123
+
124
+ <div id="message"></div>
125
+
126
+ <h2>Debug Endpoints</h2>
127
+ <p>These endpoints are available in debug mode:</p>
128
+ <ul>
129
+ <li><a href="/oauth" target="_blank">/oauth</a> - List all configured providers</li>
130
+ <li id="user-endpoint">Loading...</li>
131
+ <li id="refresh-endpoint" style="display: none">
132
+ <a href="#" onclick="refreshToken(); return false;">/oauth/{provider}/refresh</a> - Refresh access token
133
+ </li>
134
+ </ul>
135
+
136
+ <script>
137
+ let currentProvider = null;
138
+ let availableProviders = [];
139
+
140
+ async function loadProviders() {
141
+ try {
142
+ const response = await fetch('/oauth');
143
+ const data = await response.json();
144
+
145
+ const providerButtons = document.getElementById('provider-buttons');
146
+
147
+ if (data.providers && data.providers.length > 0) {
148
+ availableProviders = data.providers;
149
+ providerButtons.innerHTML = data.providers
150
+ .map(
151
+ (p) => `
152
+ <a href="/oauth/${p.name}/login"
153
+ class="provider-button ${p.provider}"
154
+ onclick="setProvider('${p.name}'); return true;">
155
+ Login with ${p.name.charAt(0).toUpperCase() + p.name.slice(1)}
156
+ </a>
157
+ `
158
+ )
159
+ .join('');
160
+
161
+ // Update user endpoint link
162
+ if (data.providers[0]) {
163
+ currentProvider = data.providers[0].name;
164
+ updateEndpoints();
165
+ }
166
+ } else {
167
+ providerButtons.innerHTML = '<p class="error">No OAuth providers configured</p>';
168
+ }
169
+ } catch (error) {
170
+ document.getElementById('provider-buttons').innerHTML =
171
+ '<p class="error">Error loading providers: ' + error.message + '</p>';
172
+ }
173
+ }
174
+
175
+ function setProvider(provider) {
176
+ currentProvider = provider;
177
+ localStorage.setItem('oauth-provider', provider);
178
+ updateEndpoints();
179
+ }
180
+
181
+ function updateEndpoints() {
182
+ if (currentProvider) {
183
+ document.getElementById('user-endpoint').innerHTML =
184
+ `<a href="/oauth/${currentProvider}/user" target="_blank">/oauth/${currentProvider}/user</a> - Current user info (JSON)`;
185
+ }
186
+ }
187
+
188
+ async function checkUser() {
189
+ if (!currentProvider && availableProviders.length > 0) {
190
+ currentProvider = availableProviders[0].name;
191
+ }
192
+
193
+ if (!currentProvider) {
194
+ document.getElementById('user-status').innerHTML = '<p class="error">No OAuth provider available</p>';
195
+ return;
196
+ }
197
+
198
+ try {
199
+ const response = await fetch(`/oauth/${currentProvider}/user`);
200
+ const data = await response.json();
201
+
202
+ const userStatus = document.getElementById('user-status');
203
+
204
+ if (response.status === 200 && (data.authenticated || data.body?.authenticated)) {
205
+ const user = data.body || data;
206
+ userStatus.innerHTML = `
207
+ <p><strong>Authenticated:</strong> Yes</p>
208
+ <p><strong>Username:</strong> ${user.username}</p>
209
+ <p><strong>Email:</strong> ${user.email || 'N/A'}</p>
210
+ <p><strong>Name:</strong> ${user.name || 'N/A'}</p>
211
+ <p><strong>Role:</strong> ${user.role || 'N/A'}</p>
212
+ <p><strong>Provider:</strong> ${user.provider}</p>
213
+ <pre>${JSON.stringify(user, null, 2)}</pre>
214
+ `;
215
+ showMessage('Authenticated successfully', 'success');
216
+ } else {
217
+ userStatus.innerHTML = `
218
+ <p><strong>Authenticated:</strong> No</p>
219
+ <p>Select a provider above to login.</p>
220
+ `;
221
+ }
222
+ } catch (error) {
223
+ document.getElementById('user-status').innerHTML =
224
+ '<p class="error">Error checking user status: ' + error.message + '</p>';
225
+ }
226
+ }
227
+
228
+ async function logout() {
229
+ try {
230
+ const response = await fetch('/oauth/logout', {
231
+ method: 'POST',
232
+ headers: {
233
+ 'Content-Type': 'application/json',
234
+ },
235
+ });
236
+
237
+ if (response.ok) {
238
+ showMessage('Logged out successfully', 'success');
239
+ checkUser();
240
+ } else {
241
+ throw new Error('Logout failed');
242
+ }
243
+ } catch (error) {
244
+ showMessage('Error logging out: ' + error.message, 'error');
245
+ }
246
+ }
247
+
248
+ async function refreshToken() {
249
+ if (!currentProvider) {
250
+ showMessage('No provider selected', 'error');
251
+ return;
252
+ }
253
+
254
+ try {
255
+ const response = await fetch(`/oauth/${currentProvider}/refresh`);
256
+ const data = await response.json();
257
+
258
+ if (response.ok) {
259
+ showMessage(
260
+ `Token refreshed successfully (expires in ${data.expiresIn || data.body?.expiresIn || 'unknown'} seconds)`,
261
+ 'success'
262
+ );
263
+ } else {
264
+ showMessage(`Refresh failed: ${data.error || data.body?.error || 'Unknown error'}`, 'error');
265
+ }
266
+ } catch (error) {
267
+ showMessage('Error refreshing token: ' + error.message, 'error');
268
+ }
269
+ }
270
+
271
+ function showMessage(text, type) {
272
+ const messageEl = document.getElementById('message');
273
+ messageEl.className = type;
274
+ messageEl.textContent = text;
275
+ messageEl.style.display = 'block';
276
+
277
+ setTimeout(() => {
278
+ messageEl.style.display = 'none';
279
+ }, 5000);
280
+ }
281
+
282
+ // Initialize on page load
283
+ async function init() {
284
+ // Load saved provider preference
285
+ currentProvider = localStorage.getItem('oauth-provider');
286
+
287
+ await loadProviders();
288
+ await checkUser();
289
+
290
+ // Check for OAuth error parameters
291
+ const urlParams = new URLSearchParams(window.location.search);
292
+ const error = urlParams.get('error');
293
+ if (error) {
294
+ if (error === 'session_expired') {
295
+ showMessage('Session expired. Please login again.', 'error');
296
+ } else if (error === 'oauth_failed') {
297
+ const reason = urlParams.get('reason') || 'unknown';
298
+ showMessage(`OAuth login failed: ${reason}`, 'error');
299
+ } else if (error === 'invalid_request') {
300
+ showMessage('Invalid OAuth request. Please try again.', 'error');
301
+ } else {
302
+ showMessage(`Login error: ${error}`, 'error');
303
+ }
304
+ // Clean URL after showing error
305
+ window.history.replaceState({}, document.title, window.location.pathname);
306
+ }
307
+
308
+ // Check if we just logged in (redirected back)
309
+ if (window.location.pathname.includes('/callback')) {
310
+ showMessage('Login successful!', 'success');
311
+ // Redirect to test page to clean URL
312
+ setTimeout(() => {
313
+ window.location.href = '/oauth/test';
314
+ }, 1000);
315
+ }
316
+ }
317
+
318
+ init();
319
+ </script>
320
+ </body>
321
+ </html>
package/config.yaml ADDED
@@ -0,0 +1,23 @@
1
+ # OAuth Plugin Configuration
2
+ # This file defines the plugin entry point and default settings
3
+ # All settings can be overridden in your application's config.yaml
4
+
5
+ # Plugin entry point (required by Harper)
6
+ pluginModule: 'dist/index.js'
7
+
8
+ # GraphQL schema for OAuth tables
9
+ graphqlSchema:
10
+ files: 'schema/oauth.graphql'
11
+
12
+ # Default settings (optional - these are used if not specified in app config)
13
+ # No providers configured by default - must be configured in application
14
+ # providers: {}
15
+
16
+ # Default OAuth settings
17
+ scope: 'openid profile email'
18
+ usernameClaim: 'email'
19
+ defaultRole: 'user'
20
+ postLoginRedirect: '/'
21
+
22
+ # Default redirect URI pattern (will be adjusted per provider)
23
+ redirectUri: 'https://localhost:9953/oauth/callback'
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Harper OAuth Plugin
3
+ *
4
+ * Provides OAuth 2.0 authentication for Harper applications.
5
+ * Supports any standard OAuth 2.0 provider through configuration.
6
+ */
7
+ import type { Scope, OAuthHooks } from './types.ts';
8
+ export { HookManager } from './lib/hookManager.ts';
9
+ export { OAuthResource } from './lib/resource.ts';
10
+ export type { OAuthHooks, OAuthUser, TokenResponse } from './types.ts';
11
+ export { TenantManager } from './lib/tenantManager.ts';
12
+ export type { TenantConfig, TenantRegistryEntry } from './lib/tenantManager.ts';
13
+ export { validateDomainSafety, validateDomainAllowlist, validateEmailDomain, validateTenantId, sanitizeTenantName, validateAzureTenantId, } from './lib/providers/validation.ts';
14
+ export { getProvider } from './lib/providers/index.ts';
15
+ /**
16
+ * Register OAuth hooks programmatically
17
+ * Call this from your application code to register lifecycle hooks
18
+ *
19
+ * This can be called:
20
+ * - At module load time (before the plugin initializes) - hooks will be queued
21
+ * - After plugin initialization - hooks will be applied immediately
22
+ *
23
+ * NOTE: This registers hooks at the module level, shared across all instances
24
+ * of the OAuth plugin. For most applications with a single OAuth plugin instance,
25
+ * this is the simplest and recommended approach.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import { registerHooks } from '@harperfast/oauth';
30
+ *
31
+ * // Can be called at module load time or later
32
+ * registerHooks({
33
+ * onLogin: async (oauthUser, tokenResponse, session, request, provider) => {
34
+ * console.log(`User logged in: ${oauthUser.username}`);
35
+ * }
36
+ * });
37
+ * ```
38
+ */
39
+ export declare function registerHooks(hooks: OAuthHooks): void;
40
+ /**
41
+ * Plugin entry point
42
+ */
43
+ export declare function handleApplication(scope: Scope): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Harper OAuth Plugin
3
+ *
4
+ * Provides OAuth 2.0 authentication for Harper applications.
5
+ * Supports any standard OAuth 2.0 provider through configuration.
6
+ */
7
+ import { initializeProviders, expandEnvVar, extractPluginDefaults } from "./lib/config.js";
8
+ import { OAuthResource } from "./lib/resource.js";
9
+ import { validateAndRefreshSession } from "./lib/sessionValidator.js";
10
+ import { clearOAuthSession } from "./lib/handlers.js";
11
+ import { HookManager } from "./lib/hookManager.js";
12
+ // Export HookManager class, OAuthResource class, and types
13
+ export { HookManager } from "./lib/hookManager.js";
14
+ export { OAuthResource } from "./lib/resource.js";
15
+ // Export multi-tenant SSO support
16
+ export { TenantManager } from "./lib/tenantManager.js";
17
+ // Export validation utilities for secure tenant configuration
18
+ export { validateDomainSafety, validateDomainAllowlist, validateEmailDomain, validateTenantId, sanitizeTenantName, validateAzureTenantId, } from "./lib/providers/validation.js";
19
+ // Export provider utilities
20
+ export { getProvider } from "./lib/providers/index.js";
21
+ // Store hooks registered at module load time and active hookManager
22
+ let pendingHooks = null;
23
+ let activeHookManager = null;
24
+ /**
25
+ * Register OAuth hooks programmatically
26
+ * Call this from your application code to register lifecycle hooks
27
+ *
28
+ * This can be called:
29
+ * - At module load time (before the plugin initializes) - hooks will be queued
30
+ * - After plugin initialization - hooks will be applied immediately
31
+ *
32
+ * NOTE: This registers hooks at the module level, shared across all instances
33
+ * of the OAuth plugin. For most applications with a single OAuth plugin instance,
34
+ * this is the simplest and recommended approach.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * import { registerHooks } from '@harperfast/oauth';
39
+ *
40
+ * // Can be called at module load time or later
41
+ * registerHooks({
42
+ * onLogin: async (oauthUser, tokenResponse, session, request, provider) => {
43
+ * console.log(`User logged in: ${oauthUser.username}`);
44
+ * }
45
+ * });
46
+ * ```
47
+ */
48
+ export function registerHooks(hooks) {
49
+ if (activeHookManager) {
50
+ // Plugin is already loaded - apply immediately
51
+ activeHookManager.register(hooks);
52
+ }
53
+ else {
54
+ // Plugin not loaded yet - queue for later
55
+ pendingHooks = hooks;
56
+ }
57
+ }
58
+ /**
59
+ * Plugin entry point
60
+ */
61
+ export async function handleApplication(scope) {
62
+ const logger = scope.logger;
63
+ let providers = {};
64
+ let debugMode = false;
65
+ let isInitialized = false;
66
+ let pluginDefaults = {}; // Store plugin defaults for dynamic provider resolution
67
+ // Create hookManager instance scoped to this application
68
+ const hookManager = new HookManager(logger);
69
+ // Set as active hookManager for late hook registration
70
+ activeHookManager = hookManager;
71
+ // Apply any hooks that were registered at module load time
72
+ if (pendingHooks) {
73
+ hookManager.register(pendingHooks);
74
+ logger?.debug?.('Applied pending OAuth hooks');
75
+ pendingHooks = null; // Clear pending hooks after applying
76
+ }
77
+ /**
78
+ * Update OAuth configuration when options change
79
+ */
80
+ async function updateConfiguration() {
81
+ const rawOptions = (scope.options.getAll() || {});
82
+ // Expand environment variables in plugin-level options
83
+ const debugValue = expandEnvVar(rawOptions.debug);
84
+ const options = { ...rawOptions, debug: debugValue };
85
+ const previousDebugMode = debugMode;
86
+ // Handle both boolean and string values (from environment variables)
87
+ debugMode = options.debug === true || options.debug === 'true';
88
+ // Log configuration update
89
+ if (isInitialized) {
90
+ logger?.info?.('OAuth configuration changed, updating providers...');
91
+ }
92
+ else {
93
+ logger?.info?.('OAuth plugin loading with options:', JSON.stringify(options, null, 2));
94
+ isInitialized = true;
95
+ }
96
+ // Re-initialize providers from new configuration
97
+ // Clear existing providers and repopulate (don't reassign to preserve closure reference)
98
+ const newProviders = initializeProviders(options, logger);
99
+ Object.keys(providers).forEach((key) => delete providers[key]);
100
+ Object.assign(providers, newProviders);
101
+ // Extract plugin defaults for dynamic provider resolution
102
+ pluginDefaults = extractPluginDefaults(options);
103
+ // Update the resource with new providers
104
+ if (Object.keys(providers).length === 0) {
105
+ // No valid providers configured - register a simple error resource
106
+ scope.resources.set('oauth', {
107
+ async get() {
108
+ return {
109
+ status: 503,
110
+ body: {
111
+ error: 'No valid OAuth providers configured',
112
+ message: 'Please check your OAuth configuration',
113
+ example: options.providers
114
+ ? 'Check that all required fields are provided'
115
+ : {
116
+ providers: {
117
+ github: {
118
+ provider: 'github',
119
+ clientId: '${OAUTH_GITHUB_CLIENT_ID}',
120
+ clientSecret: '${OAUTH_GITHUB_CLIENT_SECRET}',
121
+ },
122
+ },
123
+ },
124
+ },
125
+ };
126
+ },
127
+ });
128
+ }
129
+ else {
130
+ // Configure the OAuth resource with providers and settings
131
+ OAuthResource.configure(providers, debugMode, hookManager, pluginDefaults, logger);
132
+ // Register the OAuth resource class
133
+ scope.resources.set('oauth', OAuthResource);
134
+ // Log all configured providers
135
+ logger?.info?.('OAuth plugin ready:', {
136
+ providers: Object.entries(providers).map(([name, data]) => ({
137
+ name,
138
+ type: data.config.provider,
139
+ redirectUri: data.config.redirectUri,
140
+ })),
141
+ });
142
+ }
143
+ // Log debug mode change if it changed
144
+ if (isInitialized && previousDebugMode !== debugMode) {
145
+ logger?.info?.(`OAuth debug mode ${debugMode ? 'enabled' : 'disabled'}`);
146
+ }
147
+ }
148
+ // Register HTTP middleware for automatic OAuth session validation
149
+ // This runs on every HTTP request after authentication but before REST
150
+ scope.server.http?.(async (request, next) => {
151
+ // Only process requests with sessions that have OAuth data
152
+ if (!request.session?.oauth) {
153
+ return next(request);
154
+ }
155
+ // Get the provider config ID for this OAuth session
156
+ // Use providerConfigId (new) or fall back to provider (old) for backwards compatibility
157
+ const providerConfigId = request.session.oauth.providerConfigId || request.session.oauth.provider;
158
+ let providerData = providers[providerConfigId];
159
+ // If provider not found in registry, try to resolve via hook (dynamic providers)
160
+ if (!providerData && hookManager?.hasHook('onResolveProvider')) {
161
+ try {
162
+ logger?.debug?.(`Provider config "${providerConfigId}" not in registry, attempting dynamic resolution`);
163
+ const hookConfig = await hookManager.callResolveProvider(providerConfigId, logger);
164
+ if (hookConfig) {
165
+ // Hook resolved provider - build full config and register dynamically
166
+ const { OAuthProvider } = await import("./lib/OAuthProvider.js");
167
+ const { buildProviderConfig } = await import("./lib/config.js");
168
+ // Build full provider config (handles Okta/Azure/Auth0 domain configuration)
169
+ const config = buildProviderConfig(hookConfig, providerConfigId, pluginDefaults);
170
+ const provider = new OAuthProvider(config, logger);
171
+ providers[providerConfigId] = {
172
+ provider,
173
+ config,
174
+ };
175
+ providerData = providers[providerConfigId];
176
+ logger?.info?.(`Dynamically registered provider for session validation: ${providerConfigId}`);
177
+ }
178
+ }
179
+ catch (error) {
180
+ logger?.error?.(`Error resolving provider ${providerConfigId} for session:`, error.message);
181
+ }
182
+ }
183
+ if (!providerData) {
184
+ logger?.warn?.(`OAuth provider config '${providerConfigId}' not found, logging out user`);
185
+ // Provider no longer exists - complete logout
186
+ await clearOAuthSession(request.session, logger);
187
+ return next(request);
188
+ }
189
+ // Validate and refresh session automatically
190
+ const validation = await validateAndRefreshSession(request, providerData.provider, logger, hookManager);
191
+ if (!validation.valid) {
192
+ // Session is no longer valid (already cleaned up by validator)
193
+ logger?.debug?.(`OAuth session invalidated: ${validation.error}`);
194
+ }
195
+ else if (validation.refreshed) {
196
+ logger?.debug?.(`OAuth token auto-refreshed for ${providerConfigId}`);
197
+ }
198
+ // Continue with the request (session updated if refreshed)
199
+ return next(request);
200
+ });
201
+ // Concurrency control for configuration updates
202
+ let updating = false;
203
+ let pendingUpdate = false;
204
+ /**
205
+ * Run configuration update with concurrency protection
206
+ */
207
+ const runUpdate = async () => {
208
+ if (updating) {
209
+ pendingUpdate = true;
210
+ return;
211
+ }
212
+ updating = true;
213
+ try {
214
+ await updateConfiguration();
215
+ // If another update was requested while we were running, run again
216
+ if (pendingUpdate) {
217
+ pendingUpdate = false;
218
+ await runUpdate();
219
+ }
220
+ }
221
+ catch (error) {
222
+ logger?.error?.('Failed to update OAuth configuration:', error);
223
+ }
224
+ finally {
225
+ updating = false;
226
+ }
227
+ };
228
+ // Initial configuration (errors propagate to plugin loader)
229
+ await updateConfiguration();
230
+ // Watch for configuration changes (errors caught internally)
231
+ scope.options.on('change', () => {
232
+ runUpdate().catch((error) => {
233
+ logger?.error?.('Unexpected error in OAuth config update:', error);
234
+ });
235
+ });
236
+ // Clean up on scope close
237
+ scope.on('close', () => {
238
+ logger?.info?.('OAuth plugin shutting down');
239
+ });
240
+ }
241
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * CSRF Token Manager for OAuth flows
3
+ *
4
+ * Manages CSRF protection tokens that prevent cross-site request forgery
5
+ * during OAuth authorization flows. Tokens are stored in Harper table
6
+ * for distributed access across workers and cluster nodes.
7
+ */
8
+ import type { CSRFTokenData, Logger } from '../types.ts';
9
+ /**
10
+ * Reset the cached CSRF table reference (for testing only)
11
+ * @internal
12
+ */
13
+ export declare function resetCSRFTableCache(): void;
14
+ export declare class CSRFTokenManager {
15
+ private logger?;
16
+ constructor(logger?: Logger);
17
+ /**
18
+ * Store CSRF token with metadata
19
+ * Table expiration is handled by Harper (10 minutes)
20
+ */
21
+ set(token: string, data: CSRFTokenData): Promise<void>;
22
+ /**
23
+ * Retrieve CSRF token
24
+ * Harper automatically handles expiration via table-level setting
25
+ */
26
+ get(token: string): Promise<CSRFTokenData | null>;
27
+ /**
28
+ * Delete CSRF token (after successful verification)
29
+ */
30
+ delete(token: string): Promise<void>;
31
+ }
32
+ export declare const csrfTokenManager: CSRFTokenManager;