@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,138 @@
1
+ /**
2
+ * OAuth Configuration
3
+ *
4
+ * Provider configuration and initialization utilities
5
+ */
6
+ import { OAuthProvider } from "./OAuthProvider.js";
7
+ import { getProvider } from "./providers/index.js";
8
+ /**
9
+ * Expand environment variable in a string value
10
+ *
11
+ * If the value is a string in the format `${VAR_NAME}`, it will be replaced
12
+ * with the value of the environment variable. Non-string values are returned unchanged.
13
+ *
14
+ * @example
15
+ * expandEnvVar('${MY_VAR}') // Returns process.env.MY_VAR or '${MY_VAR}' if undefined
16
+ * expandEnvVar('literal') // Returns 'literal'
17
+ * expandEnvVar(123) // Returns 123
18
+ * expandEnvVar(true) // Returns true
19
+ */
20
+ export function expandEnvVar(value) {
21
+ if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) {
22
+ // Extract environment variable name
23
+ const envVar = value.slice(2, -1);
24
+ const envValue = process.env[envVar];
25
+ // Only use env value if it exists (even if empty string)
26
+ return envValue !== undefined ? envValue : value;
27
+ }
28
+ return value;
29
+ }
30
+ /**
31
+ * Build configuration for a specific provider
32
+ */
33
+ export function buildProviderConfig(providerConfig, providerName, pluginDefaults = {}) {
34
+ const options = providerConfig || {};
35
+ // Expand environment variables in config values
36
+ const expandedOptions = {};
37
+ for (const [key, value] of Object.entries(options)) {
38
+ expandedOptions[key] = expandEnvVar(value);
39
+ }
40
+ // Check for known provider presets
41
+ const providerType = expandedOptions.provider || providerName;
42
+ const providerPreset = providerType ? getProvider(providerType) : null;
43
+ // Build redirect URI with provider name in path
44
+ const baseRedirectUri = expandedOptions.redirectUri || pluginDefaults.redirectUri || 'https://localhost:9953/oauth';
45
+ const redirectUri = baseRedirectUri
46
+ .replace('/oauth/callback', `/oauth/${providerName}/callback`)
47
+ .replace(/\/oauth$/, `/oauth/${providerName}/callback`);
48
+ // Merge configurations: plugin defaults -> preset -> options
49
+ const config = {
50
+ // Plugin defaults
51
+ scope: pluginDefaults.scope || 'openid profile email',
52
+ usernameClaim: pluginDefaults.usernameClaim || 'email',
53
+ defaultRole: pluginDefaults.defaultRole || 'user',
54
+ postLoginRedirect: pluginDefaults.postLoginRedirect || '/',
55
+ // Provider type
56
+ provider: 'generic',
57
+ // Required fields (will be overridden if present)
58
+ clientId: '',
59
+ clientSecret: '',
60
+ authorizationUrl: '',
61
+ tokenUrl: '',
62
+ userInfoUrl: '',
63
+ // Provider preset (if available)
64
+ ...providerPreset,
65
+ // Provider-specific options (with expanded env vars)
66
+ ...expandedOptions,
67
+ // Ensure redirect URI includes provider name (override any previous value)
68
+ redirectUri,
69
+ };
70
+ // Handle provider-specific configuration
71
+ if (providerPreset?.configure) {
72
+ let providerConfig;
73
+ switch (config.provider) {
74
+ case 'azure':
75
+ if (expandedOptions.tenantId) {
76
+ providerConfig = providerPreset.configure(expandedOptions.tenantId);
77
+ }
78
+ break;
79
+ case 'auth0':
80
+ case 'okta':
81
+ if (expandedOptions.domain) {
82
+ providerConfig = providerPreset.configure(expandedOptions.domain);
83
+ }
84
+ break;
85
+ }
86
+ if (providerConfig) {
87
+ Object.assign(config, providerConfig);
88
+ }
89
+ }
90
+ return config;
91
+ }
92
+ /**
93
+ * Extract plugin-level defaults from options
94
+ */
95
+ export function extractPluginDefaults(options) {
96
+ const pluginDefaults = {};
97
+ // Copy all non-provider config to defaults, expanding environment variables
98
+ for (const [key, value] of Object.entries(options)) {
99
+ if (key !== 'providers' && key !== 'debug') {
100
+ pluginDefaults[key] = expandEnvVar(value);
101
+ }
102
+ }
103
+ return pluginDefaults;
104
+ }
105
+ /**
106
+ * Initialize OAuth providers from configuration
107
+ */
108
+ export function initializeProviders(options, logger) {
109
+ const providers = {};
110
+ // Providers configuration is required
111
+ if (!options.providers || typeof options.providers !== 'object') {
112
+ return providers;
113
+ }
114
+ // Extract plugin-level defaults
115
+ const pluginDefaults = extractPluginDefaults(options);
116
+ logger?.debug?.('Plugin defaults:', pluginDefaults);
117
+ // Initialize each provider
118
+ for (const [providerName, providerConfig] of Object.entries(options.providers)) {
119
+ const config = buildProviderConfig(providerConfig, providerName, pluginDefaults);
120
+ // Check if this provider is properly configured
121
+ const requiredFields = ['clientId', 'clientSecret', 'authorizationUrl', 'tokenUrl', 'userInfoUrl'];
122
+ const missingFields = requiredFields.filter((key) => !config[key]);
123
+ if (missingFields.length > 0) {
124
+ logger?.warn?.(`OAuth provider '${providerName}' not configured. Missing: ${missingFields.join(', ')}`);
125
+ continue;
126
+ }
127
+ try {
128
+ const provider = new OAuthProvider(config, logger);
129
+ providers[providerName] = { provider, config };
130
+ logger?.info?.(`OAuth provider '${providerName}' initialized (${config.provider})`);
131
+ }
132
+ catch (error) {
133
+ logger?.error?.(`Failed to initialize OAuth provider '${providerName}':`, error);
134
+ }
135
+ }
136
+ return providers;
137
+ }
138
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * OAuth Endpoint Handlers
3
+ *
4
+ * Handler functions for OAuth authentication endpoints
5
+ */
6
+ import type { RequestTarget } from 'harperdb';
7
+ import type { Request, Logger, IOAuthProvider, OAuthProviderConfig } from '../types.ts';
8
+ import type { HookManager } from './hookManager.ts';
9
+ /**
10
+ * Sanitize a redirect parameter to prevent open redirect attacks
11
+ *
12
+ * Takes a user-provided redirect URL and extracts only the path portion,
13
+ * stripping any protocol, domain, or port information.
14
+ *
15
+ * Blocks dangerous protocols like javascript:, data:, vbscript:, and file:
16
+ * to prevent XSS and other injection attacks.
17
+ *
18
+ * @param redirectParam - User-provided redirect URL (may be absolute, relative, or protocol-relative)
19
+ * @returns Safe relative path (pathname + search + hash), or '/' if invalid
20
+ *
21
+ * @example
22
+ * sanitizeRedirect('https://evil.com/phish') // '/phish'
23
+ * sanitizeRedirect('//evil.com/phish') // '/phish'
24
+ * sanitizeRedirect('/dashboard') // '/dashboard'
25
+ * sanitizeRedirect('javascript:alert(1)') // '/'
26
+ * sanitizeRedirect('invalid') // '/'
27
+ */
28
+ export declare function sanitizeRedirect(redirectParam: string): string;
29
+ /**
30
+ * Handle OAuth login initiation
31
+ */
32
+ export declare function handleLogin(request: Request, target: RequestTarget, provider: IOAuthProvider, config: OAuthProviderConfig, providerName: string, logger?: Logger): Promise<any>;
33
+ /**
34
+ * Handle OAuth callback from provider
35
+ */
36
+ export declare function handleCallback(request: Request, target: RequestTarget, provider: IOAuthProvider, config: OAuthProviderConfig, hookManager: HookManager, providerName: string, logger?: Logger): Promise<any>;
37
+ /**
38
+ * Clear OAuth session data and log out the user
39
+ * Shared function for explicit logout and automatic logout on token expiration
40
+ *
41
+ * Deletes the session record from the hdb_session table, completely removing it
42
+ * rather than just clearing the user field. This ensures no orphaned sessions remain.
43
+ */
44
+ export declare function clearOAuthSession(session: any, logger?: Logger): Promise<void>;
45
+ /**
46
+ * Handle user logout
47
+ */
48
+ export declare function handleLogout(request: Request, hookManager: HookManager, logger?: Logger): Promise<any>;
49
+ /**
50
+ * Get current user info
51
+ */
52
+ export declare function handleUserInfo(request: Request, tokenRefreshed?: boolean): Promise<any>;
53
+ /**
54
+ * Serve OAuth test page
55
+ */
56
+ export declare function handleTestPage(logger?: Logger): Promise<any>;
@@ -0,0 +1,386 @@
1
+ /**
2
+ * OAuth Endpoint Handlers
3
+ *
4
+ * Handler functions for OAuth authentication endpoints
5
+ */
6
+ import { readFile } from 'node:fs/promises';
7
+ import { join, dirname } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ /**
10
+ * Sanitize a redirect parameter to prevent open redirect attacks
11
+ *
12
+ * Takes a user-provided redirect URL and extracts only the path portion,
13
+ * stripping any protocol, domain, or port information.
14
+ *
15
+ * Blocks dangerous protocols like javascript:, data:, vbscript:, and file:
16
+ * to prevent XSS and other injection attacks.
17
+ *
18
+ * @param redirectParam - User-provided redirect URL (may be absolute, relative, or protocol-relative)
19
+ * @returns Safe relative path (pathname + search + hash), or '/' if invalid
20
+ *
21
+ * @example
22
+ * sanitizeRedirect('https://evil.com/phish') // '/phish'
23
+ * sanitizeRedirect('//evil.com/phish') // '/phish'
24
+ * sanitizeRedirect('/dashboard') // '/dashboard'
25
+ * sanitizeRedirect('javascript:alert(1)') // '/'
26
+ * sanitizeRedirect('invalid') // '/'
27
+ */
28
+ export function sanitizeRedirect(redirectParam) {
29
+ try {
30
+ const url = new URL(redirectParam, 'http://localhost');
31
+ // Block dangerous protocols
32
+ // These protocols can be used for XSS, file access, or other attacks
33
+ const dangerousProtocols = ['javascript:', 'data:', 'vbscript:', 'file:'];
34
+ if (dangerousProtocols.some((proto) => url.protocol === proto)) {
35
+ return '/';
36
+ }
37
+ const sanitized = url.pathname + url.search + url.hash;
38
+ // Additional validation: result must start with /
39
+ if (!sanitized.startsWith('/')) {
40
+ return '/';
41
+ }
42
+ return sanitized;
43
+ }
44
+ catch (error) {
45
+ // Invalid URL - return safe default
46
+ return '/';
47
+ }
48
+ }
49
+ /**
50
+ * Build a safe error redirect URL
51
+ *
52
+ * Sanitizes the redirect path, then appends error query params using the URL API
53
+ * so params are always placed before any hash fragment.
54
+ */
55
+ function buildErrorRedirect(rawUrl, params) {
56
+ const safePath = sanitizeRedirect(rawUrl);
57
+ const url = new URL(safePath, 'http://localhost');
58
+ for (const [key, value] of Object.entries(params)) {
59
+ url.searchParams.set(key, value);
60
+ }
61
+ return url.pathname + url.search + url.hash;
62
+ }
63
+ /**
64
+ * Handle OAuth login initiation
65
+ */
66
+ export async function handleLogin(request, target, provider, config, providerName, logger) {
67
+ // Determine redirect URL: query param > referer header > config default
68
+ let redirectParam = target.get?.('redirect');
69
+ // Sanitize redirect parameter to prevent open redirect attacks
70
+ if (redirectParam) {
71
+ redirectParam = sanitizeRedirect(redirectParam);
72
+ }
73
+ const referer = request.headers?.referer ? sanitizeRedirect(request.headers.referer) : undefined;
74
+ const originalUrl = redirectParam || referer || config.postLoginRedirect || '/';
75
+ // Generate CSRF token with metadata
76
+ // Bind token to provider to prevent cross-provider CSRF attacks
77
+ const csrfToken = await provider.generateCSRFToken({
78
+ originalUrl,
79
+ sessionId: request.session?.id,
80
+ providerName, // Bind state token to this provider
81
+ });
82
+ // Build authorization URL with CSRF token as state parameter
83
+ const authUrl = provider.getAuthorizationUrl(csrfToken, config.redirectUri || '');
84
+ logger?.info?.(`OAuth login initiated for session: ${request.session?.id}`);
85
+ return {
86
+ status: 302,
87
+ headers: {
88
+ Location: authUrl,
89
+ },
90
+ };
91
+ }
92
+ /**
93
+ * Handle OAuth callback from provider
94
+ */
95
+ export async function handleCallback(request, target, provider, config, hookManager, providerName, logger) {
96
+ // Get query parameters from target
97
+ const code = target.get?.('code');
98
+ const state = target.get?.('state');
99
+ const error = target.get?.('error');
100
+ const errorDescription = target.get?.('error_description');
101
+ // Handle OAuth errors from provider
102
+ if (error) {
103
+ logger?.error?.(`OAuth error: ${error} - ${errorDescription}`);
104
+ const errorUrl = buildErrorRedirect(config.postLoginRedirect || '/', { error: 'oauth_failed', reason: error });
105
+ return {
106
+ status: 302,
107
+ headers: {
108
+ Location: errorUrl,
109
+ },
110
+ };
111
+ }
112
+ // Validate parameters
113
+ if (!code || !state) {
114
+ logger?.warn?.('Missing required OAuth callback parameters');
115
+ const errorUrl = buildErrorRedirect(config.postLoginRedirect || '/', { error: 'invalid_request' });
116
+ return {
117
+ status: 302,
118
+ headers: {
119
+ Location: errorUrl,
120
+ },
121
+ };
122
+ }
123
+ // Verify CSRF token
124
+ const tokenData = await provider.verifyCSRFToken(state);
125
+ if (!tokenData) {
126
+ logger?.warn?.('Invalid or expired CSRF token');
127
+ // Redirect back to login with error parameter
128
+ const loginUrl = `/oauth/${providerName}/login?error=session_expired`;
129
+ return {
130
+ status: 302,
131
+ headers: {
132
+ Location: loginUrl,
133
+ },
134
+ };
135
+ }
136
+ // Verify state token was issued for THIS provider (prevents cross-provider attacks)
137
+ if (tokenData.providerName !== providerName) {
138
+ logger?.warn?.(`State token provider mismatch: token issued for '${tokenData.providerName}', callback for '${providerName}'`);
139
+ // This could be an attack - redirect back to original URL with error
140
+ // Do NOT redirect to login endpoint as that would restart OAuth flow
141
+ const errorUrl = buildErrorRedirect(tokenData.originalUrl || config.postLoginRedirect || '/', {
142
+ error: 'auth_failed',
143
+ reason: 'csrf',
144
+ });
145
+ return {
146
+ status: 302,
147
+ headers: {
148
+ Location: errorUrl,
149
+ },
150
+ };
151
+ }
152
+ try {
153
+ // Exchange code for tokens
154
+ const tokenResponse = await provider.exchangeCodeForToken(code, config.redirectUri || '');
155
+ // Verify ID token if present (OIDC flow)
156
+ let idTokenClaims = null;
157
+ if (tokenResponse.id_token) {
158
+ try {
159
+ idTokenClaims = provider.verifyIdToken ? await provider.verifyIdToken(tokenResponse.id_token) : null;
160
+ logger?.info?.('ID token verified successfully');
161
+ }
162
+ catch (error) {
163
+ // Log verification failure but continue with userinfo endpoint
164
+ logger?.warn?.('ID token verification failed, falling back to userinfo endpoint:', error.message);
165
+ }
166
+ }
167
+ // Get user info (will use ID token claims if available and verified)
168
+ const userInfo = await provider.getUserInfo(tokenResponse.access_token, idTokenClaims);
169
+ // Map to Harper user
170
+ const user = provider.mapUserToHarper(userInfo);
171
+ // Call onLogin hook before storing session
172
+ // This allows user provisioning plugins to create/update user records
173
+ // Pass providerName (registry key) not config.provider (provider type) for multi-tenant support
174
+ const hookData = await hookManager.callOnLogin(user, tokenResponse, request.session, request, providerName);
175
+ // Store in session if available
176
+ if (request.session) {
177
+ // Calculate token expiration and refresh thresholds
178
+ // For providers that don't return expires_in (like GitHub), tokens don't expire
179
+ // so we don't set expiration/refresh thresholds to avoid premature session cleanup
180
+ const now = Date.now();
181
+ let expiresAt;
182
+ let refreshThreshold;
183
+ if (tokenResponse.expires_in) {
184
+ // Token has expiration - calculate thresholds
185
+ const expiresIn = tokenResponse.expires_in;
186
+ expiresAt = now + expiresIn * 1000;
187
+ refreshThreshold = now + expiresIn * 800; // Refresh at 80% of lifetime
188
+ }
189
+ // else: No expires_in means token doesn't expire (e.g., GitHub)
190
+ // Leave expiresAt and refreshThreshold undefined so middleware doesn't try to refresh
191
+ // Prepare session data
192
+ const sessionData = {
193
+ user: hookData?.user || user.username, // Use hook's user if provided, otherwise OAuth username
194
+ oauthUser: user, // Store full OAuth user object separately
195
+ oauth: {
196
+ provider: providerName, // Config key (backwards compatible - e.g., 'my-custom-github', 'production-okta')
197
+ providerConfigId: providerName, // Config key/ID (clearer naming for new code)
198
+ providerType: config.provider, // Provider type (e.g., 'github', 'okta')
199
+ accessToken: tokenResponse.access_token,
200
+ refreshToken: tokenResponse.refresh_token,
201
+ expiresAt,
202
+ refreshThreshold,
203
+ scope: tokenResponse.scope,
204
+ tokenType: tokenResponse.token_type || 'Bearer',
205
+ lastRefreshed: now,
206
+ },
207
+ };
208
+ // Merge remaining hook data into session if provided (excluding 'user' since we already used it)
209
+ if (hookData) {
210
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, sonarjs/no-unused-vars
211
+ const { user, ...remainingHookData } = hookData;
212
+ Object.assign(sessionData, remainingHookData);
213
+ }
214
+ // Store user info and OAuth metadata in session
215
+ if (typeof request.session.update === 'function') {
216
+ await request.session.update(sessionData);
217
+ }
218
+ else {
219
+ Object.assign(request.session, sessionData);
220
+ }
221
+ logger?.info?.(`OAuth login successful for user: ${user.username}${tokenResponse.expires_in ? `, token expires in ${tokenResponse.expires_in}s` : ', token does not expire'}`);
222
+ }
223
+ else {
224
+ logger?.warn?.('No session available for OAuth user');
225
+ }
226
+ // Redirect to original URL or default (sanitize to prevent open redirect)
227
+ return {
228
+ status: 302,
229
+ headers: {
230
+ Location: sanitizeRedirect(tokenData.originalUrl || config.postLoginRedirect || '/'),
231
+ },
232
+ };
233
+ }
234
+ catch (error) {
235
+ logger?.error?.('OAuth callback error:', error);
236
+ // Use a safe, generic reason code — details are in the server log
237
+ const message = error.message || '';
238
+ let reason = 'unknown';
239
+ if (message.startsWith('Token exchange failed'))
240
+ reason = 'token_exchange';
241
+ else if (message.includes('claim'))
242
+ reason = 'user_mapping';
243
+ else if (message.includes('user info') || message.includes('userinfo'))
244
+ reason = 'user_info';
245
+ else if (message.includes('hook') || message.includes('onLogin'))
246
+ reason = 'login_hook';
247
+ const errorUrl = buildErrorRedirect(tokenData.originalUrl || config.postLoginRedirect || '/', {
248
+ error: 'auth_failed',
249
+ reason,
250
+ });
251
+ return {
252
+ status: 302,
253
+ headers: {
254
+ Location: errorUrl,
255
+ },
256
+ };
257
+ }
258
+ }
259
+ /**
260
+ * Clear OAuth session data and log out the user
261
+ * Shared function for explicit logout and automatic logout on token expiration
262
+ *
263
+ * Deletes the session record from the hdb_session table, completely removing it
264
+ * rather than just clearing the user field. This ensures no orphaned sessions remain.
265
+ */
266
+ export async function clearOAuthSession(session, logger) {
267
+ if (!session)
268
+ return;
269
+ // Delete the session record from the hdb_session table
270
+ // This completely removes the session on logout, rather than just nulling the user field
271
+ if (typeof session.delete === 'function') {
272
+ await session.delete(session.id);
273
+ }
274
+ else {
275
+ // Fallback for sessions without delete method - clear in-memory
276
+ session.user = null;
277
+ delete session.oauth;
278
+ delete session.oauthUser;
279
+ }
280
+ logger?.info?.('OAuth session cleared');
281
+ }
282
+ /**
283
+ * Handle user logout
284
+ */
285
+ export async function handleLogout(request, hookManager, logger) {
286
+ // Call onLogout hook before clearing session
287
+ await hookManager.callOnLogout(request.session, request);
288
+ // Clear the OAuth session
289
+ await clearOAuthSession(request.session, logger);
290
+ return {
291
+ status: 200,
292
+ body: { message: 'Logged out successfully' },
293
+ };
294
+ }
295
+ /**
296
+ * Get current user info
297
+ */
298
+ export async function handleUserInfo(request, tokenRefreshed = false) {
299
+ // Add debug logging
300
+ if (!request) {
301
+ return {
302
+ status: 500,
303
+ body: { error: 'Request object not provided' },
304
+ };
305
+ }
306
+ // Check for OAuth user in session first, then Harper user
307
+ const oauthUser = request?.session?.oauthUser;
308
+ const oauthMetadata = request?.session?.oauth;
309
+ const username = request?.user || request?.session?.user;
310
+ if (!username && !oauthUser) {
311
+ return {
312
+ status: 401,
313
+ body: {
314
+ authenticated: false,
315
+ message: 'Not authenticated',
316
+ },
317
+ };
318
+ }
319
+ // If we have OAuth user details, use those
320
+ if (oauthUser) {
321
+ return {
322
+ status: 200,
323
+ body: {
324
+ authenticated: true,
325
+ username: oauthUser.username,
326
+ role: oauthUser.role,
327
+ email: oauthUser.email,
328
+ name: oauthUser.name,
329
+ provider: oauthUser.provider,
330
+ // Include OAuth token status in debug mode
331
+ oauth: oauthMetadata
332
+ ? {
333
+ provider: oauthMetadata.provider,
334
+ providerConfigId: oauthMetadata.providerConfigId,
335
+ providerType: oauthMetadata.providerType,
336
+ expiresAt: oauthMetadata.expiresAt,
337
+ refreshThreshold: oauthMetadata.refreshThreshold,
338
+ lastRefreshed: oauthMetadata.lastRefreshed,
339
+ hasRefreshToken: !!oauthMetadata.refreshToken,
340
+ tokenRefreshed,
341
+ }
342
+ : undefined,
343
+ },
344
+ };
345
+ }
346
+ // Fall back to Harper user - extract just the username string and role name
347
+ const usernameString = typeof username === 'string' ? username : username?.username;
348
+ const roleData = typeof username === 'object' ? username?.role : request?.user?.role;
349
+ // Extract role name - Harper roles can be objects with id/name or just strings
350
+ const roleName = typeof roleData === 'string' ? roleData : roleData?.id || roleData?.name || 'user';
351
+ return {
352
+ status: 200,
353
+ body: {
354
+ authenticated: true,
355
+ username: usernameString,
356
+ role: roleName,
357
+ email: null,
358
+ name: null,
359
+ provider: 'harper',
360
+ },
361
+ };
362
+ }
363
+ /**
364
+ * Serve OAuth test page
365
+ */
366
+ export async function handleTestPage(logger) {
367
+ try {
368
+ const __dirname = dirname(fileURLToPath(import.meta.url));
369
+ const testHtml = await readFile(join(__dirname, '..', '..', 'assets', 'test.html'), 'utf8');
370
+ return {
371
+ status: 200,
372
+ headers: {
373
+ 'Content-Type': 'text/html',
374
+ },
375
+ body: testHtml,
376
+ };
377
+ }
378
+ catch (error) {
379
+ logger?.error?.('Failed to load test page:', error);
380
+ return {
381
+ status: 500,
382
+ body: { error: 'Failed to load test page' },
383
+ };
384
+ }
385
+ }
386
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/lib/handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * OAuth Hook Manager
3
+ *
4
+ * Manages loading and calling lifecycle hooks for the OAuth plugin
5
+ */
6
+ import type { OAuthHooks, OAuthUser, TokenResponse, Logger, OAuthProviderConfig } from '../types.ts';
7
+ /**
8
+ * Hook Manager
9
+ * Loads and executes OAuth lifecycle hooks
10
+ */
11
+ export declare class HookManager {
12
+ private hooks;
13
+ private logger?;
14
+ constructor(logger?: Logger);
15
+ /**
16
+ * Register hooks programmatically
17
+ * Allows applications to register hooks directly without using config
18
+ */
19
+ register(hooks: OAuthHooks): void;
20
+ /**
21
+ * Call onLogin hook
22
+ */
23
+ callOnLogin(oauthUser: OAuthUser, tokenResponse: TokenResponse, session: any, request: any, provider: string): Promise<Record<string, any> | void>;
24
+ /**
25
+ * Call onLogout hook
26
+ */
27
+ callOnLogout(session: any, request: any): Promise<void>;
28
+ /**
29
+ * Call onTokenRefresh hook
30
+ */
31
+ callOnTokenRefresh(session: any, refreshed: boolean, request?: any): Promise<void>;
32
+ /**
33
+ * Check if a specific hook is registered
34
+ */
35
+ hasHook(hookName: keyof OAuthHooks): boolean;
36
+ /**
37
+ * Check if any hooks are loaded
38
+ */
39
+ hasHooks(): boolean;
40
+ /**
41
+ * Call onResolveProvider hook
42
+ *
43
+ * Called when a provider is not found in the static registry.
44
+ * Allows applications to dynamically resolve provider configurations.
45
+ *
46
+ * @param providerName - Provider name from URL path (e.g., "okta-org_abc123")
47
+ * @param logger - Optional logger instance
48
+ * @returns Provider configuration or null if not found
49
+ * @throws Error if resolution fails
50
+ */
51
+ callResolveProvider(providerName: string, logger?: Logger): Promise<OAuthProviderConfig | null>;
52
+ }