@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,156 @@
1
+ /**
2
+ * Shared validation utilities for OAuth providers
3
+ *
4
+ * Security-first validation helpers to prevent SSRF, injection attacks,
5
+ * and other common OAuth configuration vulnerabilities.
6
+ */
7
+ /**
8
+ * Validates that a domain string is safe from common attacks
9
+ *
10
+ * Prevents SSRF attacks by blocking private IPs, localhost, cloud metadata endpoints,
11
+ * and non-HTTP protocols.
12
+ *
13
+ * @param domain - Domain string to validate (e.g., 'example.okta.com' or 'https://example.okta.com')
14
+ * @param providerName - Name of provider for error messages
15
+ * @returns Validated hostname (without protocol)
16
+ * @throws Error if domain is invalid or unsafe
17
+ */
18
+ export function validateDomainSafety(domain, providerName) {
19
+ if (!domain) {
20
+ throw new Error(`${providerName} provider requires domain configuration`);
21
+ }
22
+ // Block non-HTTP protocols (file://, ftp://, etc.)
23
+ if (domain.includes('://') && !domain.startsWith('http://') && !domain.startsWith('https://')) {
24
+ throw new Error(`Invalid ${providerName} domain: ${domain}. Protocol must be http:// or https://`);
25
+ }
26
+ // Check for IPv6 addresses directly (before URL parsing)
27
+ // This catches some forms before normalization
28
+ const ipv6Loopback = /^(::1|0:0:0:0:0:0:0:1)$/i;
29
+ const ipv6LinkLocal = /^fe80:/i;
30
+ if (ipv6Loopback.test(domain) || ipv6LinkLocal.test(domain)) {
31
+ throw new Error(`${providerName} domain cannot be a private IP address or localhost`);
32
+ }
33
+ // Parse domain to extract hostname
34
+ let url;
35
+ try {
36
+ url = new URL(domain.startsWith('http') ? domain : `https://${domain}`);
37
+ }
38
+ catch (error) {
39
+ throw new Error(`Invalid ${providerName} domain: ${domain}. Expected format: 'example.com' or 'https://example.com'`);
40
+ }
41
+ const hostname = url.hostname;
42
+ // Block private IPs, localhost, and cloud metadata endpoints
43
+ // 169.254.169.254 is used by AWS, GCP, Azure, DigitalOcean for instance metadata
44
+ const isPrivateIP = /^(10|127|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\./.test(hostname);
45
+ const isLinkLocal = /^169\.254\./.test(hostname);
46
+ // IPv6: Check after URL parsing for defense-in-depth (URL normalizes various IPv6 forms)
47
+ // This catches ::1 (loopback) and fe80:: (link-local) in any normalized representation
48
+ const isIPv6Private = /^::1$|^fe80:/i.test(hostname);
49
+ if (isPrivateIP || hostname === 'localhost' || isLinkLocal || isIPv6Private) {
50
+ throw new Error(`${providerName} domain cannot be a private IP address or localhost`);
51
+ }
52
+ return hostname;
53
+ }
54
+ /**
55
+ * Validates that a domain matches an allowlist of permitted suffixes
56
+ *
57
+ * Use after validateDomainSafety() to ensure domains match expected patterns
58
+ * (e.g., *.okta.com, *.auth0.com)
59
+ *
60
+ * @param hostname - Validated hostname (from validateDomainSafety)
61
+ * @param allowedSuffixes - Array of allowed domain suffixes (e.g., ['.okta.com', '.okta-emea.com'])
62
+ * @param providerName - Name of provider for error messages
63
+ * @throws Error if hostname doesn't match any allowed suffix
64
+ */
65
+ export function validateDomainAllowlist(hostname, allowedSuffixes, providerName) {
66
+ const isAllowed = allowedSuffixes.some((suffix) => hostname.endsWith(suffix) || hostname === suffix.slice(1));
67
+ if (!isAllowed) {
68
+ const allowedDomains = allowedSuffixes.map((s) => `*${s}`).join(', ');
69
+ throw new Error(`Invalid ${providerName} domain: ${hostname}. Must be one of: ${allowedDomains}`);
70
+ }
71
+ }
72
+ /**
73
+ * Validates email domain format
74
+ *
75
+ * Prevents injection attacks by blocking CRLF, null bytes, and control characters.
76
+ *
77
+ * @param emailDomain - Email domain to validate (e.g., 'example.com')
78
+ * @throws Error if domain contains dangerous characters
79
+ */
80
+ export function validateEmailDomain(emailDomain) {
81
+ if (!emailDomain || typeof emailDomain !== 'string') {
82
+ throw new Error('Email domain must be a non-empty string');
83
+ }
84
+ // Block CRLF, null bytes, and control characters
85
+ if (/[\r\n\0\x00-\x1F\x7F]/.test(emailDomain)) {
86
+ throw new Error('Email domain contains invalid characters');
87
+ }
88
+ // Block malicious dot patterns (check before format validation)
89
+ if (emailDomain.includes('..') || emailDomain.startsWith('.') || emailDomain.endsWith('.')) {
90
+ throw new Error('Email domain contains invalid dot patterns');
91
+ }
92
+ // Basic format validation (permissive to avoid blocking legitimate domains)
93
+ if (!/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(emailDomain)) {
94
+ throw new Error('Email domain must be a valid domain format (e.g., example.com)');
95
+ }
96
+ }
97
+ /**
98
+ * Validates tenant ID format
99
+ *
100
+ * Ensures tenant IDs are safe for URLs and file paths. Enforces length limits
101
+ * and character restrictions.
102
+ *
103
+ * @param tenantId - Tenant ID to validate
104
+ * @throws Error if tenant ID is invalid or unsafe
105
+ */
106
+ export function validateTenantId(tenantId) {
107
+ if (!tenantId || typeof tenantId !== 'string') {
108
+ throw new Error('Tenant ID must be a non-empty string');
109
+ }
110
+ // Enforce length limits (3-64 characters)
111
+ if (tenantId.length < 3 || tenantId.length > 64) {
112
+ throw new Error('Tenant ID must be 3-64 characters long');
113
+ }
114
+ // Allow only alphanumeric characters, hyphens, and underscores
115
+ if (!/^[a-zA-Z0-9_-]+$/.test(tenantId)) {
116
+ throw new Error('Tenant ID must contain only alphanumeric characters, hyphens, and underscores');
117
+ }
118
+ }
119
+ /**
120
+ * Sanitizes tenant name for safe HTML output
121
+ *
122
+ * Prevents XSS attacks by HTML-escaping special characters.
123
+ *
124
+ * @param name - Tenant name to sanitize
125
+ * @returns HTML-escaped tenant name
126
+ */
127
+ export function sanitizeTenantName(name) {
128
+ if (!name || typeof name !== 'string') {
129
+ return '';
130
+ }
131
+ return name
132
+ .replace(/&/g, '&amp;')
133
+ .replace(/</g, '&lt;')
134
+ .replace(/>/g, '&gt;')
135
+ .replace(/"/g, '&quot;')
136
+ .replace(/'/g, '&#x27;')
137
+ .replace(/\//g, '&#x2F;');
138
+ }
139
+ /**
140
+ * Validates Azure tenant ID format
141
+ *
142
+ * Valid formats: GUID, 'common', 'organizations', or 'consumers'
143
+ *
144
+ * @param tenantId - Azure tenant ID to validate
145
+ * @throws Error if tenant ID is invalid
146
+ */
147
+ export function validateAzureTenantId(tenantId) {
148
+ if (!tenantId) {
149
+ throw new Error('Azure AD provider requires tenantId configuration');
150
+ }
151
+ const validTenantId = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|common|organizations|consumers)$/i;
152
+ if (!validTenantId.test(tenantId)) {
153
+ throw new Error(`Invalid Azure tenant ID: ${tenantId}. Must be a GUID or one of: common, organizations, consumers`);
154
+ }
155
+ }
156
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/lib/providers/validation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * OAuth Resource
3
+ *
4
+ * Harper resource class for handling OAuth REST endpoints
5
+ */
6
+ import { Resource } from 'harperdb';
7
+ import type { RequestTarget } from 'harperdb';
8
+ import type { Request, Logger, ProviderRegistry, OAuthProviderConfig } from '../types.ts';
9
+ import type { HookManager } from './hookManager.ts';
10
+ /**
11
+ * Parsed route information from a request target
12
+ */
13
+ export interface ParsedRoute {
14
+ providerName: string;
15
+ action: string;
16
+ path: string;
17
+ }
18
+ /**
19
+ * OAuth Resource - proper Harper Resource class for handling OAuth endpoints
20
+ * Follows Resource API v2 pattern (loadAsInstance = false)
21
+ */
22
+ export declare class OAuthResource extends Resource {
23
+ static loadAsInstance: boolean;
24
+ static providers: ProviderRegistry;
25
+ static debugMode: boolean;
26
+ static hookManager: HookManager | null;
27
+ static pluginDefaults: Partial<OAuthProviderConfig>;
28
+ static logger: Logger | undefined;
29
+ /**
30
+ * Configure the OAuth resource with providers and settings
31
+ * Called once during plugin initialization
32
+ */
33
+ static configure(providers: ProviderRegistry, debugMode: boolean, hookManager: HookManager, pluginDefaults: Partial<OAuthProviderConfig>, logger?: Logger): void;
34
+ /**
35
+ * Parse a request target into provider and action components
36
+ * Exported as static method for testability
37
+ */
38
+ static parseRoute(target: RequestTarget): ParsedRoute;
39
+ /**
40
+ * Check if a route should return 404 in production mode
41
+ * Debug-only endpoints return 404 when debug mode is off
42
+ */
43
+ static isDebugOnlyRoute(route: ParsedRoute): boolean;
44
+ /**
45
+ * Check if a request is allowed to access debug endpoints
46
+ * Uses IP allowlist for security (defaults to localhost only)
47
+ *
48
+ * @param request - The incoming request
49
+ * @param logger - Optional logger for access tracking
50
+ * @returns true if access is allowed, false otherwise
51
+ */
52
+ static checkDebugAccess(request: Request, logger?: Logger): boolean;
53
+ /**
54
+ * Build forbidden response for unauthorized debug access
55
+ */
56
+ static forbiddenResponse(): any;
57
+ /**
58
+ * Build the standard 404 response
59
+ */
60
+ static notFoundResponse(): {
61
+ status: number;
62
+ body: {
63
+ error: string;
64
+ };
65
+ };
66
+ /**
67
+ * Build provider list response for root path
68
+ */
69
+ static buildProviderListResponse(providers: ProviderRegistry): any;
70
+ /**
71
+ * Build provider info response
72
+ */
73
+ static buildProviderInfoResponse(providerName: string, providers: ProviderRegistry): any;
74
+ /**
75
+ * Build token status response for /refresh endpoint
76
+ */
77
+ static buildTokenStatusResponse(request: Request): any;
78
+ /**
79
+ * Handle GET requests to OAuth endpoints
80
+ * Resource API v2 signature: get(target)
81
+ */
82
+ get(target: RequestTarget): Promise<any>;
83
+ /**
84
+ * Handle POST requests to OAuth endpoints
85
+ * Resource API v2 signature: post(target, data)
86
+ */
87
+ post(target: RequestTarget, _data: any): Promise<any>;
88
+ /**
89
+ * Expose hookManager for programmatic hook registration
90
+ * This allows access via: scope.resources.get('oauth').hookManager
91
+ */
92
+ static getHookManager(): HookManager | null;
93
+ /**
94
+ * Expose providers for use with withOAuthValidation
95
+ * This allows access via: scope.resources.get('oauth').providers
96
+ */
97
+ static getProviders(): ProviderRegistry;
98
+ /**
99
+ * Reset configuration (useful for testing)
100
+ */
101
+ static reset(): void;
102
+ }
@@ -0,0 +1,368 @@
1
+ /**
2
+ * OAuth Resource
3
+ *
4
+ * Harper resource class for handling OAuth REST endpoints
5
+ */
6
+ import { Resource } from 'harperdb';
7
+ import { handleLogin, handleCallback, handleLogout, handleUserInfo, handleTestPage } from "./handlers.js";
8
+ /**
9
+ * OAuth Resource - proper Harper Resource class for handling OAuth endpoints
10
+ * Follows Resource API v2 pattern (loadAsInstance = false)
11
+ */
12
+ export class OAuthResource extends Resource {
13
+ static loadAsInstance = false; // Use Resource API v2
14
+ // Store configuration as static properties (shared across all requests)
15
+ static providers = {};
16
+ static debugMode = false;
17
+ static hookManager = null;
18
+ static pluginDefaults = {};
19
+ static logger = undefined;
20
+ /**
21
+ * Configure the OAuth resource with providers and settings
22
+ * Called once during plugin initialization
23
+ */
24
+ static configure(providers, debugMode, hookManager, pluginDefaults, logger) {
25
+ OAuthResource.providers = providers;
26
+ OAuthResource.debugMode = debugMode;
27
+ OAuthResource.hookManager = hookManager;
28
+ OAuthResource.pluginDefaults = pluginDefaults;
29
+ OAuthResource.logger = logger;
30
+ }
31
+ /**
32
+ * Parse a request target into provider and action components
33
+ * Exported as static method for testability
34
+ */
35
+ static parseRoute(target) {
36
+ const id = target.id || target.pathname || '';
37
+ const path = typeof id === 'string' ? id : String(id);
38
+ // Validate path length to prevent DoS attacks with extremely long URLs
39
+ if (path.length > 2048) {
40
+ // Return empty route for oversized paths (will result in 404)
41
+ return {
42
+ providerName: '',
43
+ action: '',
44
+ path: '',
45
+ };
46
+ }
47
+ const pathParts = path.split('/').filter((p) => p);
48
+ return {
49
+ providerName: pathParts[0] || '',
50
+ action: pathParts[1] || '',
51
+ path,
52
+ };
53
+ }
54
+ /**
55
+ * Check if a route should return 404 in production mode
56
+ * Debug-only endpoints return 404 when debug mode is off
57
+ */
58
+ static isDebugOnlyRoute(route) {
59
+ const { providerName, action } = route;
60
+ // Root path (provider list) - debug only
61
+ if (!providerName)
62
+ return true;
63
+ // Test endpoints - debug only
64
+ if (providerName === 'test' && !action)
65
+ return true;
66
+ if (action === 'test')
67
+ return true;
68
+ // Debug info endpoints
69
+ if (action === 'user' || action === 'refresh')
70
+ return true;
71
+ // Provider info (no action) - debug only
72
+ if (providerName && !action)
73
+ return true;
74
+ return false;
75
+ }
76
+ /**
77
+ * Check if a request is allowed to access debug endpoints
78
+ * Uses IP allowlist for security (defaults to localhost only)
79
+ *
80
+ * @param request - The incoming request
81
+ * @param logger - Optional logger for access tracking
82
+ * @returns true if access is allowed, false otherwise
83
+ */
84
+ static checkDebugAccess(request, logger) {
85
+ // Get IP allowlist from environment variable or use localhost-only default
86
+ // Use ?? to allow empty string (which denies all access)
87
+ const DEBUG_ALLOWED_IPS = process.env.DEBUG_ALLOWED_IPS ?? '127.0.0.1,::1';
88
+ const allowedIps = DEBUG_ALLOWED_IPS.split(',').map((ip) => ip.trim());
89
+ const clientIp = request.ip || '';
90
+ // Check if client IP matches any allowed IP
91
+ let ipAllowed = false;
92
+ for (const allowed of allowedIps) {
93
+ // Exact match
94
+ if (allowed === clientIp) {
95
+ ipAllowed = true;
96
+ break;
97
+ }
98
+ // Simple prefix match for CIDR-like patterns (e.g., "10.0.0." matches "10.0.0.1")
99
+ if (allowed.endsWith('.') && clientIp.startsWith(allowed)) {
100
+ ipAllowed = true;
101
+ break;
102
+ }
103
+ }
104
+ // Log access attempt
105
+ if (ipAllowed) {
106
+ logger?.info?.('OAuth debug endpoint accessed', {
107
+ ip: clientIp,
108
+ });
109
+ }
110
+ else {
111
+ logger?.warn?.('OAuth debug endpoint access denied - unauthorized IP', {
112
+ ip: clientIp,
113
+ allowedIps,
114
+ });
115
+ }
116
+ return ipAllowed;
117
+ }
118
+ /**
119
+ * Build forbidden response for unauthorized debug access
120
+ */
121
+ static forbiddenResponse() {
122
+ return {
123
+ status: 403,
124
+ body: {
125
+ error: 'Access forbidden',
126
+ message: 'Debug endpoints are only accessible from allowed IPs.',
127
+ hint: 'Set DEBUG_ALLOWED_IPS environment variable to allow access from your IP. Defaults to localhost only (127.0.0.1,::1).',
128
+ },
129
+ };
130
+ }
131
+ /**
132
+ * Build the standard 404 response
133
+ */
134
+ static notFoundResponse() {
135
+ return {
136
+ status: 404,
137
+ body: { error: 'Not found' },
138
+ };
139
+ }
140
+ /**
141
+ * Build provider list response for root path
142
+ */
143
+ static buildProviderListResponse(providers) {
144
+ return {
145
+ message: 'OAuth providers',
146
+ logout: 'POST /oauth/logout',
147
+ providers: Object.keys(providers).map((name) => ({
148
+ name,
149
+ provider: providers[name].config.provider,
150
+ endpoints: {
151
+ login: `/oauth/${name}/login`,
152
+ callback: `/oauth/${name}/callback`,
153
+ user: `/oauth/${name}/user`,
154
+ refresh: `/oauth/${name}/refresh`,
155
+ test: `/oauth/${name}/test`,
156
+ },
157
+ })),
158
+ };
159
+ }
160
+ /**
161
+ * Build provider info response
162
+ */
163
+ static buildProviderInfoResponse(providerName, providers) {
164
+ const providerData = providers[providerName];
165
+ if (!providerData) {
166
+ return {
167
+ status: 404,
168
+ body: {
169
+ error: 'Provider not found',
170
+ available: Object.keys(providers),
171
+ },
172
+ };
173
+ }
174
+ return {
175
+ message: `OAuth provider: ${providerName}`,
176
+ provider: providerData.config.provider,
177
+ configured: true,
178
+ logout: 'POST /oauth/logout',
179
+ endpoints: {
180
+ login: `/oauth/${providerName}/login`,
181
+ callback: `/oauth/${providerName}/callback`,
182
+ user: `/oauth/${providerName}/user`,
183
+ refresh: `/oauth/${providerName}/refresh`,
184
+ test: `/oauth/${providerName}/test`,
185
+ },
186
+ };
187
+ }
188
+ /**
189
+ * Build token status response for /refresh endpoint
190
+ */
191
+ static buildTokenStatusResponse(request) {
192
+ const oauthData = request.session?.oauth;
193
+ if (!oauthData || !oauthData.accessToken) {
194
+ return {
195
+ status: 401,
196
+ body: {
197
+ error: 'No OAuth session',
198
+ message: 'OAuth session is no longer valid. Please log in again.',
199
+ },
200
+ };
201
+ }
202
+ return {
203
+ status: 200,
204
+ body: {
205
+ message: 'Token is valid',
206
+ provider: oauthData.provider,
207
+ expiresAt: oauthData.expiresAt,
208
+ lastRefreshed: oauthData.lastRefreshed,
209
+ },
210
+ };
211
+ }
212
+ /**
213
+ * Handle GET requests to OAuth endpoints
214
+ * Resource API v2 signature: get(target)
215
+ */
216
+ async get(target) {
217
+ const providers = OAuthResource.providers;
218
+ const debugMode = OAuthResource.debugMode;
219
+ const logger = OAuthResource.logger;
220
+ // Parse the route
221
+ const route = OAuthResource.parseRoute(target);
222
+ const { providerName, action } = route;
223
+ // Get request from context (HarperDB provides the HTTP request here)
224
+ const context = this.getContext();
225
+ if (!context) {
226
+ logger?.error?.('Request context is null or undefined');
227
+ return {
228
+ status: 500,
229
+ body: { error: 'Internal server error' },
230
+ };
231
+ }
232
+ const request = context;
233
+ // Check debug mode restrictions
234
+ if (!debugMode && OAuthResource.isDebugOnlyRoute(route)) {
235
+ return OAuthResource.notFoundResponse();
236
+ }
237
+ // If debug mode is enabled and this is a debug-only route, check IP allowlist
238
+ if (debugMode && OAuthResource.isDebugOnlyRoute(route)) {
239
+ if (!OAuthResource.checkDebugAccess(request, logger)) {
240
+ return OAuthResource.forbiddenResponse();
241
+ }
242
+ }
243
+ // Special case: /oauth/test without provider
244
+ if (providerName === 'test' && !action) {
245
+ return handleTestPage(logger);
246
+ }
247
+ // Root path - show provider list
248
+ if (!providerName) {
249
+ return OAuthResource.buildProviderListResponse(providers);
250
+ }
251
+ // Validate provider name format (basic security check)
252
+ if (providerName.length > 128 || !/^[a-zA-Z0-9_-]+$/.test(providerName)) {
253
+ return {
254
+ status: 400,
255
+ body: { error: 'Invalid provider name format' },
256
+ };
257
+ }
258
+ // Check if provider exists in registry
259
+ let providerData = providers[providerName];
260
+ // If not found, try to resolve via hook
261
+ if (!providerData && OAuthResource.hookManager?.hasHook('onResolveProvider')) {
262
+ try {
263
+ logger?.debug?.(`Provider "${providerName}" not found in registry, calling onResolveProvider hook`);
264
+ const hookConfig = await OAuthResource.hookManager.callResolveProvider(providerName, logger);
265
+ if (hookConfig) {
266
+ // Hook resolved provider - build full config and register dynamically
267
+ const { OAuthProvider } = await import("./OAuthProvider.js");
268
+ const { buildProviderConfig } = await import("./config.js");
269
+ // Build full provider config (handles Okta/Azure/Auth0 domain configuration)
270
+ const pluginDefaults = OAuthResource.pluginDefaults || {};
271
+ const config = buildProviderConfig(hookConfig, providerName, pluginDefaults);
272
+ const provider = new OAuthProvider(config, logger);
273
+ providers[providerName] = {
274
+ provider,
275
+ config,
276
+ };
277
+ providerData = providers[providerName];
278
+ logger?.info?.(`Dynamically registered provider: ${providerName}`);
279
+ }
280
+ }
281
+ catch (error) {
282
+ // Hook threw error - log and return 500
283
+ logger?.error?.(`Error resolving provider ${providerName}:`, error.message);
284
+ return {
285
+ status: 500,
286
+ body: { error: 'Failed to resolve OAuth provider' },
287
+ };
288
+ }
289
+ }
290
+ // Still not found - return 404
291
+ if (!providerData) {
292
+ return {
293
+ status: 404,
294
+ body: { error: 'OAuth provider not found' },
295
+ };
296
+ }
297
+ const { provider, config } = providerData;
298
+ const hookManager = OAuthResource.hookManager;
299
+ // Handle specific actions
300
+ switch (action) {
301
+ case 'login':
302
+ return handleLogin(request, target, provider, config, providerName, logger);
303
+ case 'callback':
304
+ return handleCallback(request, target, provider, config, hookManager, providerName, logger);
305
+ case 'user':
306
+ return handleUserInfo(request, false);
307
+ case 'refresh':
308
+ return OAuthResource.buildTokenStatusResponse(request);
309
+ case 'test':
310
+ return handleTestPage(logger);
311
+ default:
312
+ // Provider info (no action specified)
313
+ return OAuthResource.buildProviderInfoResponse(providerName, providers);
314
+ }
315
+ }
316
+ /**
317
+ * Handle POST requests to OAuth endpoints
318
+ * Resource API v2 signature: post(target, data)
319
+ */
320
+ async post(target, _data) {
321
+ const logger = OAuthResource.logger;
322
+ const hookManager = OAuthResource.hookManager;
323
+ // Parse the route
324
+ const route = OAuthResource.parseRoute(target);
325
+ const { providerName } = route;
326
+ // Get request from context (HarperDB provides the HTTP request here)
327
+ const context = this.getContext();
328
+ if (!context) {
329
+ logger?.error?.('Request context is null or undefined');
330
+ return {
331
+ status: 500,
332
+ body: { error: 'Internal server error' },
333
+ };
334
+ }
335
+ const request = context;
336
+ // Handle logout endpoint
337
+ if (providerName === 'logout') {
338
+ return handleLogout(request, hookManager, logger);
339
+ }
340
+ // All other POST endpoints are not supported
341
+ return OAuthResource.notFoundResponse();
342
+ }
343
+ /**
344
+ * Expose hookManager for programmatic hook registration
345
+ * This allows access via: scope.resources.get('oauth').hookManager
346
+ */
347
+ static getHookManager() {
348
+ return OAuthResource.hookManager;
349
+ }
350
+ /**
351
+ * Expose providers for use with withOAuthValidation
352
+ * This allows access via: scope.resources.get('oauth').providers
353
+ */
354
+ static getProviders() {
355
+ return OAuthResource.providers;
356
+ }
357
+ /**
358
+ * Reset configuration (useful for testing)
359
+ */
360
+ static reset() {
361
+ OAuthResource.providers = {};
362
+ OAuthResource.debugMode = false;
363
+ OAuthResource.hookManager = null;
364
+ OAuthResource.pluginDefaults = {};
365
+ OAuthResource.logger = undefined;
366
+ }
367
+ }
368
+ //# sourceMappingURL=resource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/lib/resource.ts"],"names":[],"mappings":"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"}