@holo-js/auth-workos 0.3.27 → 0.4.0
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 +21 -0
- package/dist/.holo-build-fingerprint +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.mjs +37 -30
- package/package.json +16 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Holo-JS contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2a7a3172b7daf8be938a98765d10c89bc37b172775f9c93e82ba03ca38aa73a2
|
package/dist/index.d.ts
CHANGED
|
@@ -218,4 +218,5 @@ declare const workosAuthInternals: {
|
|
|
218
218
|
resolveGuardAndProvider: typeof resolveGuardAndProvider;
|
|
219
219
|
};
|
|
220
220
|
|
|
221
|
-
export {
|
|
221
|
+
export { WorkosAuthConflictError, authenticate, completeWorkosAuth, configureWorkosAuthRuntime, loginWithWorkos, logoutWithWorkos, registerWithWorkos, resetWorkosAuthRuntime, syncIdentity, verifyRequest, verifySession, workosAuth, workosAuthInternals };
|
|
222
|
+
export type { ConfigureWorkosAuthRuntimeOptions, HostedIdentityRecord, HostedIdentityStore, WorkosAuthBindings, WorkosAuthFacade, WorkosAuthenticatedUser, WorkosAuthenticationResult, WorkosCompleteAuthData, WorkosCompleteAuthOptions, WorkosCompleteAuthResult, WorkosDefaultUserAttributes, WorkosHostedAuthFailureFields, WorkosIdentityProfile, WorkosJsonValue, WorkosLogoutData, WorkosLogoutErrorCode, WorkosLogoutResult, WorkosLogoutSession, WorkosProviderRuntime, WorkosSyncIdentityOptions, WorkosSyncStatus, WorkosUserAttributeValue, WorkosUserAttributes, WorkosVerifiedSession, WorkosVerifyRequestContext, WorkosVerifySessionContext };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { randomBytes } from "crypto";
|
|
3
3
|
import { authRuntimeInternals, getAuthRuntime } from "@holo-js/auth";
|
|
4
|
+
import { resolveRequestUrl } from "@holo-js/kernel";
|
|
4
5
|
import { cookie, parseCookieHeader } from "@holo-js/session";
|
|
5
6
|
|
|
6
7
|
// src/contracts.ts
|
|
@@ -25,6 +26,7 @@ var WORKOS_AUTHORIZE_URL = `${WORKOS_API_BASE_URL}/user_management/authorize`;
|
|
|
25
26
|
var WORKOS_AUTHENTICATE_URL = `${WORKOS_API_BASE_URL}/user_management/authenticate`;
|
|
26
27
|
var WORKOS_LOGOUT_URL = `${WORKOS_API_BASE_URL}/user_management/sessions/logout`;
|
|
27
28
|
var WORKOS_STATE_COOKIE_MAX_AGE_SECONDS = 300;
|
|
29
|
+
var WORKOS_SESSION_ID_PATTERN = /^session_[A-Za-z0-9_-]+$/;
|
|
28
30
|
var workosDefaultProviderRuntimeCache = /* @__PURE__ */ new Map();
|
|
29
31
|
var workosJwksCache = /* @__PURE__ */ new Map();
|
|
30
32
|
var AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
|
|
@@ -73,12 +75,11 @@ function verifyJwtSignatureWithJwk(token, jwk) {
|
|
|
73
75
|
unsupportedAlgorithmMessage: (algorithm) => `[@holo-js/auth-workos] Unsupported WorkOS JWT algorithm "${algorithm}".`
|
|
74
76
|
});
|
|
75
77
|
}
|
|
76
|
-
async function
|
|
78
|
+
async function resolveWorkosJwk(clientId, kid) {
|
|
77
79
|
const normalizedClientId = clientId.trim();
|
|
78
|
-
return authRuntimeInternals.jwt.
|
|
79
|
-
cache: workosJwksCache,
|
|
80
|
+
return authRuntimeInternals.jwt.resolveCachedJwk(workosJwksCache, normalizedClientId, {
|
|
80
81
|
requestUrl: `${WORKOS_API_BASE_URL}/sso/jwks/${encodeURIComponent(normalizedClientId)}`,
|
|
81
|
-
|
|
82
|
+
kid,
|
|
82
83
|
errorMessage: `[@holo-js/auth-workos] Failed to load WorkOS JWKS for "${normalizedClientId}".`
|
|
83
84
|
});
|
|
84
85
|
}
|
|
@@ -89,14 +90,7 @@ async function verifyWorkosSessionToken(token, config) {
|
|
|
89
90
|
}
|
|
90
91
|
const parsed = parseJwt(token);
|
|
91
92
|
const headerKid = typeof parsed.header.kid === "string" ? parsed.header.kid : void 0;
|
|
92
|
-
const
|
|
93
|
-
const keys = await fetchWorkosJwks(clientId, { refresh });
|
|
94
|
-
return headerKid ? keys.find((candidate) => candidate.kid === headerKid) : keys[0];
|
|
95
|
-
};
|
|
96
|
-
let key = await resolveKey();
|
|
97
|
-
if (!key || !verifyJwtSignatureWithJwk(parsed, key)) {
|
|
98
|
-
key = await resolveKey(true);
|
|
99
|
-
}
|
|
93
|
+
const key = await resolveWorkosJwk(clientId, headerKid);
|
|
100
94
|
if (!key || !verifyJwtSignatureWithJwk(parsed, key)) {
|
|
101
95
|
throw new Error("[@holo-js/auth-workos] WorkOS token signature verification failed.");
|
|
102
96
|
}
|
|
@@ -280,12 +274,15 @@ function getWorkosStateCookieName(providerName) {
|
|
|
280
274
|
function createWorkosState() {
|
|
281
275
|
return randomBytes(32).toString("base64url");
|
|
282
276
|
}
|
|
277
|
+
function isHttpsRequest(request) {
|
|
278
|
+
return resolveRequestUrl(request).protocol === "https:";
|
|
279
|
+
}
|
|
283
280
|
function createStateCookie(providerName, state, request) {
|
|
284
281
|
return cookie(getWorkosStateCookieName(providerName), state, {
|
|
285
282
|
httpOnly: true,
|
|
286
283
|
maxAge: WORKOS_STATE_COOKIE_MAX_AGE_SECONDS,
|
|
287
284
|
sameSite: "lax",
|
|
288
|
-
secure:
|
|
285
|
+
secure: isHttpsRequest(request) || void 0
|
|
289
286
|
});
|
|
290
287
|
}
|
|
291
288
|
function clearStateCookie(providerName, request) {
|
|
@@ -293,7 +290,7 @@ function clearStateCookie(providerName, request) {
|
|
|
293
290
|
expires: /* @__PURE__ */ new Date(0),
|
|
294
291
|
httpOnly: true,
|
|
295
292
|
sameSite: "lax",
|
|
296
|
-
secure:
|
|
293
|
+
secure: isHttpsRequest(request) || void 0
|
|
297
294
|
});
|
|
298
295
|
}
|
|
299
296
|
function getWorkosStateFromRequest(request, providerName) {
|
|
@@ -384,6 +381,9 @@ function createWorkosSessionPayload(authenticated, session) {
|
|
|
384
381
|
})
|
|
385
382
|
});
|
|
386
383
|
}
|
|
384
|
+
function isWorkosSessionId(value) {
|
|
385
|
+
return typeof value === "string" && WORKOS_SESSION_ID_PATTERN.test(value);
|
|
386
|
+
}
|
|
387
387
|
function getWorkosLogoutSession(payload, providerName) {
|
|
388
388
|
if (!payload || typeof payload !== "object" || !("workos" in payload)) {
|
|
389
389
|
return null;
|
|
@@ -394,12 +394,12 @@ function getWorkosLogoutSession(payload, providerName) {
|
|
|
394
394
|
}
|
|
395
395
|
const provider = workos.provider;
|
|
396
396
|
const sessionId = workos.sessionId;
|
|
397
|
-
if (provider !== providerName
|
|
397
|
+
if (provider !== providerName) {
|
|
398
398
|
return null;
|
|
399
399
|
}
|
|
400
400
|
return Object.freeze({
|
|
401
401
|
provider,
|
|
402
|
-
sessionId
|
|
402
|
+
...isWorkosSessionId(sessionId) ? { sessionId } : {}
|
|
403
403
|
});
|
|
404
404
|
}
|
|
405
405
|
function serializeLocalUser(adapter, user, providerName) {
|
|
@@ -435,12 +435,15 @@ function resolveDisplayNameForUpdate(profile) {
|
|
|
435
435
|
const fullName = [profile.firstName?.trim(), profile.lastName?.trim()].filter(Boolean).join(" ").trim();
|
|
436
436
|
return fullName || void 0;
|
|
437
437
|
}
|
|
438
|
+
function resolveHostedPlaceholderEmail(profile) {
|
|
439
|
+
return `${profile.id}@workos.hosted.local`;
|
|
440
|
+
}
|
|
438
441
|
function resolveEmailForCreation(profile) {
|
|
439
442
|
const normalized = profile.email.trim();
|
|
440
|
-
if (normalized) {
|
|
443
|
+
if (normalized && profile.emailVerified === true) {
|
|
441
444
|
return normalized;
|
|
442
445
|
}
|
|
443
|
-
return
|
|
446
|
+
return resolveHostedPlaceholderEmail(profile);
|
|
444
447
|
}
|
|
445
448
|
function toAdapterInput(input) {
|
|
446
449
|
return Object.freeze(Object.fromEntries(
|
|
@@ -463,11 +466,12 @@ function resolveCreateUserInput(profile, mapper) {
|
|
|
463
466
|
}, profile));
|
|
464
467
|
}
|
|
465
468
|
function resolveUpdateUserInput(profile, mapper) {
|
|
469
|
+
const verifiedEmail = profile.emailVerified ? profile.email.trim() || void 0 : void 0;
|
|
466
470
|
return toAdapterInput({
|
|
467
|
-
email:
|
|
471
|
+
email: verifiedEmail,
|
|
468
472
|
name: resolveDisplayNameForUpdate(profile),
|
|
469
473
|
avatar: profile.profilePictureUrl,
|
|
470
|
-
email_verified_at:
|
|
474
|
+
email_verified_at: verifiedEmail ? /* @__PURE__ */ new Date() : void 0,
|
|
471
475
|
...mapper?.(profile) ?? {}
|
|
472
476
|
});
|
|
473
477
|
}
|
|
@@ -514,14 +518,15 @@ function getSessionTokenFromRequest(request, sessionCookie) {
|
|
|
514
518
|
return cookies[sessionCookie] ?? null;
|
|
515
519
|
}
|
|
516
520
|
function getHoloSessionIdFromRequest(request) {
|
|
517
|
-
const
|
|
521
|
+
const { session } = authRuntimeInternals.getRuntimeBindings();
|
|
522
|
+
const cookieHeader = session.sessionCookie("");
|
|
518
523
|
const separator = cookieHeader.indexOf("=");
|
|
519
524
|
const cookieName = separator > 0 ? decodeURIComponent(cookieHeader.slice(0, separator)) : "";
|
|
520
525
|
if (!cookieName) {
|
|
521
526
|
return null;
|
|
522
527
|
}
|
|
523
|
-
const
|
|
524
|
-
return
|
|
528
|
+
const value = parseCookieHeader(request.headers.get("cookie"))[cookieName];
|
|
529
|
+
return value ? session.decryptCookie(cookieName, value) ?? null : null;
|
|
525
530
|
}
|
|
526
531
|
async function reuseExistingHoloSession(request, authenticated) {
|
|
527
532
|
const bindings = authRuntimeInternals.getRuntimeBindings();
|
|
@@ -593,9 +598,6 @@ function getRecordField(input, name) {
|
|
|
593
598
|
const value = input[name];
|
|
594
599
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
595
600
|
}
|
|
596
|
-
function getRequestIpAddress(request) {
|
|
597
|
-
return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
|
|
598
|
-
}
|
|
599
601
|
async function authenticateWorkosCode(request, code, config) {
|
|
600
602
|
const { clientId, apiKey } = requireWorkosClientConfig(config);
|
|
601
603
|
const body = {
|
|
@@ -603,7 +605,7 @@ async function authenticateWorkosCode(request, code, config) {
|
|
|
603
605
|
client_id: clientId,
|
|
604
606
|
client_secret: apiKey,
|
|
605
607
|
code,
|
|
606
|
-
ip_address:
|
|
608
|
+
ip_address: authRuntimeInternals.resolveClientAddress(request),
|
|
607
609
|
user_agent: request.headers.get("user-agent")?.trim() || void 0
|
|
608
610
|
};
|
|
609
611
|
const response = await fetch(WORKOS_AUTHENTICATE_URL, {
|
|
@@ -626,7 +628,10 @@ async function authenticateWorkosCode(request, code, config) {
|
|
|
626
628
|
}
|
|
627
629
|
const accessToken = getStringField(payload, "accessToken", "access_token");
|
|
628
630
|
const session = getRecordField(payload, "session");
|
|
629
|
-
const sessionId = getStringField(session ?? {}, "id") ?? getStringField(payload, "sessionId", "session_id") ?? (accessToken ? getJwtStringClaim(accessToken, "sid") : void 0) ?? accessToken
|
|
631
|
+
const sessionId = getStringField(session ?? {}, "id") ?? getStringField(payload, "sessionId", "session_id") ?? (accessToken ? getJwtStringClaim(accessToken, "sid") : void 0) ?? (accessToken ? getJwtStringClaim(accessToken, "session_id") : void 0);
|
|
632
|
+
if (!isWorkosSessionId(sessionId)) {
|
|
633
|
+
throw new Error("[@holo-js/auth-workos] WorkOS callback did not include a valid session id.");
|
|
634
|
+
}
|
|
630
635
|
return Object.freeze({
|
|
631
636
|
sessionId,
|
|
632
637
|
identity: normalizeWorkosUserProfile({
|
|
@@ -656,6 +661,7 @@ async function syncIdentity(session, provider, options = {}) {
|
|
|
656
661
|
ownership: "save",
|
|
657
662
|
verifiedEmail,
|
|
658
663
|
email: profileEmail,
|
|
664
|
+
placeholderEmail: resolveHostedPlaceholderEmail(profile),
|
|
659
665
|
errorPrefix: "[@holo-js/auth-workos]",
|
|
660
666
|
identityLabel: "WorkOS",
|
|
661
667
|
createUserInput: () => resolveCreateUserInput(profile, options.user),
|
|
@@ -766,7 +772,7 @@ async function logoutWithWorkos(input, options = {}) {
|
|
|
766
772
|
const response = new Response(null, {
|
|
767
773
|
status: 303,
|
|
768
774
|
headers: {
|
|
769
|
-
Location: createLogoutUrl(workosSession.sessionId, request, options.returnTo)
|
|
775
|
+
Location: workosSession.sessionId ? createLogoutUrl(workosSession.sessionId, request, options.returnTo) : createWorkosReturnUrl(options.returnTo?.trim() || "/", request)
|
|
770
776
|
}
|
|
771
777
|
});
|
|
772
778
|
for (const cookie2 of local.cookies) {
|
|
@@ -809,6 +815,7 @@ async function completeWorkosAuth(input, options = {}) {
|
|
|
809
815
|
422
|
|
810
816
|
);
|
|
811
817
|
}
|
|
818
|
+
const stateCookieClear = clearStateCookie(providerConfig.name, request);
|
|
812
819
|
const session = await authenticateWorkosCode(request, code, providerConfig);
|
|
813
820
|
const authenticated = await syncIdentity(session, providerConfig.name, {
|
|
814
821
|
user: options.user
|
|
@@ -830,7 +837,7 @@ async function completeWorkosAuth(input, options = {}) {
|
|
|
830
837
|
...authSession,
|
|
831
838
|
cookies: Object.freeze([
|
|
832
839
|
...authSession.cookies,
|
|
833
|
-
|
|
840
|
+
stateCookieClear
|
|
834
841
|
])
|
|
835
842
|
})
|
|
836
843
|
}));
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/auth-workos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Holo-JS Framework - WorkOS auth integration contracts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/cobraprojects/holo-js.git",
|
|
10
|
+
"directory": "packages/auth-workos"
|
|
11
|
+
},
|
|
7
12
|
"exports": {
|
|
8
13
|
".": {
|
|
9
14
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +19,8 @@
|
|
|
14
19
|
"main": "./dist/index.mjs",
|
|
15
20
|
"types": "./dist/index.d.ts",
|
|
16
21
|
"files": [
|
|
17
|
-
"dist"
|
|
22
|
+
"dist",
|
|
23
|
+
"LICENSE"
|
|
18
24
|
],
|
|
19
25
|
"scripts": {
|
|
20
26
|
"build": "tsup",
|
|
@@ -23,16 +29,17 @@
|
|
|
23
29
|
"test": "vitest --run"
|
|
24
30
|
},
|
|
25
31
|
"dependencies": {
|
|
26
|
-
"@holo-js/auth": "^0.
|
|
27
|
-
"@holo-js/
|
|
32
|
+
"@holo-js/auth": "^0.4.0",
|
|
33
|
+
"@holo-js/kernel": "^0.4.0",
|
|
34
|
+
"@holo-js/session": "^0.4.0"
|
|
28
35
|
},
|
|
29
36
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^22.
|
|
31
|
-
"tsup": "^
|
|
32
|
-
"typescript": "^
|
|
33
|
-
"vitest": "^
|
|
37
|
+
"@types/node": "^22.20.5",
|
|
38
|
+
"tsup": "^9.0.0",
|
|
39
|
+
"typescript": "^6.0.3",
|
|
40
|
+
"vitest": "^5.0.5"
|
|
34
41
|
},
|
|
35
42
|
"engines": {
|
|
36
|
-
"node": "^22.
|
|
43
|
+
"node": "^22.18.0 || ^24.0.0 || ^26.0.0"
|
|
37
44
|
}
|
|
38
45
|
}
|