@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,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, '&')
|
|
133
|
+
.replace(/</g, '<')
|
|
134
|
+
.replace(/>/g, '>')
|
|
135
|
+
.replace(/"/g, '"')
|
|
136
|
+
.replace(/'/g, ''')
|
|
137
|
+
.replace(/\//g, '/');
|
|
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":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGpC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAY1G;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,QAAQ;IAC1C,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC,CAAC,sBAAsB;IAErD,wEAAwE;IACxE,MAAM,CAAC,SAAS,GAAqB,EAAE,CAAC;IACxC,MAAM,CAAC,SAAS,GAAY,KAAK,CAAC;IAClC,MAAM,CAAC,WAAW,GAAuB,IAAI,CAAC;IAC9C,MAAM,CAAC,cAAc,GAAiC,EAAE,CAAC;IACzD,MAAM,CAAC,MAAM,GAAuB,SAAS,CAAC;IAE9C;;;OAGG;IACH,MAAM,CAAC,SAAS,CACf,SAA2B,EAC3B,SAAkB,EAClB,WAAwB,EACxB,cAA4C,EAC5C,MAAe;QAEf,aAAa,CAAC,SAAS,GAAG,SAAS,CAAC;QACpC,aAAa,CAAC,SAAS,GAAG,SAAS,CAAC;QACpC,aAAa,CAAC,WAAW,GAAG,WAAW,CAAC;QACxC,aAAa,CAAC,cAAc,GAAG,cAAc,CAAC;QAC9C,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,MAAqB;QACtC,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAEtD,uEAAuE;QACvE,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;YACxB,8DAA8D;YAC9D,OAAO;gBACN,YAAY,EAAE,EAAE;gBAChB,MAAM,EAAE,EAAE;gBACV,IAAI,EAAE,EAAE;aACR,CAAC;QACH,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAEnD,OAAO;YACN,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE;YAChC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE;YAC1B,IAAI;SACJ,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,gBAAgB,CAAC,KAAkB;QACzC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;QAEvC,yCAAyC;QACzC,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QAE/B,8BAA8B;QAC9B,IAAI,YAAY,KAAK,MAAM,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACpD,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QAEnC,uBAAuB;QACvB,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAE3D,yCAAyC;QACzC,IAAI,YAAY,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAEzC,OAAO,KAAK,CAAC;IACd,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAgB,EAAE,MAAe;QACxD,2EAA2E;QAC3E,yDAAyD;QACzD,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,eAAe,CAAC;QAC3E,MAAM,UAAU,GAAG,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC;QAElC,4CAA4C;QAC5C,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;YAClC,cAAc;YACd,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC1B,SAAS,GAAG,IAAI,CAAC;gBACjB,MAAM;YACP,CAAC;YACD,kFAAkF;YAClF,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3D,SAAS,GAAG,IAAI,CAAC;gBACjB,MAAM;YACP,CAAC;QACF,CAAC;QAED,qBAAqB;QACrB,IAAI,SAAS,EAAE,CAAC;YACf,MAAM,EAAE,IAAI,EAAE,CAAC,+BAA+B,EAAE;gBAC/C,EAAE,EAAE,QAAQ;aACZ,CAAC,CAAC;QACJ,CAAC;aAAM,CAAC;YACP,MAAM,EAAE,IAAI,EAAE,CAAC,sDAAsD,EAAE;gBACtE,EAAE,EAAE,QAAQ;gBACZ,UAAU;aACV,CAAC,CAAC;QACJ,CAAC;QAED,OAAO,SAAS,CAAC;IAClB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB;QACvB,OAAO;YACN,MAAM,EAAE,GAAG;YACX,IAAI,EAAE;gBACL,KAAK,EAAE,kBAAkB;gBACzB,OAAO,EAAE,uDAAuD;gBAChE,IAAI,EAAE,sHAAsH;aAC5H;SACD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,gBAAgB;QACtB,OAAO;YACN,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;SAC5B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,yBAAyB,CAAC,SAA2B;QAC3D,OAAO;YACN,OAAO,EAAE,iBAAiB;YAC1B,MAAM,EAAE,oBAAoB;YAC5B,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAChD,IAAI;gBACJ,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ;gBACzC,SAAS,EAAE;oBACV,KAAK,EAAE,UAAU,IAAI,QAAQ;oBAC7B,QAAQ,EAAE,UAAU,IAAI,WAAW;oBACnC,IAAI,EAAE,UAAU,IAAI,OAAO;oBAC3B,OAAO,EAAE,UAAU,IAAI,UAAU;oBACjC,IAAI,EAAE,UAAU,IAAI,OAAO;iBAC3B;aACD,CAAC,CAAC;SACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,yBAAyB,CAAC,YAAoB,EAAE,SAA2B;QACjF,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;QAC7C,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,OAAO;gBACN,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE;oBACL,KAAK,EAAE,oBAAoB;oBAC3B,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;iBACjC;aACD,CAAC;QACH,CAAC;QAED,OAAO;YACN,OAAO,EAAE,mBAAmB,YAAY,EAAE;YAC1C,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,QAAQ;YACtC,UAAU,EAAE,IAAI;YAChB,MAAM,EAAE,oBAAoB;YAC5B,SAAS,EAAE;gBACV,KAAK,EAAE,UAAU,YAAY,QAAQ;gBACrC,QAAQ,EAAE,UAAU,YAAY,WAAW;gBAC3C,IAAI,EAAE,UAAU,YAAY,OAAO;gBACnC,OAAO,EAAE,UAAU,YAAY,UAAU;gBACzC,IAAI,EAAE,UAAU,YAAY,OAAO;aACnC;SACD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,wBAAwB,CAAC,OAAgB;QAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;QACzC,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;YAC1C,OAAO;gBACN,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE;oBACL,KAAK,EAAE,kBAAkB;oBACzB,OAAO,EAAE,wDAAwD;iBACjE;aACD,CAAC;QACH,CAAC;QAED,OAAO;YACN,MAAM,EAAE,GAAG;YACX,IAAI,EAAE;gBACL,OAAO,EAAE,gBAAgB;gBACzB,QAAQ,EAAE,SAAS,CAAC,QAAQ;gBAC5B,SAAS,EAAE,SAAS,CAAC,SAAS;gBAC9B,aAAa,EAAE,SAAS,CAAC,aAAa;aACtC;SACD,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,MAAqB;QAC9B,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC;QAC1C,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC;QAC1C,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;QAEpC,kBAAkB;QAClB,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;QAEvC,qEAAqE;QACrE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,MAAM,EAAE,KAAK,EAAE,CAAC,sCAAsC,CAAC,CAAC;YACxD,OAAO;gBACN,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,EAAE,KAAK,EAAE,uBAAuB,EAAE;aACxC,CAAC;QACH,CAAC;QACD,MAAM,OAAO,GAAG,OAA6B,CAAC;QAE9C,gCAAgC;QAChC,IAAI,CAAC,SAAS,IAAI,aAAa,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;YACzD,OAAO,aAAa,CAAC,gBAAgB,EAAE,CAAC;QACzC,CAAC;QAED,8EAA8E;QAC9E,IAAI,SAAS,IAAI,aAAa,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,IAAI,CAAC,aAAa,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;gBACtD,OAAO,aAAa,CAAC,iBAAiB,EAAE,CAAC;YAC1C,CAAC;QACF,CAAC;QAED,6CAA6C;QAC7C,IAAI,YAAY,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACxC,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC;QAED,iCAAiC;QACjC,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,OAAO,aAAa,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAC;QAC3D,CAAC;QAED,uDAAuD;QACvD,IAAI,YAAY,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YACzE,OAAO;gBACN,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,EAAE,KAAK,EAAE,8BAA8B,EAAE;aAC/C,CAAC;QACH,CAAC;QAED,uCAAuC;QACvC,IAAI,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;QAE3C,wCAAwC;QACxC,IAAI,CAAC,YAAY,IAAI,aAAa,CAAC,WAAW,EAAE,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC9E,IAAI,CAAC;gBACJ,MAAM,EAAE,KAAK,EAAE,CAAC,aAAa,YAAY,yDAAyD,CAAC,CAAC;gBAEpG,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,mBAAmB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;gBAE7F,IAAI,UAAU,EAAE,CAAC;oBAChB,sEAAsE;oBACtE,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;oBAC7D,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;oBAE5D,6EAA6E;oBAC7E,MAAM,cAAc,GAAG,aAAa,CAAC,cAAc,IAAI,EAAE,CAAC;oBAC1D,MAAM,MAAM,GAAG,mBAAmB,CAAC,UAAU,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;oBAE7E,MAAM,QAAQ,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBAEnD,SAAS,CAAC,YAAY,CAAC,GAAG;wBACzB,QAAQ;wBACR,MAAM;qBACN,CAAC;oBAEF,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;oBAEvC,MAAM,EAAE,IAAI,EAAE,CAAC,oCAAoC,YAAY,EAAE,CAAC,CAAC;gBACpE,CAAC;YACF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,wCAAwC;gBACxC,MAAM,EAAE,KAAK,EAAE,CAAC,4BAA4B,YAAY,GAAG,EAAG,KAAe,CAAC,OAAO,CAAC,CAAC;gBACvF,OAAO;oBACN,MAAM,EAAE,GAAG;oBACX,IAAI,EAAE,EAAE,KAAK,EAAE,kCAAkC,EAAE;iBACnD,CAAC;YACH,CAAC;QACF,CAAC;QAED,+BAA+B;QAC/B,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,OAAO;gBACN,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE;aAC3C,CAAC;QACH,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC;QAC1C,MAAM,WAAW,GAAG,aAAa,CAAC,WAAY,CAAC;QAE/C,0BAA0B;QAC1B,QAAQ,MAAM,EAAE,CAAC;YAChB,KAAK,OAAO;gBACX,OAAO,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;YAE7E,KAAK,UAAU;gBACd,OAAO,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;YAE7F,KAAK,MAAM;gBACV,OAAO,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAEvC,KAAK,SAAS;gBACb,OAAO,aAAa,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;YAExD,KAAK,MAAM;gBACV,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;YAE/B;gBACC,sCAAsC;gBACtC,OAAO,aAAa,CAAC,yBAAyB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC1E,CAAC;IACF,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI,CAAC,MAAqB,EAAE,KAAU;QAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;QACpC,MAAM,WAAW,GAAG,aAAa,CAAC,WAAY,CAAC;QAE/C,kBAAkB;QAClB,MAAM,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;QAE/B,qEAAqE;QACrE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,MAAM,EAAE,KAAK,EAAE,CAAC,sCAAsC,CAAC,CAAC;YACxD,OAAO;gBACN,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,EAAE,KAAK,EAAE,uBAAuB,EAAE;aACxC,CAAC;QACH,CAAC;QACD,MAAM,OAAO,GAAG,OAA6B,CAAC;QAE9C,yBAAyB;QACzB,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC/B,OAAO,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC;QAED,6CAA6C;QAC7C,OAAO,aAAa,CAAC,gBAAgB,EAAE,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,cAAc;QACpB,OAAO,aAAa,CAAC,WAAW,CAAC;IAClC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,YAAY;QAClB,OAAO,aAAa,CAAC,SAAS,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK;QACX,aAAa,CAAC,SAAS,GAAG,EAAE,CAAC;QAC7B,aAAa,CAAC,SAAS,GAAG,KAAK,CAAC;QAChC,aAAa,CAAC,WAAW,GAAG,IAAI,CAAC;QACjC,aAAa,CAAC,cAAc,GAAG,EAAE,CAAC;QAClC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC;IAClC,CAAC"}
|