@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,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Session Validation and Token Refresh
|
|
3
|
+
*
|
|
4
|
+
* Handles automatic validation and refresh of OAuth tokens in sessions
|
|
5
|
+
*/
|
|
6
|
+
import type { Request, IOAuthProvider, Logger } from '../types.ts';
|
|
7
|
+
import type { HookManager } from './hookManager.ts';
|
|
8
|
+
export interface SessionValidationResult {
|
|
9
|
+
/** Whether the session has valid OAuth data */
|
|
10
|
+
valid: boolean;
|
|
11
|
+
/** Whether tokens were refreshed during validation */
|
|
12
|
+
refreshed?: boolean;
|
|
13
|
+
/** Error message if validation failed */
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validate OAuth session and refresh tokens if needed
|
|
18
|
+
*
|
|
19
|
+
* This function checks if OAuth tokens in the session are expired or approaching
|
|
20
|
+
* expiration, and automatically refreshes them if possible.
|
|
21
|
+
*
|
|
22
|
+
* Token refresh strategy:
|
|
23
|
+
* - If token is expired (past expiresAt), refresh immediately
|
|
24
|
+
* - If token is approaching expiration (past refreshThreshold = 80% of lifetime), refresh proactively
|
|
25
|
+
* - If no refresh token available and token is expired, clear OAuth session data
|
|
26
|
+
*
|
|
27
|
+
* @param request - Harper request object with session
|
|
28
|
+
* @param provider - OAuth provider instance for token refresh
|
|
29
|
+
* @param logger - Optional logger for debugging
|
|
30
|
+
* @param hookManager - Optional hook manager for calling onTokenRefresh hook
|
|
31
|
+
* @returns Validation result indicating if session is valid and if refresh occurred
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateAndRefreshSession(request: Request, provider: IOAuthProvider, logger?: Logger, hookManager?: HookManager): Promise<SessionValidationResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a session has valid OAuth authentication
|
|
36
|
+
* Does not refresh tokens, only checks validity
|
|
37
|
+
*/
|
|
38
|
+
export declare function hasValidOAuthSession(request: Request): boolean;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Session Validation and Token Refresh
|
|
3
|
+
*
|
|
4
|
+
* Handles automatic validation and refresh of OAuth tokens in sessions
|
|
5
|
+
*/
|
|
6
|
+
import { clearOAuthSession } from "./handlers.js";
|
|
7
|
+
/**
|
|
8
|
+
* Validate OAuth session and refresh tokens if needed
|
|
9
|
+
*
|
|
10
|
+
* This function checks if OAuth tokens in the session are expired or approaching
|
|
11
|
+
* expiration, and automatically refreshes them if possible.
|
|
12
|
+
*
|
|
13
|
+
* Token refresh strategy:
|
|
14
|
+
* - If token is expired (past expiresAt), refresh immediately
|
|
15
|
+
* - If token is approaching expiration (past refreshThreshold = 80% of lifetime), refresh proactively
|
|
16
|
+
* - If no refresh token available and token is expired, clear OAuth session data
|
|
17
|
+
*
|
|
18
|
+
* @param request - Harper request object with session
|
|
19
|
+
* @param provider - OAuth provider instance for token refresh
|
|
20
|
+
* @param logger - Optional logger for debugging
|
|
21
|
+
* @param hookManager - Optional hook manager for calling onTokenRefresh hook
|
|
22
|
+
* @returns Validation result indicating if session is valid and if refresh occurred
|
|
23
|
+
*/
|
|
24
|
+
export async function validateAndRefreshSession(request, provider, logger, hookManager) {
|
|
25
|
+
const session = request.session;
|
|
26
|
+
// No session available
|
|
27
|
+
if (!session) {
|
|
28
|
+
return { valid: false, error: 'No session available' };
|
|
29
|
+
}
|
|
30
|
+
// Check for OAuth metadata in session
|
|
31
|
+
const oauthMetadata = session.oauth;
|
|
32
|
+
if (!oauthMetadata) {
|
|
33
|
+
// No OAuth data in session - not an OAuth session
|
|
34
|
+
return { valid: false, error: 'No OAuth data in session' };
|
|
35
|
+
}
|
|
36
|
+
// Validate required fields
|
|
37
|
+
if (!oauthMetadata.accessToken) {
|
|
38
|
+
logger?.warn?.('OAuth session missing access token, logging out');
|
|
39
|
+
await clearOAuthSession(session, logger);
|
|
40
|
+
return { valid: false, error: 'OAuth session missing access token' };
|
|
41
|
+
}
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const isExpired = oauthMetadata.expiresAt ? now >= oauthMetadata.expiresAt : false;
|
|
44
|
+
const needsRefresh = oauthMetadata.refreshThreshold ? now >= oauthMetadata.refreshThreshold : false;
|
|
45
|
+
// For tokens without expiration (like GitHub), perform periodic validation
|
|
46
|
+
if (!oauthMetadata.expiresAt && !oauthMetadata.refreshThreshold && provider.config.validateToken) {
|
|
47
|
+
const validationInterval = provider.config.tokenValidationInterval || 15 * 60 * 1000; // Default 15 minutes
|
|
48
|
+
const lastCheck = oauthMetadata.lastValidated || oauthMetadata.lastRefreshed || 0;
|
|
49
|
+
const timeSinceLastCheck = now - lastCheck;
|
|
50
|
+
if (timeSinceLastCheck > validationInterval) {
|
|
51
|
+
logger?.debug?.('Performing periodic token validation for non-expiring token');
|
|
52
|
+
try {
|
|
53
|
+
const isValid = await provider.config.validateToken(oauthMetadata.accessToken, logger);
|
|
54
|
+
if (!isValid) {
|
|
55
|
+
logger?.debug?.('OAuth token validation failed (token revoked or invalid), logging out');
|
|
56
|
+
await clearOAuthSession(session, logger);
|
|
57
|
+
return { valid: false, error: 'Token validation failed - token may have been revoked' };
|
|
58
|
+
}
|
|
59
|
+
// Update last validated timestamp in session
|
|
60
|
+
oauthMetadata.lastValidated = now;
|
|
61
|
+
session.oauth = oauthMetadata;
|
|
62
|
+
if (typeof session.update === 'function') {
|
|
63
|
+
await session.update(session);
|
|
64
|
+
}
|
|
65
|
+
logger?.debug?.('Token validation successful');
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
logger?.error?.('Token validation error:', error.message);
|
|
69
|
+
// Don't logout on validation errors - could be network issue
|
|
70
|
+
// Token will be validated again on next request
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { valid: true, refreshed: false };
|
|
74
|
+
}
|
|
75
|
+
// Token is still valid and doesn't need refresh
|
|
76
|
+
if (!isExpired && !needsRefresh) {
|
|
77
|
+
return { valid: true, refreshed: false };
|
|
78
|
+
}
|
|
79
|
+
// Token needs refresh - check if refresh token is available
|
|
80
|
+
if (!oauthMetadata.refreshToken) {
|
|
81
|
+
if (isExpired) {
|
|
82
|
+
logger?.warn?.('OAuth token expired and no refresh token available, logging out');
|
|
83
|
+
await clearOAuthSession(session, logger);
|
|
84
|
+
return { valid: false, error: 'Token expired and no refresh token available' };
|
|
85
|
+
}
|
|
86
|
+
// Token approaching expiration but no refresh token - still valid for now
|
|
87
|
+
return { valid: true, refreshed: false };
|
|
88
|
+
}
|
|
89
|
+
// Attempt to refresh the token
|
|
90
|
+
logger?.debug?.(isExpired
|
|
91
|
+
? 'OAuth token expired, attempting refresh...'
|
|
92
|
+
: 'OAuth token approaching expiration (80% lifetime), refreshing proactively...');
|
|
93
|
+
try {
|
|
94
|
+
// Check if provider supports token refresh
|
|
95
|
+
if (!provider.refreshAccessToken) {
|
|
96
|
+
logger?.warn?.('OAuth provider does not support token refresh');
|
|
97
|
+
if (isExpired) {
|
|
98
|
+
await clearOAuthSession(session, logger);
|
|
99
|
+
return { valid: false, error: 'Token expired and provider does not support refresh' };
|
|
100
|
+
}
|
|
101
|
+
return { valid: true, refreshed: false };
|
|
102
|
+
}
|
|
103
|
+
// Attempt to refresh the token
|
|
104
|
+
logger?.debug?.('Attempting token refresh with refresh token');
|
|
105
|
+
// Perform token refresh
|
|
106
|
+
const tokenResponse = await provider.refreshAccessToken(oauthMetadata.refreshToken);
|
|
107
|
+
// Calculate new expiration times
|
|
108
|
+
const expiresIn = tokenResponse.expires_in || 3600; // Default 1 hour
|
|
109
|
+
const newExpiresAt = now + expiresIn * 1000;
|
|
110
|
+
const newRefreshThreshold = now + expiresIn * 800; // Refresh at 80% of lifetime
|
|
111
|
+
// Update session with new tokens and metadata
|
|
112
|
+
const updatedMetadata = {
|
|
113
|
+
provider: oauthMetadata.provider,
|
|
114
|
+
providerConfigId: oauthMetadata.providerConfigId,
|
|
115
|
+
providerType: oauthMetadata.providerType,
|
|
116
|
+
accessToken: tokenResponse.access_token,
|
|
117
|
+
refreshToken: tokenResponse.refresh_token || oauthMetadata.refreshToken, // Keep existing if not provided
|
|
118
|
+
expiresAt: newExpiresAt,
|
|
119
|
+
refreshThreshold: newRefreshThreshold,
|
|
120
|
+
scope: tokenResponse.scope || oauthMetadata.scope,
|
|
121
|
+
tokenType: tokenResponse.token_type || oauthMetadata.tokenType,
|
|
122
|
+
lastRefreshed: now,
|
|
123
|
+
lastValidated: oauthMetadata.lastValidated, // Preserve if exists
|
|
124
|
+
};
|
|
125
|
+
// Update session with refreshed token
|
|
126
|
+
session.oauth = updatedMetadata;
|
|
127
|
+
if (typeof session.update === 'function') {
|
|
128
|
+
await session.update(session);
|
|
129
|
+
}
|
|
130
|
+
logger?.info?.('OAuth token refreshed successfully');
|
|
131
|
+
// Call onTokenRefresh hook
|
|
132
|
+
if (hookManager) {
|
|
133
|
+
await hookManager.callOnTokenRefresh(session, true, request);
|
|
134
|
+
}
|
|
135
|
+
return { valid: true, refreshed: true };
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
logger?.error?.('OAuth token refresh failed:', error.message);
|
|
139
|
+
// If token was expired and refresh failed, log out
|
|
140
|
+
if (isExpired) {
|
|
141
|
+
await clearOAuthSession(session, logger);
|
|
142
|
+
return { valid: false, error: `Token refresh failed: ${error.message}` };
|
|
143
|
+
}
|
|
144
|
+
// Token not yet expired, allow continued use
|
|
145
|
+
return { valid: true, refreshed: false };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check if a session has valid OAuth authentication
|
|
150
|
+
* Does not refresh tokens, only checks validity
|
|
151
|
+
*/
|
|
152
|
+
export function hasValidOAuthSession(request) {
|
|
153
|
+
const session = request.session;
|
|
154
|
+
if (!session)
|
|
155
|
+
return false;
|
|
156
|
+
const oauthMetadata = session.oauth;
|
|
157
|
+
if (!oauthMetadata || !oauthMetadata.accessToken)
|
|
158
|
+
return false;
|
|
159
|
+
// Check if token is expired - return true if valid, false if expired
|
|
160
|
+
return !(oauthMetadata.expiresAt && Date.now() >= oauthMetadata.expiresAt);
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=sessionValidator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionValidator.js","sourceRoot":"","sources":["../../src/lib/sessionValidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Tenant SSO Manager
|
|
3
|
+
*
|
|
4
|
+
* Enables dynamic tenant routing for enterprise SSO
|
|
5
|
+
* Supports Okta, Azure AD, Auth0, and any domain-based OAuth provider
|
|
6
|
+
*/
|
|
7
|
+
import type { OAuthProviderConfig, Logger } from '../types.ts';
|
|
8
|
+
export interface TenantConfig {
|
|
9
|
+
/** Unique tenant identifier (e.g., 'acme-corp', 'globex') */
|
|
10
|
+
tenantId: string;
|
|
11
|
+
/** Tenant display name */
|
|
12
|
+
name: string;
|
|
13
|
+
/** OAuth provider type (okta, azure, auth0, etc.) */
|
|
14
|
+
provider: 'okta' | 'azure' | 'auth0' | string;
|
|
15
|
+
/** Provider-specific domain (for Okta/Auth0) or tenant ID (for Azure) */
|
|
16
|
+
domain?: string;
|
|
17
|
+
/** Azure AD specific tenant ID (alternative to domain) */
|
|
18
|
+
azureTenantId?: string;
|
|
19
|
+
/** OAuth client ID for this tenant */
|
|
20
|
+
clientId: string;
|
|
21
|
+
/** OAuth client secret for this tenant */
|
|
22
|
+
clientSecret: string;
|
|
23
|
+
/** Optional: Email domains that belong to this tenant */
|
|
24
|
+
emailDomains?: string[];
|
|
25
|
+
/** Optional: Custom scopes */
|
|
26
|
+
scope?: string;
|
|
27
|
+
/** Optional: Custom post-login redirect */
|
|
28
|
+
postLoginRedirect?: string;
|
|
29
|
+
/** Optional: Additional provider-specific config */
|
|
30
|
+
additionalConfig?: Record<string, any>;
|
|
31
|
+
}
|
|
32
|
+
export interface TenantRegistryEntry {
|
|
33
|
+
config: TenantConfig;
|
|
34
|
+
providerConfig: OAuthProviderConfig;
|
|
35
|
+
}
|
|
36
|
+
export declare class TenantManager {
|
|
37
|
+
private tenants;
|
|
38
|
+
private domainToTenant;
|
|
39
|
+
private logger?;
|
|
40
|
+
constructor(logger?: Logger);
|
|
41
|
+
/**
|
|
42
|
+
* Register a tenant with their OAuth provider configuration
|
|
43
|
+
* Supports Okta, Azure AD, Auth0, and custom providers
|
|
44
|
+
*/
|
|
45
|
+
registerTenant(tenant: TenantConfig): void;
|
|
46
|
+
/**
|
|
47
|
+
* Register multiple tenants at once
|
|
48
|
+
*/
|
|
49
|
+
registerTenants(tenants: TenantConfig[]): void;
|
|
50
|
+
/**
|
|
51
|
+
* Get tenant by ID
|
|
52
|
+
*
|
|
53
|
+
* ⚠️ **Security Warning**: Returns full tenant configuration including clientSecret.
|
|
54
|
+
* This method is intended for internal use (hooks, OAuth flows) only.
|
|
55
|
+
* Never expose the returned TenantRegistryEntry directly in HTTP responses.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // ✅ Safe: Use in hooks/internal logic
|
|
59
|
+
* const tenant = tenantManager.getTenant(provider);
|
|
60
|
+
* return { tenantName: tenant?.config.name };
|
|
61
|
+
*
|
|
62
|
+
* // ❌ Unsafe: Direct HTTP exposure
|
|
63
|
+
* return { tenant: tenantManager.getTenant(id) }; // Leaks clientSecret!
|
|
64
|
+
*/
|
|
65
|
+
getTenant(tenantId: string): TenantRegistryEntry | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Get tenant by email domain
|
|
68
|
+
*
|
|
69
|
+
* ⚠️ **Security Warning**: Returns full tenant configuration including clientSecret.
|
|
70
|
+
* This method is intended for internal use (hooks, OAuth flows) only.
|
|
71
|
+
* Never expose the returned TenantRegistryEntry directly in HTTP responses.
|
|
72
|
+
*/
|
|
73
|
+
getTenantByEmail(email: string): TenantRegistryEntry | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Get all registered tenants
|
|
76
|
+
*
|
|
77
|
+
* Note: clientSecret is automatically redacted for security.
|
|
78
|
+
* Secrets are only needed internally for OAuth flows.
|
|
79
|
+
*/
|
|
80
|
+
getAllTenants(): TenantConfig[];
|
|
81
|
+
/**
|
|
82
|
+
* Convert tenant configurations to provider registry format
|
|
83
|
+
* This can be merged with the standard provider registry
|
|
84
|
+
*
|
|
85
|
+
* ⚠️ **Security Warning**: Returns provider configurations including clientSecret.
|
|
86
|
+
* This method is intended for OAuth plugin initialization only.
|
|
87
|
+
* The returned configs are needed for OAuth token exchange flows.
|
|
88
|
+
* Never expose these configurations in HTTP responses.
|
|
89
|
+
*/
|
|
90
|
+
toProviderRegistry(): Record<string, {
|
|
91
|
+
provider: any;
|
|
92
|
+
config: OAuthProviderConfig;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Load tenants from configuration
|
|
96
|
+
* Supports both static config and dynamic loading from database
|
|
97
|
+
*/
|
|
98
|
+
static fromConfig(config: {
|
|
99
|
+
tenants?: TenantConfig[];
|
|
100
|
+
logger?: Logger;
|
|
101
|
+
}): TenantManager;
|
|
102
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Tenant SSO Manager
|
|
3
|
+
*
|
|
4
|
+
* Enables dynamic tenant routing for enterprise SSO
|
|
5
|
+
* Supports Okta, Azure AD, Auth0, and any domain-based OAuth provider
|
|
6
|
+
*/
|
|
7
|
+
import { getProvider } from "./providers/index.js";
|
|
8
|
+
import { validateTenantId, validateEmailDomain } from "./providers/validation.js";
|
|
9
|
+
export class TenantManager {
|
|
10
|
+
tenants = new Map();
|
|
11
|
+
domainToTenant = new Map();
|
|
12
|
+
logger;
|
|
13
|
+
constructor(logger) {
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register a tenant with their OAuth provider configuration
|
|
18
|
+
* Supports Okta, Azure AD, Auth0, and custom providers
|
|
19
|
+
*/
|
|
20
|
+
registerTenant(tenant) {
|
|
21
|
+
// Validate tenant ID format
|
|
22
|
+
validateTenantId(tenant.tenantId);
|
|
23
|
+
// Validate email domains
|
|
24
|
+
if (tenant.emailDomains) {
|
|
25
|
+
for (const domain of tenant.emailDomains) {
|
|
26
|
+
validateEmailDomain(domain);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Get the base provider configuration
|
|
30
|
+
const baseProvider = getProvider(tenant.provider);
|
|
31
|
+
if (!baseProvider) {
|
|
32
|
+
throw new Error(`Unknown provider type: ${tenant.provider}`);
|
|
33
|
+
}
|
|
34
|
+
// Apply provider-specific configuration
|
|
35
|
+
// Note: Provider configure() methods now include security validation
|
|
36
|
+
let providerSpecificConfig = {};
|
|
37
|
+
if (baseProvider.configure) {
|
|
38
|
+
switch (tenant.provider) {
|
|
39
|
+
case 'okta':
|
|
40
|
+
case 'auth0':
|
|
41
|
+
if (!tenant.domain) {
|
|
42
|
+
throw new Error(`${tenant.provider} provider requires domain configuration`);
|
|
43
|
+
}
|
|
44
|
+
providerSpecificConfig = baseProvider.configure(tenant.domain);
|
|
45
|
+
break;
|
|
46
|
+
case 'azure':
|
|
47
|
+
case 'microsoft':
|
|
48
|
+
if (!tenant.azureTenantId) {
|
|
49
|
+
throw new Error('Azure AD provider requires azureTenantId configuration');
|
|
50
|
+
}
|
|
51
|
+
providerSpecificConfig = baseProvider.configure(tenant.azureTenantId);
|
|
52
|
+
break;
|
|
53
|
+
default:
|
|
54
|
+
// For custom providers, pass domain or azureTenantId if available
|
|
55
|
+
const configParam = tenant.domain || tenant.azureTenantId;
|
|
56
|
+
if (configParam) {
|
|
57
|
+
providerSpecificConfig = baseProvider.configure(configParam);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Build the complete provider configuration
|
|
62
|
+
const providerConfig = {
|
|
63
|
+
...baseProvider,
|
|
64
|
+
...providerSpecificConfig,
|
|
65
|
+
provider: tenant.provider,
|
|
66
|
+
clientId: tenant.clientId,
|
|
67
|
+
clientSecret: tenant.clientSecret,
|
|
68
|
+
scope: tenant.scope || baseProvider.scope,
|
|
69
|
+
postLoginRedirect: tenant.postLoginRedirect,
|
|
70
|
+
...tenant.additionalConfig,
|
|
71
|
+
};
|
|
72
|
+
this.tenants.set(tenant.tenantId, {
|
|
73
|
+
config: tenant,
|
|
74
|
+
providerConfig,
|
|
75
|
+
});
|
|
76
|
+
// Map email domains to tenant (already validated above)
|
|
77
|
+
if (tenant.emailDomains) {
|
|
78
|
+
for (const domain of tenant.emailDomains) {
|
|
79
|
+
const normalizedDomain = domain.toLowerCase();
|
|
80
|
+
const existingTenantId = this.domainToTenant.get(normalizedDomain);
|
|
81
|
+
if (existingTenantId && existingTenantId !== tenant.tenantId) {
|
|
82
|
+
this.logger?.warn?.(`Email domain "${normalizedDomain}" is already mapped to tenant "${existingTenantId}". ` +
|
|
83
|
+
`Overwriting with tenant "${tenant.tenantId}".`);
|
|
84
|
+
}
|
|
85
|
+
this.domainToTenant.set(normalizedDomain, tenant.tenantId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.logger?.info?.(`Registered tenant: ${tenant.name} (${tenant.tenantId}) using ${tenant.provider} provider`);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Register multiple tenants at once
|
|
92
|
+
*/
|
|
93
|
+
registerTenants(tenants) {
|
|
94
|
+
for (const tenant of tenants) {
|
|
95
|
+
this.registerTenant(tenant);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get tenant by ID
|
|
100
|
+
*
|
|
101
|
+
* ⚠️ **Security Warning**: Returns full tenant configuration including clientSecret.
|
|
102
|
+
* This method is intended for internal use (hooks, OAuth flows) only.
|
|
103
|
+
* Never expose the returned TenantRegistryEntry directly in HTTP responses.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* // ✅ Safe: Use in hooks/internal logic
|
|
107
|
+
* const tenant = tenantManager.getTenant(provider);
|
|
108
|
+
* return { tenantName: tenant?.config.name };
|
|
109
|
+
*
|
|
110
|
+
* // ❌ Unsafe: Direct HTTP exposure
|
|
111
|
+
* return { tenant: tenantManager.getTenant(id) }; // Leaks clientSecret!
|
|
112
|
+
*/
|
|
113
|
+
getTenant(tenantId) {
|
|
114
|
+
return this.tenants.get(tenantId);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get tenant by email domain
|
|
118
|
+
*
|
|
119
|
+
* ⚠️ **Security Warning**: Returns full tenant configuration including clientSecret.
|
|
120
|
+
* This method is intended for internal use (hooks, OAuth flows) only.
|
|
121
|
+
* Never expose the returned TenantRegistryEntry directly in HTTP responses.
|
|
122
|
+
*/
|
|
123
|
+
getTenantByEmail(email) {
|
|
124
|
+
const domain = email.split('@')[1]?.toLowerCase();
|
|
125
|
+
if (!domain)
|
|
126
|
+
return undefined;
|
|
127
|
+
const tenantId = this.domainToTenant.get(domain);
|
|
128
|
+
if (!tenantId)
|
|
129
|
+
return undefined;
|
|
130
|
+
return this.tenants.get(tenantId);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get all registered tenants
|
|
134
|
+
*
|
|
135
|
+
* Note: clientSecret is automatically redacted for security.
|
|
136
|
+
* Secrets are only needed internally for OAuth flows.
|
|
137
|
+
*/
|
|
138
|
+
getAllTenants() {
|
|
139
|
+
return Array.from(this.tenants.values()).map((entry) => ({
|
|
140
|
+
...entry.config,
|
|
141
|
+
clientSecret: undefined, // Redact secret for security
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Convert tenant configurations to provider registry format
|
|
146
|
+
* This can be merged with the standard provider registry
|
|
147
|
+
*
|
|
148
|
+
* ⚠️ **Security Warning**: Returns provider configurations including clientSecret.
|
|
149
|
+
* This method is intended for OAuth plugin initialization only.
|
|
150
|
+
* The returned configs are needed for OAuth token exchange flows.
|
|
151
|
+
* Never expose these configurations in HTTP responses.
|
|
152
|
+
*/
|
|
153
|
+
toProviderRegistry() {
|
|
154
|
+
const registry = {};
|
|
155
|
+
for (const [tenantId, entry] of this.tenants) {
|
|
156
|
+
// We'll need to create a provider instance here
|
|
157
|
+
// For now, return the config - the caller will need to instantiate OAuthProvider
|
|
158
|
+
registry[tenantId] = {
|
|
159
|
+
provider: null, // Will be instantiated by caller
|
|
160
|
+
config: entry.providerConfig,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return registry;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Load tenants from configuration
|
|
167
|
+
* Supports both static config and dynamic loading from database
|
|
168
|
+
*/
|
|
169
|
+
static fromConfig(config) {
|
|
170
|
+
const manager = new TenantManager(config.logger);
|
|
171
|
+
if (config.tenants) {
|
|
172
|
+
manager.registerTenants(config.tenants);
|
|
173
|
+
}
|
|
174
|
+
return manager;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=tenantManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenantManager.js","sourceRoot":"","sources":["../../src/lib/tenantManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Session Validation Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Wraps Harper resources to add automatic OAuth session validation and token refresh
|
|
5
|
+
* before handling any request. This enables transparent token management for protected endpoints.
|
|
6
|
+
*/
|
|
7
|
+
import type { Request, Logger, ProviderRegistry } from '../types.ts';
|
|
8
|
+
export interface OAuthValidationOptions {
|
|
9
|
+
/** OAuth provider registry from plugin initialization */
|
|
10
|
+
providers: ProviderRegistry;
|
|
11
|
+
/** Logger instance for debugging */
|
|
12
|
+
logger?: Logger;
|
|
13
|
+
/** Whether to require OAuth authentication (401 if not present) */
|
|
14
|
+
requireAuth?: boolean;
|
|
15
|
+
/** Custom error handler for validation failures */
|
|
16
|
+
onValidationError?: (request: Request, error: string) => any;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Wraps a Harper resource to add automatic OAuth session validation
|
|
20
|
+
*
|
|
21
|
+
* This wrapper intercepts all resource method calls (get, post, put, patch, delete)
|
|
22
|
+
* and validates/refreshes OAuth tokens before passing the request to the original resource.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // In your application component:
|
|
27
|
+
* import { withOAuthValidation } from '@harperfast/oauth';
|
|
28
|
+
*
|
|
29
|
+
* export function handleApplication(scope) {
|
|
30
|
+
* // Get OAuth providers from the OAuth plugin
|
|
31
|
+
* const oauthPlugin = scope.parent.resources.get('oauth');
|
|
32
|
+
*
|
|
33
|
+
* // Wrap your protected resource
|
|
34
|
+
* const myResource = {
|
|
35
|
+
* async get(target, request) {
|
|
36
|
+
* // This code only runs if OAuth session is valid
|
|
37
|
+
* return { user: request.session.oauthUser };
|
|
38
|
+
* }
|
|
39
|
+
* };
|
|
40
|
+
*
|
|
41
|
+
* scope.resources.set('protected', withOAuthValidation(myResource, {
|
|
42
|
+
* providers: oauthPlugin.providers,
|
|
43
|
+
* requireAuth: true,
|
|
44
|
+
* logger: scope.logger
|
|
45
|
+
* }));
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function withOAuthValidation(resource: any, options: OAuthValidationOptions): any;
|
|
50
|
+
/**
|
|
51
|
+
* Helper to get OAuth providers from the OAuth plugin
|
|
52
|
+
* Call this from your application to access the provider registry
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* import { getOAuthProviders } from '@harperfast/oauth';
|
|
57
|
+
*
|
|
58
|
+
* export function handleApplication(scope) {
|
|
59
|
+
* const providers = getOAuthProviders(scope);
|
|
60
|
+
* // Use providers with withOAuthValidation
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function getOAuthProviders(scope: any): ProviderRegistry | null;
|