@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.
- package/LICENSE +201 -0
- package/README.md +219 -0
- package/assets/test.html +321 -0
- package/config.yaml +23 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +241 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/CSRFTokenManager.d.ts +32 -0
- package/dist/lib/CSRFTokenManager.js +90 -0
- package/dist/lib/CSRFTokenManager.js.map +1 -0
- package/dist/lib/OAuthProvider.d.ts +59 -0
- package/dist/lib/OAuthProvider.js +370 -0
- package/dist/lib/OAuthProvider.js.map +1 -0
- package/dist/lib/config.d.ts +31 -0
- package/dist/lib/config.js +138 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/handlers.d.ts +56 -0
- package/dist/lib/handlers.js +386 -0
- package/dist/lib/handlers.js.map +1 -0
- package/dist/lib/hookManager.d.ts +52 -0
- package/dist/lib/hookManager.js +114 -0
- package/dist/lib/hookManager.js.map +1 -0
- package/dist/lib/providers/auth0.d.ts +8 -0
- package/dist/lib/providers/auth0.js +34 -0
- package/dist/lib/providers/auth0.js.map +1 -0
- package/dist/lib/providers/azure.d.ts +7 -0
- package/dist/lib/providers/azure.js +33 -0
- package/dist/lib/providers/azure.js.map +1 -0
- package/dist/lib/providers/generic.d.ts +7 -0
- package/dist/lib/providers/generic.js +20 -0
- package/dist/lib/providers/generic.js.map +1 -0
- package/dist/lib/providers/github.d.ts +7 -0
- package/dist/lib/providers/github.js +73 -0
- package/dist/lib/providers/github.js.map +1 -0
- package/dist/lib/providers/google.d.ts +7 -0
- package/dist/lib/providers/google.js +27 -0
- package/dist/lib/providers/google.js.map +1 -0
- package/dist/lib/providers/index.d.ts +17 -0
- package/dist/lib/providers/index.js +49 -0
- package/dist/lib/providers/index.js.map +1 -0
- package/dist/lib/providers/okta.d.ts +8 -0
- package/dist/lib/providers/okta.js +45 -0
- package/dist/lib/providers/okta.js.map +1 -0
- package/dist/lib/providers/validation.d.ts +67 -0
- package/dist/lib/providers/validation.js +156 -0
- package/dist/lib/providers/validation.js.map +1 -0
- package/dist/lib/resource.d.ts +102 -0
- package/dist/lib/resource.js +368 -0
- package/dist/lib/resource.js.map +1 -0
- package/dist/lib/sessionValidator.d.ts +38 -0
- package/dist/lib/sessionValidator.js +162 -0
- package/dist/lib/sessionValidator.js.map +1 -0
- package/dist/lib/tenantManager.d.ts +102 -0
- package/dist/lib/tenantManager.js +177 -0
- package/dist/lib/tenantManager.js.map +1 -0
- package/dist/lib/withOAuthValidation.d.ts +64 -0
- package/dist/lib/withOAuthValidation.js +188 -0
- package/dist/lib/withOAuthValidation.js.map +1 -0
- package/dist/types.d.ts +326 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +89 -0
- 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
|
+
}
|