@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
package/assets/test.html
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Harper OAuth Test</title>
|
|
5
|
+
<style>
|
|
6
|
+
body {
|
|
7
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
8
|
+
max-width: 800px;
|
|
9
|
+
margin: 50px auto;
|
|
10
|
+
padding: 20px;
|
|
11
|
+
}
|
|
12
|
+
.user-info {
|
|
13
|
+
background: #f5f5f5;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
border-radius: 8px;
|
|
16
|
+
margin: 20px 0;
|
|
17
|
+
}
|
|
18
|
+
.providers {
|
|
19
|
+
margin: 20px 0;
|
|
20
|
+
}
|
|
21
|
+
.provider-button {
|
|
22
|
+
display: inline-block;
|
|
23
|
+
padding: 10px 20px;
|
|
24
|
+
margin: 10px 10px 10px 0;
|
|
25
|
+
background: #0366d6;
|
|
26
|
+
color: white;
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
border-radius: 6px;
|
|
29
|
+
border: none;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
font-size: 14px;
|
|
32
|
+
}
|
|
33
|
+
.provider-button.github {
|
|
34
|
+
background: #24292e;
|
|
35
|
+
}
|
|
36
|
+
.provider-button.github:hover {
|
|
37
|
+
background: #1a1e22;
|
|
38
|
+
}
|
|
39
|
+
.provider-button.google {
|
|
40
|
+
background: #4285f4;
|
|
41
|
+
}
|
|
42
|
+
.provider-button.google:hover {
|
|
43
|
+
background: #357ae8;
|
|
44
|
+
}
|
|
45
|
+
.provider-button.azure,
|
|
46
|
+
.provider-button.microsoft {
|
|
47
|
+
background: #0078d4;
|
|
48
|
+
}
|
|
49
|
+
.provider-button.azure:hover,
|
|
50
|
+
.provider-button.microsoft:hover {
|
|
51
|
+
background: #006cc1;
|
|
52
|
+
}
|
|
53
|
+
.provider-button.auth0 {
|
|
54
|
+
background: #eb5424;
|
|
55
|
+
}
|
|
56
|
+
.provider-button.auth0:hover {
|
|
57
|
+
background: #d44820;
|
|
58
|
+
}
|
|
59
|
+
.provider-button.okta {
|
|
60
|
+
background: #007dc1;
|
|
61
|
+
}
|
|
62
|
+
.provider-button.okta:hover {
|
|
63
|
+
background: #006ba1;
|
|
64
|
+
}
|
|
65
|
+
.button {
|
|
66
|
+
display: inline-block;
|
|
67
|
+
padding: 10px 20px;
|
|
68
|
+
margin: 10px 10px 10px 0;
|
|
69
|
+
background: #6c757d;
|
|
70
|
+
color: white;
|
|
71
|
+
text-decoration: none;
|
|
72
|
+
border-radius: 6px;
|
|
73
|
+
border: none;
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
font-size: 14px;
|
|
76
|
+
}
|
|
77
|
+
.button:hover {
|
|
78
|
+
background: #5a6268;
|
|
79
|
+
}
|
|
80
|
+
pre {
|
|
81
|
+
background: #f6f8fa;
|
|
82
|
+
padding: 10px;
|
|
83
|
+
border-radius: 6px;
|
|
84
|
+
overflow-x: auto;
|
|
85
|
+
}
|
|
86
|
+
.error {
|
|
87
|
+
color: #d73a49;
|
|
88
|
+
background: #ffeef0;
|
|
89
|
+
padding: 10px;
|
|
90
|
+
border-radius: 6px;
|
|
91
|
+
margin: 10px 0;
|
|
92
|
+
}
|
|
93
|
+
.success {
|
|
94
|
+
color: #28a745;
|
|
95
|
+
background: #d4edda;
|
|
96
|
+
padding: 10px;
|
|
97
|
+
border-radius: 6px;
|
|
98
|
+
margin: 10px 0;
|
|
99
|
+
}
|
|
100
|
+
.loading {
|
|
101
|
+
color: #666;
|
|
102
|
+
font-style: italic;
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
105
|
+
</head>
|
|
106
|
+
<body>
|
|
107
|
+
<h1>Harper OAuth Test</h1>
|
|
108
|
+
|
|
109
|
+
<div id="user-info" class="user-info">
|
|
110
|
+
<h2>Current User</h2>
|
|
111
|
+
<div id="user-status" class="loading">Loading...</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="providers">
|
|
115
|
+
<h2>Login Options</h2>
|
|
116
|
+
<div id="provider-buttons" class="loading">Loading providers...</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div id="actions">
|
|
120
|
+
<button onclick="logout()" class="button">Logout</button>
|
|
121
|
+
<button onclick="checkUser()" class="button">Refresh Status</button>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div id="message"></div>
|
|
125
|
+
|
|
126
|
+
<h2>Debug Endpoints</h2>
|
|
127
|
+
<p>These endpoints are available in debug mode:</p>
|
|
128
|
+
<ul>
|
|
129
|
+
<li><a href="/oauth" target="_blank">/oauth</a> - List all configured providers</li>
|
|
130
|
+
<li id="user-endpoint">Loading...</li>
|
|
131
|
+
<li id="refresh-endpoint" style="display: none">
|
|
132
|
+
<a href="#" onclick="refreshToken(); return false;">/oauth/{provider}/refresh</a> - Refresh access token
|
|
133
|
+
</li>
|
|
134
|
+
</ul>
|
|
135
|
+
|
|
136
|
+
<script>
|
|
137
|
+
let currentProvider = null;
|
|
138
|
+
let availableProviders = [];
|
|
139
|
+
|
|
140
|
+
async function loadProviders() {
|
|
141
|
+
try {
|
|
142
|
+
const response = await fetch('/oauth');
|
|
143
|
+
const data = await response.json();
|
|
144
|
+
|
|
145
|
+
const providerButtons = document.getElementById('provider-buttons');
|
|
146
|
+
|
|
147
|
+
if (data.providers && data.providers.length > 0) {
|
|
148
|
+
availableProviders = data.providers;
|
|
149
|
+
providerButtons.innerHTML = data.providers
|
|
150
|
+
.map(
|
|
151
|
+
(p) => `
|
|
152
|
+
<a href="/oauth/${p.name}/login"
|
|
153
|
+
class="provider-button ${p.provider}"
|
|
154
|
+
onclick="setProvider('${p.name}'); return true;">
|
|
155
|
+
Login with ${p.name.charAt(0).toUpperCase() + p.name.slice(1)}
|
|
156
|
+
</a>
|
|
157
|
+
`
|
|
158
|
+
)
|
|
159
|
+
.join('');
|
|
160
|
+
|
|
161
|
+
// Update user endpoint link
|
|
162
|
+
if (data.providers[0]) {
|
|
163
|
+
currentProvider = data.providers[0].name;
|
|
164
|
+
updateEndpoints();
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
providerButtons.innerHTML = '<p class="error">No OAuth providers configured</p>';
|
|
168
|
+
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
document.getElementById('provider-buttons').innerHTML =
|
|
171
|
+
'<p class="error">Error loading providers: ' + error.message + '</p>';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function setProvider(provider) {
|
|
176
|
+
currentProvider = provider;
|
|
177
|
+
localStorage.setItem('oauth-provider', provider);
|
|
178
|
+
updateEndpoints();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function updateEndpoints() {
|
|
182
|
+
if (currentProvider) {
|
|
183
|
+
document.getElementById('user-endpoint').innerHTML =
|
|
184
|
+
`<a href="/oauth/${currentProvider}/user" target="_blank">/oauth/${currentProvider}/user</a> - Current user info (JSON)`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function checkUser() {
|
|
189
|
+
if (!currentProvider && availableProviders.length > 0) {
|
|
190
|
+
currentProvider = availableProviders[0].name;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!currentProvider) {
|
|
194
|
+
document.getElementById('user-status').innerHTML = '<p class="error">No OAuth provider available</p>';
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const response = await fetch(`/oauth/${currentProvider}/user`);
|
|
200
|
+
const data = await response.json();
|
|
201
|
+
|
|
202
|
+
const userStatus = document.getElementById('user-status');
|
|
203
|
+
|
|
204
|
+
if (response.status === 200 && (data.authenticated || data.body?.authenticated)) {
|
|
205
|
+
const user = data.body || data;
|
|
206
|
+
userStatus.innerHTML = `
|
|
207
|
+
<p><strong>Authenticated:</strong> Yes</p>
|
|
208
|
+
<p><strong>Username:</strong> ${user.username}</p>
|
|
209
|
+
<p><strong>Email:</strong> ${user.email || 'N/A'}</p>
|
|
210
|
+
<p><strong>Name:</strong> ${user.name || 'N/A'}</p>
|
|
211
|
+
<p><strong>Role:</strong> ${user.role || 'N/A'}</p>
|
|
212
|
+
<p><strong>Provider:</strong> ${user.provider}</p>
|
|
213
|
+
<pre>${JSON.stringify(user, null, 2)}</pre>
|
|
214
|
+
`;
|
|
215
|
+
showMessage('Authenticated successfully', 'success');
|
|
216
|
+
} else {
|
|
217
|
+
userStatus.innerHTML = `
|
|
218
|
+
<p><strong>Authenticated:</strong> No</p>
|
|
219
|
+
<p>Select a provider above to login.</p>
|
|
220
|
+
`;
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
document.getElementById('user-status').innerHTML =
|
|
224
|
+
'<p class="error">Error checking user status: ' + error.message + '</p>';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function logout() {
|
|
229
|
+
try {
|
|
230
|
+
const response = await fetch('/oauth/logout', {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
headers: {
|
|
233
|
+
'Content-Type': 'application/json',
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (response.ok) {
|
|
238
|
+
showMessage('Logged out successfully', 'success');
|
|
239
|
+
checkUser();
|
|
240
|
+
} else {
|
|
241
|
+
throw new Error('Logout failed');
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
showMessage('Error logging out: ' + error.message, 'error');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function refreshToken() {
|
|
249
|
+
if (!currentProvider) {
|
|
250
|
+
showMessage('No provider selected', 'error');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const response = await fetch(`/oauth/${currentProvider}/refresh`);
|
|
256
|
+
const data = await response.json();
|
|
257
|
+
|
|
258
|
+
if (response.ok) {
|
|
259
|
+
showMessage(
|
|
260
|
+
`Token refreshed successfully (expires in ${data.expiresIn || data.body?.expiresIn || 'unknown'} seconds)`,
|
|
261
|
+
'success'
|
|
262
|
+
);
|
|
263
|
+
} else {
|
|
264
|
+
showMessage(`Refresh failed: ${data.error || data.body?.error || 'Unknown error'}`, 'error');
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
showMessage('Error refreshing token: ' + error.message, 'error');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function showMessage(text, type) {
|
|
272
|
+
const messageEl = document.getElementById('message');
|
|
273
|
+
messageEl.className = type;
|
|
274
|
+
messageEl.textContent = text;
|
|
275
|
+
messageEl.style.display = 'block';
|
|
276
|
+
|
|
277
|
+
setTimeout(() => {
|
|
278
|
+
messageEl.style.display = 'none';
|
|
279
|
+
}, 5000);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Initialize on page load
|
|
283
|
+
async function init() {
|
|
284
|
+
// Load saved provider preference
|
|
285
|
+
currentProvider = localStorage.getItem('oauth-provider');
|
|
286
|
+
|
|
287
|
+
await loadProviders();
|
|
288
|
+
await checkUser();
|
|
289
|
+
|
|
290
|
+
// Check for OAuth error parameters
|
|
291
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
292
|
+
const error = urlParams.get('error');
|
|
293
|
+
if (error) {
|
|
294
|
+
if (error === 'session_expired') {
|
|
295
|
+
showMessage('Session expired. Please login again.', 'error');
|
|
296
|
+
} else if (error === 'oauth_failed') {
|
|
297
|
+
const reason = urlParams.get('reason') || 'unknown';
|
|
298
|
+
showMessage(`OAuth login failed: ${reason}`, 'error');
|
|
299
|
+
} else if (error === 'invalid_request') {
|
|
300
|
+
showMessage('Invalid OAuth request. Please try again.', 'error');
|
|
301
|
+
} else {
|
|
302
|
+
showMessage(`Login error: ${error}`, 'error');
|
|
303
|
+
}
|
|
304
|
+
// Clean URL after showing error
|
|
305
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Check if we just logged in (redirected back)
|
|
309
|
+
if (window.location.pathname.includes('/callback')) {
|
|
310
|
+
showMessage('Login successful!', 'success');
|
|
311
|
+
// Redirect to test page to clean URL
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
window.location.href = '/oauth/test';
|
|
314
|
+
}, 1000);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
init();
|
|
319
|
+
</script>
|
|
320
|
+
</body>
|
|
321
|
+
</html>
|
package/config.yaml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# OAuth Plugin Configuration
|
|
2
|
+
# This file defines the plugin entry point and default settings
|
|
3
|
+
# All settings can be overridden in your application's config.yaml
|
|
4
|
+
|
|
5
|
+
# Plugin entry point (required by Harper)
|
|
6
|
+
pluginModule: 'dist/index.js'
|
|
7
|
+
|
|
8
|
+
# GraphQL schema for OAuth tables
|
|
9
|
+
graphqlSchema:
|
|
10
|
+
files: 'schema/oauth.graphql'
|
|
11
|
+
|
|
12
|
+
# Default settings (optional - these are used if not specified in app config)
|
|
13
|
+
# No providers configured by default - must be configured in application
|
|
14
|
+
# providers: {}
|
|
15
|
+
|
|
16
|
+
# Default OAuth settings
|
|
17
|
+
scope: 'openid profile email'
|
|
18
|
+
usernameClaim: 'email'
|
|
19
|
+
defaultRole: 'user'
|
|
20
|
+
postLoginRedirect: '/'
|
|
21
|
+
|
|
22
|
+
# Default redirect URI pattern (will be adjusted per provider)
|
|
23
|
+
redirectUri: 'https://localhost:9953/oauth/callback'
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harper OAuth Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides OAuth 2.0 authentication for Harper applications.
|
|
5
|
+
* Supports any standard OAuth 2.0 provider through configuration.
|
|
6
|
+
*/
|
|
7
|
+
import type { Scope, OAuthHooks } from './types.ts';
|
|
8
|
+
export { HookManager } from './lib/hookManager.ts';
|
|
9
|
+
export { OAuthResource } from './lib/resource.ts';
|
|
10
|
+
export type { OAuthHooks, OAuthUser, TokenResponse } from './types.ts';
|
|
11
|
+
export { TenantManager } from './lib/tenantManager.ts';
|
|
12
|
+
export type { TenantConfig, TenantRegistryEntry } from './lib/tenantManager.ts';
|
|
13
|
+
export { validateDomainSafety, validateDomainAllowlist, validateEmailDomain, validateTenantId, sanitizeTenantName, validateAzureTenantId, } from './lib/providers/validation.ts';
|
|
14
|
+
export { getProvider } from './lib/providers/index.ts';
|
|
15
|
+
/**
|
|
16
|
+
* Register OAuth hooks programmatically
|
|
17
|
+
* Call this from your application code to register lifecycle hooks
|
|
18
|
+
*
|
|
19
|
+
* This can be called:
|
|
20
|
+
* - At module load time (before the plugin initializes) - hooks will be queued
|
|
21
|
+
* - After plugin initialization - hooks will be applied immediately
|
|
22
|
+
*
|
|
23
|
+
* NOTE: This registers hooks at the module level, shared across all instances
|
|
24
|
+
* of the OAuth plugin. For most applications with a single OAuth plugin instance,
|
|
25
|
+
* this is the simplest and recommended approach.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { registerHooks } from '@harperfast/oauth';
|
|
30
|
+
*
|
|
31
|
+
* // Can be called at module load time or later
|
|
32
|
+
* registerHooks({
|
|
33
|
+
* onLogin: async (oauthUser, tokenResponse, session, request, provider) => {
|
|
34
|
+
* console.log(`User logged in: ${oauthUser.username}`);
|
|
35
|
+
* }
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function registerHooks(hooks: OAuthHooks): void;
|
|
40
|
+
/**
|
|
41
|
+
* Plugin entry point
|
|
42
|
+
*/
|
|
43
|
+
export declare function handleApplication(scope: Scope): Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harper OAuth Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides OAuth 2.0 authentication for Harper applications.
|
|
5
|
+
* Supports any standard OAuth 2.0 provider through configuration.
|
|
6
|
+
*/
|
|
7
|
+
import { initializeProviders, expandEnvVar, extractPluginDefaults } from "./lib/config.js";
|
|
8
|
+
import { OAuthResource } from "./lib/resource.js";
|
|
9
|
+
import { validateAndRefreshSession } from "./lib/sessionValidator.js";
|
|
10
|
+
import { clearOAuthSession } from "./lib/handlers.js";
|
|
11
|
+
import { HookManager } from "./lib/hookManager.js";
|
|
12
|
+
// Export HookManager class, OAuthResource class, and types
|
|
13
|
+
export { HookManager } from "./lib/hookManager.js";
|
|
14
|
+
export { OAuthResource } from "./lib/resource.js";
|
|
15
|
+
// Export multi-tenant SSO support
|
|
16
|
+
export { TenantManager } from "./lib/tenantManager.js";
|
|
17
|
+
// Export validation utilities for secure tenant configuration
|
|
18
|
+
export { validateDomainSafety, validateDomainAllowlist, validateEmailDomain, validateTenantId, sanitizeTenantName, validateAzureTenantId, } from "./lib/providers/validation.js";
|
|
19
|
+
// Export provider utilities
|
|
20
|
+
export { getProvider } from "./lib/providers/index.js";
|
|
21
|
+
// Store hooks registered at module load time and active hookManager
|
|
22
|
+
let pendingHooks = null;
|
|
23
|
+
let activeHookManager = null;
|
|
24
|
+
/**
|
|
25
|
+
* Register OAuth hooks programmatically
|
|
26
|
+
* Call this from your application code to register lifecycle hooks
|
|
27
|
+
*
|
|
28
|
+
* This can be called:
|
|
29
|
+
* - At module load time (before the plugin initializes) - hooks will be queued
|
|
30
|
+
* - After plugin initialization - hooks will be applied immediately
|
|
31
|
+
*
|
|
32
|
+
* NOTE: This registers hooks at the module level, shared across all instances
|
|
33
|
+
* of the OAuth plugin. For most applications with a single OAuth plugin instance,
|
|
34
|
+
* this is the simplest and recommended approach.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { registerHooks } from '@harperfast/oauth';
|
|
39
|
+
*
|
|
40
|
+
* // Can be called at module load time or later
|
|
41
|
+
* registerHooks({
|
|
42
|
+
* onLogin: async (oauthUser, tokenResponse, session, request, provider) => {
|
|
43
|
+
* console.log(`User logged in: ${oauthUser.username}`);
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function registerHooks(hooks) {
|
|
49
|
+
if (activeHookManager) {
|
|
50
|
+
// Plugin is already loaded - apply immediately
|
|
51
|
+
activeHookManager.register(hooks);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Plugin not loaded yet - queue for later
|
|
55
|
+
pendingHooks = hooks;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Plugin entry point
|
|
60
|
+
*/
|
|
61
|
+
export async function handleApplication(scope) {
|
|
62
|
+
const logger = scope.logger;
|
|
63
|
+
let providers = {};
|
|
64
|
+
let debugMode = false;
|
|
65
|
+
let isInitialized = false;
|
|
66
|
+
let pluginDefaults = {}; // Store plugin defaults for dynamic provider resolution
|
|
67
|
+
// Create hookManager instance scoped to this application
|
|
68
|
+
const hookManager = new HookManager(logger);
|
|
69
|
+
// Set as active hookManager for late hook registration
|
|
70
|
+
activeHookManager = hookManager;
|
|
71
|
+
// Apply any hooks that were registered at module load time
|
|
72
|
+
if (pendingHooks) {
|
|
73
|
+
hookManager.register(pendingHooks);
|
|
74
|
+
logger?.debug?.('Applied pending OAuth hooks');
|
|
75
|
+
pendingHooks = null; // Clear pending hooks after applying
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Update OAuth configuration when options change
|
|
79
|
+
*/
|
|
80
|
+
async function updateConfiguration() {
|
|
81
|
+
const rawOptions = (scope.options.getAll() || {});
|
|
82
|
+
// Expand environment variables in plugin-level options
|
|
83
|
+
const debugValue = expandEnvVar(rawOptions.debug);
|
|
84
|
+
const options = { ...rawOptions, debug: debugValue };
|
|
85
|
+
const previousDebugMode = debugMode;
|
|
86
|
+
// Handle both boolean and string values (from environment variables)
|
|
87
|
+
debugMode = options.debug === true || options.debug === 'true';
|
|
88
|
+
// Log configuration update
|
|
89
|
+
if (isInitialized) {
|
|
90
|
+
logger?.info?.('OAuth configuration changed, updating providers...');
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
logger?.info?.('OAuth plugin loading with options:', JSON.stringify(options, null, 2));
|
|
94
|
+
isInitialized = true;
|
|
95
|
+
}
|
|
96
|
+
// Re-initialize providers from new configuration
|
|
97
|
+
// Clear existing providers and repopulate (don't reassign to preserve closure reference)
|
|
98
|
+
const newProviders = initializeProviders(options, logger);
|
|
99
|
+
Object.keys(providers).forEach((key) => delete providers[key]);
|
|
100
|
+
Object.assign(providers, newProviders);
|
|
101
|
+
// Extract plugin defaults for dynamic provider resolution
|
|
102
|
+
pluginDefaults = extractPluginDefaults(options);
|
|
103
|
+
// Update the resource with new providers
|
|
104
|
+
if (Object.keys(providers).length === 0) {
|
|
105
|
+
// No valid providers configured - register a simple error resource
|
|
106
|
+
scope.resources.set('oauth', {
|
|
107
|
+
async get() {
|
|
108
|
+
return {
|
|
109
|
+
status: 503,
|
|
110
|
+
body: {
|
|
111
|
+
error: 'No valid OAuth providers configured',
|
|
112
|
+
message: 'Please check your OAuth configuration',
|
|
113
|
+
example: options.providers
|
|
114
|
+
? 'Check that all required fields are provided'
|
|
115
|
+
: {
|
|
116
|
+
providers: {
|
|
117
|
+
github: {
|
|
118
|
+
provider: 'github',
|
|
119
|
+
clientId: '${OAUTH_GITHUB_CLIENT_ID}',
|
|
120
|
+
clientSecret: '${OAUTH_GITHUB_CLIENT_SECRET}',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Configure the OAuth resource with providers and settings
|
|
131
|
+
OAuthResource.configure(providers, debugMode, hookManager, pluginDefaults, logger);
|
|
132
|
+
// Register the OAuth resource class
|
|
133
|
+
scope.resources.set('oauth', OAuthResource);
|
|
134
|
+
// Log all configured providers
|
|
135
|
+
logger?.info?.('OAuth plugin ready:', {
|
|
136
|
+
providers: Object.entries(providers).map(([name, data]) => ({
|
|
137
|
+
name,
|
|
138
|
+
type: data.config.provider,
|
|
139
|
+
redirectUri: data.config.redirectUri,
|
|
140
|
+
})),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Log debug mode change if it changed
|
|
144
|
+
if (isInitialized && previousDebugMode !== debugMode) {
|
|
145
|
+
logger?.info?.(`OAuth debug mode ${debugMode ? 'enabled' : 'disabled'}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Register HTTP middleware for automatic OAuth session validation
|
|
149
|
+
// This runs on every HTTP request after authentication but before REST
|
|
150
|
+
scope.server.http?.(async (request, next) => {
|
|
151
|
+
// Only process requests with sessions that have OAuth data
|
|
152
|
+
if (!request.session?.oauth) {
|
|
153
|
+
return next(request);
|
|
154
|
+
}
|
|
155
|
+
// Get the provider config ID for this OAuth session
|
|
156
|
+
// Use providerConfigId (new) or fall back to provider (old) for backwards compatibility
|
|
157
|
+
const providerConfigId = request.session.oauth.providerConfigId || request.session.oauth.provider;
|
|
158
|
+
let providerData = providers[providerConfigId];
|
|
159
|
+
// If provider not found in registry, try to resolve via hook (dynamic providers)
|
|
160
|
+
if (!providerData && hookManager?.hasHook('onResolveProvider')) {
|
|
161
|
+
try {
|
|
162
|
+
logger?.debug?.(`Provider config "${providerConfigId}" not in registry, attempting dynamic resolution`);
|
|
163
|
+
const hookConfig = await hookManager.callResolveProvider(providerConfigId, logger);
|
|
164
|
+
if (hookConfig) {
|
|
165
|
+
// Hook resolved provider - build full config and register dynamically
|
|
166
|
+
const { OAuthProvider } = await import("./lib/OAuthProvider.js");
|
|
167
|
+
const { buildProviderConfig } = await import("./lib/config.js");
|
|
168
|
+
// Build full provider config (handles Okta/Azure/Auth0 domain configuration)
|
|
169
|
+
const config = buildProviderConfig(hookConfig, providerConfigId, pluginDefaults);
|
|
170
|
+
const provider = new OAuthProvider(config, logger);
|
|
171
|
+
providers[providerConfigId] = {
|
|
172
|
+
provider,
|
|
173
|
+
config,
|
|
174
|
+
};
|
|
175
|
+
providerData = providers[providerConfigId];
|
|
176
|
+
logger?.info?.(`Dynamically registered provider for session validation: ${providerConfigId}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
logger?.error?.(`Error resolving provider ${providerConfigId} for session:`, error.message);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (!providerData) {
|
|
184
|
+
logger?.warn?.(`OAuth provider config '${providerConfigId}' not found, logging out user`);
|
|
185
|
+
// Provider no longer exists - complete logout
|
|
186
|
+
await clearOAuthSession(request.session, logger);
|
|
187
|
+
return next(request);
|
|
188
|
+
}
|
|
189
|
+
// Validate and refresh session automatically
|
|
190
|
+
const validation = await validateAndRefreshSession(request, providerData.provider, logger, hookManager);
|
|
191
|
+
if (!validation.valid) {
|
|
192
|
+
// Session is no longer valid (already cleaned up by validator)
|
|
193
|
+
logger?.debug?.(`OAuth session invalidated: ${validation.error}`);
|
|
194
|
+
}
|
|
195
|
+
else if (validation.refreshed) {
|
|
196
|
+
logger?.debug?.(`OAuth token auto-refreshed for ${providerConfigId}`);
|
|
197
|
+
}
|
|
198
|
+
// Continue with the request (session updated if refreshed)
|
|
199
|
+
return next(request);
|
|
200
|
+
});
|
|
201
|
+
// Concurrency control for configuration updates
|
|
202
|
+
let updating = false;
|
|
203
|
+
let pendingUpdate = false;
|
|
204
|
+
/**
|
|
205
|
+
* Run configuration update with concurrency protection
|
|
206
|
+
*/
|
|
207
|
+
const runUpdate = async () => {
|
|
208
|
+
if (updating) {
|
|
209
|
+
pendingUpdate = true;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
updating = true;
|
|
213
|
+
try {
|
|
214
|
+
await updateConfiguration();
|
|
215
|
+
// If another update was requested while we were running, run again
|
|
216
|
+
if (pendingUpdate) {
|
|
217
|
+
pendingUpdate = false;
|
|
218
|
+
await runUpdate();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
logger?.error?.('Failed to update OAuth configuration:', error);
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
updating = false;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
// Initial configuration (errors propagate to plugin loader)
|
|
229
|
+
await updateConfiguration();
|
|
230
|
+
// Watch for configuration changes (errors caught internally)
|
|
231
|
+
scope.options.on('change', () => {
|
|
232
|
+
runUpdate().catch((error) => {
|
|
233
|
+
logger?.error?.('Unexpected error in OAuth config update:', error);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
// Clean up on scope close
|
|
237
|
+
scope.on('close', () => {
|
|
238
|
+
logger?.info?.('OAuth plugin shutting down');
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSRF Token Manager for OAuth flows
|
|
3
|
+
*
|
|
4
|
+
* Manages CSRF protection tokens that prevent cross-site request forgery
|
|
5
|
+
* during OAuth authorization flows. Tokens are stored in Harper table
|
|
6
|
+
* for distributed access across workers and cluster nodes.
|
|
7
|
+
*/
|
|
8
|
+
import type { CSRFTokenData, Logger } from '../types.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Reset the cached CSRF table reference (for testing only)
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare function resetCSRFTableCache(): void;
|
|
14
|
+
export declare class CSRFTokenManager {
|
|
15
|
+
private logger?;
|
|
16
|
+
constructor(logger?: Logger);
|
|
17
|
+
/**
|
|
18
|
+
* Store CSRF token with metadata
|
|
19
|
+
* Table expiration is handled by Harper (10 minutes)
|
|
20
|
+
*/
|
|
21
|
+
set(token: string, data: CSRFTokenData): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Retrieve CSRF token
|
|
24
|
+
* Harper automatically handles expiration via table-level setting
|
|
25
|
+
*/
|
|
26
|
+
get(token: string): Promise<CSRFTokenData | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Delete CSRF token (after successful verification)
|
|
29
|
+
*/
|
|
30
|
+
delete(token: string): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export declare const csrfTokenManager: CSRFTokenManager;
|