@holo-js/auth-workos 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +45 -47
  2. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -235,9 +235,23 @@ async function verifyWorkosSessionToken(token, config) {
235
235
  throw new Error("[@holo-js/auth-workos] WorkOS token signature verification failed.");
236
236
  }
237
237
  const exp = typeof parsed.payload.exp === "number" ? parsed.payload.exp : void 0;
238
- if (typeof exp === "number" && exp * 1e3 <= Date.now()) {
238
+ if (typeof exp !== "number") {
239
+ throw new Error("[@holo-js/auth-workos] WorkOS token did not include an expiration.");
240
+ }
241
+ if (exp * 1e3 <= Date.now()) {
239
242
  throw new Error("[@holo-js/auth-workos] WorkOS token has expired.");
240
243
  }
244
+ if (typeof parsed.payload.sub !== "string" || !parsed.payload.sub.trim()) {
245
+ throw new Error("[@holo-js/auth-workos] WorkOS token did not include a subject.");
246
+ }
247
+ if ((typeof parsed.payload.sid !== "string" || !parsed.payload.sid.trim()) && (typeof parsed.payload.session_id !== "string" || !parsed.payload.session_id.trim())) {
248
+ throw new Error("[@holo-js/auth-workos] WorkOS token did not include a session id.");
249
+ }
250
+ const audience = parsed.payload.aud;
251
+ const audiences = Array.isArray(audience) ? audience.filter((value) => typeof value === "string") : typeof audience === "string" ? [audience] : [];
252
+ if (!audiences.includes(clientId)) {
253
+ throw new Error(`[@holo-js/auth-workos] WorkOS token audience is not valid for client "${clientId}".`);
254
+ }
241
255
  const nbf = typeof parsed.payload.nbf === "number" ? parsed.payload.nbf : void 0;
242
256
  if (typeof nbf === "number" && nbf * 1e3 > Date.now()) {
243
257
  throw new Error("[@holo-js/auth-workos] WorkOS token is not valid yet.");
@@ -444,10 +458,23 @@ function createLogoutUrl(sessionId, request, returnTo) {
444
458
  const url = new URL(WORKOS_LOGOUT_URL);
445
459
  url.searchParams.set("session_id", sessionId);
446
460
  if (returnTo?.trim()) {
447
- url.searchParams.set("return_to", new URL(returnTo, request.url).toString());
461
+ url.searchParams.set("return_to", createWorkosReturnUrl(returnTo, request));
448
462
  }
449
463
  return url.toString();
450
464
  }
465
+ function createWorkosReturnUrl(returnTo, request) {
466
+ const requestUrl = new URL(request.url);
467
+ let target;
468
+ try {
469
+ target = new URL(returnTo, requestUrl.origin);
470
+ } catch {
471
+ return requestUrl.origin;
472
+ }
473
+ if (target.origin !== requestUrl.origin) {
474
+ return requestUrl.origin;
475
+ }
476
+ return target.toString();
477
+ }
451
478
  function createWorkosErrorResponse(error, code) {
452
479
  return Response.json({
453
480
  ok: false,
@@ -670,21 +697,6 @@ async function ensureNoUnexpectedEmailCollision(adapter, providerName, profile,
670
697
  });
671
698
  }
672
699
  }
673
- async function assertUserLinkAvailable(providerName, authProvider, adapter, user, workosUserId) {
674
- const existing = await getBindings().identityStore.findByUserId(
675
- providerName,
676
- authProvider,
677
- requireUserId(adapter, user, "[@holo-js/auth-workos] Linked users must expose a serializable id.")
678
- );
679
- if (existing && existing.providerUserId !== workosUserId) {
680
- throw new WorkosAuthConflictError({
681
- provider: providerName,
682
- workosUserId,
683
- email: existing.email,
684
- message: `[@holo-js/auth-workos] Local user is already linked to WorkOS identity "${existing.providerUserId}".`
685
- });
686
- }
687
- }
688
700
  function isEmailVerificationRequired() {
689
701
  return authRuntimeInternals.getRuntimeBindings().config.emailVerification.required === true;
690
702
  }
@@ -855,16 +867,19 @@ async function syncIdentity(session, provider, options = {}) {
855
867
  const existingLinkedUser = await adapter.findById(existingIdentity.userId);
856
868
  let linkedUser = existingLinkedUser ? requireUserRecord(existingLinkedUser, "[@holo-js/auth-workos] Auth provider lookups must return object users.") : null;
857
869
  if (!linkedUser) {
858
- linkedUser = verifiedEmail ? await findUserByEmail(adapter, verifiedEmail) : null;
859
- if (linkedUser) {
860
- await assertUserLinkAvailable(providerName, authProvider, adapter, linkedUser, profile.id);
861
- }
862
- if (!linkedUser) {
863
- linkedUser = requireUserRecord(
864
- await adapter.create(resolveCreateUserInput(profile, options.user)),
865
- "[@holo-js/auth-workos] Auth provider create() must return an object user."
866
- );
870
+ const emailMatchedUser = verifiedEmail ? await findUserByEmail(adapter, verifiedEmail) : null;
871
+ if (emailMatchedUser) {
872
+ throw new WorkosAuthConflictError({
873
+ provider: providerName,
874
+ workosUserId: profile.id,
875
+ email: verifiedEmail,
876
+ message: `[@holo-js/auth-workos] WorkOS email "${verifiedEmail}" matches an existing local user and must be linked explicitly.`
877
+ });
867
878
  }
879
+ linkedUser = requireUserRecord(
880
+ await adapter.create(resolveCreateUserInput(profile, options.user)),
881
+ "[@holo-js/auth-workos] Auth provider create() must return an object user."
882
+ );
868
883
  const relinked = await updateLocalUser(adapter, linkedUser, resolveUpdateUserInput(profile, options.user));
869
884
  const relinkedUser = relinked.user;
870
885
  const identity3 = createIdentityRecord({
@@ -926,28 +941,11 @@ async function syncIdentity(session, provider, options = {}) {
926
941
  }
927
942
  let localUser = verifiedEmail ? await findUserByEmail(adapter, verifiedEmail) : null;
928
943
  if (localUser) {
929
- await assertUserLinkAvailable(providerName, authProvider, adapter, localUser, profile.id);
930
- const linked = await updateLocalUser(adapter, localUser, resolveUpdateUserInput(profile, options.user));
931
- const identity2 = createIdentityRecord({
932
- provider: providerName,
933
- guard,
934
- authProvider,
935
- userId: requireUserId(
936
- adapter,
937
- linked.user,
938
- "[@holo-js/auth-workos] Linked local users must expose a serializable id."
939
- ),
940
- profile
941
- });
942
- await identityStore.save(identity2);
943
- return Object.freeze({
944
+ throw new WorkosAuthConflictError({
944
945
  provider: providerName,
945
- guard,
946
- authProvider,
947
- status: "linked",
948
- user: serializeLocalUser(adapter, linked.user, authProvider),
949
- identity: identity2,
950
- session
946
+ workosUserId: profile.id,
947
+ email: verifiedEmail,
948
+ message: `[@holo-js/auth-workos] WorkOS email "${verifiedEmail}" matches an existing local user and must be linked explicitly.`
951
949
  });
952
950
  }
953
951
  localUser = requireUserRecord(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/auth-workos",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Holo-JS Framework - WorkOS auth integration contracts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,9 +23,9 @@
23
23
  "test": "vitest --run"
24
24
  },
25
25
  "dependencies": {
26
- "@holo-js/auth": "^0.1.5",
27
- "@holo-js/config": "^0.1.5",
28
- "@holo-js/session": "^0.1.5"
26
+ "@holo-js/auth": "^0.1.6",
27
+ "@holo-js/config": "^0.1.6",
28
+ "@holo-js/session": "^0.1.6"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.10.2",