@holo-js/auth-workos 0.1.4 → 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 (3) hide show
  1. package/dist/index.d.ts +122 -26
  2. package/dist/index.mjs +616 -110
  3. package/package.json +5 -5
package/dist/index.d.ts CHANGED
@@ -1,17 +1,40 @@
1
- import { AuthUserLike, AuthEstablishedSession, authRuntimeInternals } from '@holo-js/auth';
2
- import { AuthWorkosProviderConfig } from '@holo-js/config';
1
+ import { AuthResult, AuthLogoutResult, AuthFieldErrors, AuthenticatedAuthUser, AuthEstablishedSession, authRuntimeInternals } from '@holo-js/auth';
2
+ import { NormalizedAuthWorkosProviderConfig } from '@holo-js/config';
3
3
 
4
+ type WorkosJsonValue = string | number | boolean | null | readonly WorkosJsonValue[] | {
5
+ readonly [key: string]: WorkosJsonValue;
6
+ };
4
7
  interface WorkosIdentityProfile {
5
8
  readonly id: string;
6
- readonly email?: string;
7
- readonly emailVerified?: boolean;
9
+ readonly email: string;
10
+ readonly name: string;
11
+ readonly emailVerified: boolean;
8
12
  readonly firstName?: string;
9
13
  readonly lastName?: string;
10
- readonly name?: string;
11
- readonly avatar?: string;
14
+ readonly profilePictureUrl?: string;
15
+ readonly externalId?: string;
12
16
  readonly organizationId?: string;
13
- readonly raw?: unknown;
17
+ readonly metadata: Readonly<Record<string, WorkosJsonValue>>;
18
+ readonly createdAt?: string;
19
+ readonly updatedAt?: string;
20
+ readonly raw: Readonly<Record<string, WorkosJsonValue>>;
14
21
  }
22
+ type WorkosUserAttributeValue = string | number | boolean | Date | null | undefined | readonly WorkosUserAttributeValue[] | {
23
+ readonly [key: string]: WorkosUserAttributeValue;
24
+ };
25
+ type WorkosUserAttributes = Readonly<Record<string, WorkosUserAttributeValue>>;
26
+ type WorkosDefaultUserAttributes = {
27
+ readonly email: string;
28
+ readonly name: string;
29
+ };
30
+ type WorkosUserMapper<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes> = (workosUser: WorkosIdentityProfile) => TUserAttributes;
31
+ type WorkosCompleteAuthOptions<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes> = {
32
+ readonly provider?: string;
33
+ readonly user?: WorkosUserMapper<TUserAttributes>;
34
+ };
35
+ type WorkosSyncIdentityOptions<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes> = {
36
+ readonly user?: WorkosUserMapper<TUserAttributes>;
37
+ };
15
38
  interface WorkosVerifiedSession {
16
39
  readonly sessionId: string;
17
40
  readonly identity: WorkosIdentityProfile;
@@ -19,21 +42,45 @@ interface WorkosVerifiedSession {
19
42
  readonly expiresAt?: Date;
20
43
  readonly raw?: unknown;
21
44
  }
45
+ interface WorkosLogoutSession {
46
+ readonly provider: string;
47
+ readonly sessionId: string;
48
+ }
49
+ type WorkosAuthenticatedUser<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes> = AuthenticatedAuthUser & Omit<TUserAttributes, 'can' | 'id'> & {
50
+ readonly id: string | number;
51
+ };
52
+ type WorkosHostedAuthFailureFields = AuthFieldErrors<'_root'>;
53
+ interface WorkosCompleteAuthData<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes> {
54
+ readonly provider: string;
55
+ readonly guard: string;
56
+ readonly authProvider: string;
57
+ readonly status: WorkosSyncStatus;
58
+ readonly user: WorkosAuthenticatedUser<TUserAttributes>;
59
+ readonly identity: HostedIdentityRecord;
60
+ readonly session: WorkosVerifiedSession;
61
+ readonly authSession?: AuthEstablishedSession;
62
+ }
63
+ type WorkosCompleteAuthResult<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes> = AuthResult<WorkosCompleteAuthData<TUserAttributes>, string, WorkosHostedAuthFailureFields>;
64
+ type WorkosLogoutErrorCode = 'workos_logout_failed' | 'workos_session_missing';
65
+ interface WorkosLogoutData {
66
+ readonly url: string;
67
+ readonly local: AuthLogoutResult;
68
+ }
69
+ type WorkosLogoutResult = AuthResult<WorkosLogoutData, WorkosLogoutErrorCode, WorkosHostedAuthFailureFields>;
22
70
  interface WorkosVerifyRequestContext {
23
71
  readonly provider: string;
24
72
  readonly request: Request;
25
- readonly config: AuthWorkosProviderConfig;
73
+ readonly config: NormalizedAuthWorkosProviderConfig;
26
74
  }
27
75
  interface WorkosVerifySessionContext {
28
76
  readonly provider: string;
29
77
  readonly token: string;
30
- readonly config: AuthWorkosProviderConfig;
78
+ readonly config: NormalizedAuthWorkosProviderConfig;
31
79
  }
32
80
  interface WorkosProviderRuntime {
33
81
  verifyRequest?(context: WorkosVerifyRequestContext): Promise<WorkosVerifiedSession | null>;
34
82
  verifySession?(context: WorkosVerifySessionContext): Promise<WorkosVerifiedSession | null>;
35
83
  }
36
- type RuntimeAuthProviderAdapter = ReturnType<typeof authRuntimeInternals.getRuntimeBindings>['providers'][string];
37
84
  interface HostedIdentityRecord {
38
85
  readonly provider: string;
39
86
  readonly providerUserId: string;
@@ -52,12 +99,12 @@ interface HostedIdentityStore {
52
99
  save(record: HostedIdentityRecord): Promise<void>;
53
100
  }
54
101
  type WorkosSyncStatus = 'created' | 'updated' | 'linked' | 'relinked';
55
- interface WorkosAuthenticationResult {
102
+ interface WorkosAuthenticationResult<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes> {
56
103
  readonly provider: string;
57
104
  readonly guard: string;
58
105
  readonly authProvider: string;
59
106
  readonly status: WorkosSyncStatus;
60
- readonly user: AuthUserLike;
107
+ readonly user: WorkosAuthenticatedUser<TUserAttributes>;
61
108
  readonly identity: HostedIdentityRecord;
62
109
  readonly session: WorkosVerifiedSession;
63
110
  readonly authSession?: AuthEstablishedSession;
@@ -71,9 +118,20 @@ interface ConfigureWorkosAuthRuntimeOptions {
71
118
  readonly identityStore?: HostedIdentityStore;
72
119
  }
73
120
  interface WorkosAuthFacade {
121
+ loginWithWorkos(request: Request, options?: {
122
+ readonly provider?: string;
123
+ }): Promise<Response>;
124
+ registerWithWorkos(request: Request, options?: {
125
+ readonly provider?: string;
126
+ }): Promise<Response>;
127
+ logoutWithWorkos(request: Request, options?: {
128
+ readonly provider?: string;
129
+ readonly returnTo?: string;
130
+ }): Promise<WorkosLogoutResult>;
131
+ completeWorkosAuth<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes>(request: Request, options?: WorkosCompleteAuthOptions<TUserAttributes>): Promise<WorkosCompleteAuthResult<TUserAttributes>>;
74
132
  verifyRequest(request: Request, provider?: string): Promise<WorkosVerifiedSession | null>;
75
133
  verifySession(token: string, provider?: string): Promise<WorkosVerifiedSession | null>;
76
- syncIdentity(session: WorkosVerifiedSession, provider?: string): Promise<WorkosAuthenticationResult>;
134
+ syncIdentity<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes>(session: WorkosVerifiedSession, provider?: string, options?: WorkosSyncIdentityOptions<TUserAttributes>): Promise<WorkosAuthenticationResult<TUserAttributes>>;
77
135
  authenticate(request: Request, provider?: string): Promise<WorkosAuthenticationResult | null>;
78
136
  }
79
137
  declare class WorkosAuthConflictError extends Error {
@@ -88,35 +146,73 @@ declare class WorkosAuthConflictError extends Error {
88
146
  readonly message: string;
89
147
  });
90
148
  }
149
+
150
+ type RuntimeAuthProviderAdapter = ReturnType<typeof authRuntimeInternals.getRuntimeBindings>['providers'][string];
151
+ type WorkosRequestHeaders = Headers | ReadonlyArray<readonly [string, string]> | Record<string, string | readonly string[] | undefined> | {
152
+ readonly get?: (name: string) => string | null | undefined;
153
+ readonly forEach?: (callback: (value: string, key: string) => void) => void;
154
+ readonly entries?: () => Iterable<readonly [string, string]>;
155
+ };
156
+ type WorkosRequestLike = {
157
+ readonly method?: string;
158
+ readonly path?: string;
159
+ readonly url?: string | URL;
160
+ readonly headers?: WorkosRequestHeaders;
161
+ readonly request?: Request;
162
+ readonly req?: Request | {
163
+ readonly method?: string;
164
+ readonly url?: string;
165
+ readonly headers?: WorkosRequestHeaders;
166
+ };
167
+ readonly node?: {
168
+ readonly req?: {
169
+ readonly method?: string;
170
+ readonly url?: string;
171
+ readonly headers?: WorkosRequestHeaders;
172
+ };
173
+ };
174
+ readonly web?: {
175
+ readonly request?: Request;
176
+ };
177
+ };
178
+ type WorkosRequestInput = Request | WorkosRequestLike;
91
179
  declare function getBindings(): WorkosAuthBindings;
92
180
  declare function resolveConfiguredProviderName(provider?: string): string;
93
181
  declare function getConfiguredProviderConfig(provider?: string): {
94
182
  readonly name: string;
95
- readonly clientId?: string;
96
- readonly apiKey?: string;
97
- readonly cookiePassword?: string;
98
- readonly redirectUri?: string;
99
- readonly sessionCookie: string;
100
- readonly guard?: string;
101
- readonly mapToProvider?: string;
102
- };
183
+ } & NormalizedAuthWorkosProviderConfig;
103
184
  declare function resolveGuardAndProvider(provider?: string): {
104
185
  readonly guard: string;
105
186
  readonly authProvider: string;
106
187
  readonly adapter: RuntimeAuthProviderAdapter;
107
188
  };
108
- declare function resolveDisplayName(profile: WorkosIdentityProfile): string | undefined;
189
+ declare function resolveDisplayName(profile: WorkosIdentityProfile): string;
109
190
  declare function resolveEmailForCreation(profile: WorkosIdentityProfile): string;
110
191
  declare function normalizeHostedProfile(profile: WorkosIdentityProfile): Readonly<Record<string, unknown>>;
111
192
  declare function getSessionTokenFromRequest(request: Request, sessionCookie: string): string | null;
112
193
  declare function verifySession(token: string, provider?: string): Promise<WorkosVerifiedSession | null>;
113
- declare function verifyRequest(request: Request, provider?: string): Promise<WorkosVerifiedSession | null>;
114
- declare function syncIdentity(session: WorkosVerifiedSession, provider?: string): Promise<WorkosAuthenticationResult>;
115
- declare function authenticate(request: Request, provider?: string): Promise<WorkosAuthenticationResult | null>;
194
+ declare function verifyRequest(input: WorkosRequestInput, provider?: string): Promise<WorkosVerifiedSession | null>;
195
+ declare function syncIdentity<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes>(session: WorkosVerifiedSession, provider?: string, options?: WorkosSyncIdentityOptions<TUserAttributes>): Promise<WorkosAuthenticationResult<TUserAttributes>>;
196
+ declare function authenticate(input: WorkosRequestInput, provider?: string): Promise<WorkosAuthenticationResult | null>;
197
+ declare function loginWithWorkos(input: WorkosRequestInput, options?: {
198
+ readonly provider?: string;
199
+ }): Promise<Response>;
200
+ declare function registerWithWorkos(input: WorkosRequestInput, options?: {
201
+ readonly provider?: string;
202
+ }): Promise<Response>;
203
+ declare function logoutWithWorkos(input: WorkosRequestInput, options?: {
204
+ readonly provider?: string;
205
+ readonly returnTo?: string;
206
+ }): Promise<WorkosLogoutResult>;
207
+ declare function completeWorkosAuth<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes>(input: WorkosRequestInput, options?: WorkosCompleteAuthOptions<TUserAttributes>): Promise<WorkosCompleteAuthResult<TUserAttributes>>;
116
208
  declare function configureWorkosAuthRuntime(bindings?: ConfigureWorkosAuthRuntimeOptions): void;
117
209
  declare function resetWorkosAuthRuntime(): void;
118
210
  declare const workosAuth: Readonly<{
119
211
  authenticate: typeof authenticate;
212
+ completeWorkosAuth: typeof completeWorkosAuth;
213
+ loginWithWorkos: typeof loginWithWorkos;
214
+ logoutWithWorkos: typeof logoutWithWorkos;
215
+ registerWithWorkos: typeof registerWithWorkos;
120
216
  syncIdentity: typeof syncIdentity;
121
217
  verifyRequest: typeof verifyRequest;
122
218
  verifySession: typeof verifySession;
@@ -132,4 +228,4 @@ declare const workosAuthInternals: {
132
228
  resolveGuardAndProvider: typeof resolveGuardAndProvider;
133
229
  };
134
230
 
135
- export { type ConfigureWorkosAuthRuntimeOptions, type HostedIdentityRecord, type HostedIdentityStore, type WorkosAuthBindings, WorkosAuthConflictError, type WorkosAuthFacade, type WorkosAuthenticationResult, type WorkosIdentityProfile, type WorkosProviderRuntime, type WorkosSyncStatus, type WorkosVerifiedSession, type WorkosVerifyRequestContext, type WorkosVerifySessionContext, authenticate, configureWorkosAuthRuntime, resetWorkosAuthRuntime, syncIdentity, verifyRequest, verifySession, workosAuth, workosAuthInternals };
231
+ export { type ConfigureWorkosAuthRuntimeOptions, type HostedIdentityRecord, type HostedIdentityStore, type WorkosAuthBindings, WorkosAuthConflictError, type WorkosAuthFacade, type WorkosAuthenticatedUser, type WorkosAuthenticationResult, type WorkosCompleteAuthData, type WorkosCompleteAuthOptions, type WorkosCompleteAuthResult, type WorkosDefaultUserAttributes, type WorkosHostedAuthFailureFields, type WorkosIdentityProfile, type WorkosJsonValue, type WorkosLogoutData, type WorkosLogoutErrorCode, type WorkosLogoutResult, type WorkosLogoutSession, type WorkosProviderRuntime, type WorkosSyncIdentityOptions, type WorkosSyncStatus, type WorkosUserAttributeValue, type WorkosUserAttributes, type WorkosVerifiedSession, type WorkosVerifyRequestContext, type WorkosVerifySessionContext, authenticate, completeWorkosAuth, configureWorkosAuthRuntime, loginWithWorkos, logoutWithWorkos, registerWithWorkos, resetWorkosAuthRuntime, syncIdentity, verifyRequest, verifySession, workosAuth, workosAuthInternals };
package/dist/index.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  // src/index.ts
2
- import { createPublicKey, verify as verifySignature } from "crypto";
3
- import { authRuntimeInternals } from "@holo-js/auth";
4
- import { parseCookieHeader } from "@holo-js/session";
2
+ import { createPublicKey, randomBytes, verify as verifySignature } from "crypto";
3
+ import { authRuntimeInternals, getAuthRuntime } from "@holo-js/auth";
4
+ import { cookie, parseCookieHeader } from "@holo-js/session";
5
+
6
+ // src/contracts.ts
5
7
  var WorkosAuthConflictError = class extends Error {
6
8
  code = "workos_identity_conflict";
7
9
  provider;
@@ -15,15 +17,127 @@ var WorkosAuthConflictError = class extends Error {
15
17
  this.email = options.email;
16
18
  }
17
19
  };
18
- var workosBindings;
20
+
21
+ // src/index.ts
22
+ var WORKOS_RUNTIME_STATE_KEY = "__holoJsAuthWorkosRuntime";
19
23
  var WORKOS_API_BASE_URL = "https://api.workos.com";
24
+ var WORKOS_AUTHORIZE_URL = `${WORKOS_API_BASE_URL}/user_management/authorize`;
25
+ var WORKOS_AUTHENTICATE_URL = `${WORKOS_API_BASE_URL}/user_management/authenticate`;
26
+ var WORKOS_LOGOUT_URL = `${WORKOS_API_BASE_URL}/user_management/sessions/logout`;
27
+ var WORKOS_STATE_COOKIE_MAX_AGE_SECONDS = 300;
20
28
  var workosDefaultProviderRuntimeCache = /* @__PURE__ */ new Map();
21
29
  var workosJwksCache = /* @__PURE__ */ new Map();
22
30
  var AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
31
+ function getRuntimeState() {
32
+ const runtimeGlobal = globalThis;
33
+ if (!runtimeGlobal[WORKOS_RUNTIME_STATE_KEY]) {
34
+ Object.defineProperty(runtimeGlobal, WORKOS_RUNTIME_STATE_KEY, {
35
+ value: {},
36
+ enumerable: false,
37
+ configurable: true,
38
+ writable: true
39
+ });
40
+ }
41
+ return runtimeGlobal[WORKOS_RUNTIME_STATE_KEY];
42
+ }
23
43
  function throwUnconfigured() {
24
44
  throw new Error("[@holo-js/auth-workos] WorkOS auth runtime is not configured yet.");
25
45
  }
46
+ function isPlainHeaderRecord(value) {
47
+ return Boolean(value) && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype;
48
+ }
49
+ function appendKnownHeaders(headers, input) {
50
+ for (const name of ["authorization", "cookie", "host", "x-forwarded-host", "x-forwarded-proto"]) {
51
+ const value = input.get?.(name);
52
+ if (typeof value === "string" && value) {
53
+ headers.set(name, value);
54
+ }
55
+ }
56
+ }
57
+ function hasHeaderForEach(input) {
58
+ return !Array.isArray(input) && "forEach" in input && typeof input.forEach === "function";
59
+ }
60
+ function hasHeaderEntries(input) {
61
+ return !Array.isArray(input) && "entries" in input && typeof input.entries === "function";
62
+ }
63
+ function hasHeaderGet(input) {
64
+ return !Array.isArray(input) && "get" in input && typeof input.get === "function";
65
+ }
66
+ function normalizeRequestHeaders(input) {
67
+ const headers = new Headers();
68
+ if (!input) {
69
+ return headers;
70
+ }
71
+ if (input instanceof Headers || Array.isArray(input)) {
72
+ new Headers(input).forEach((value, name) => headers.append(name, value));
73
+ return headers;
74
+ }
75
+ if (hasHeaderForEach(input)) {
76
+ input.forEach((value, name) => headers.append(name, value));
77
+ return headers;
78
+ }
79
+ if (hasHeaderEntries(input)) {
80
+ for (const [name, value] of input.entries()) {
81
+ headers.append(name, value);
82
+ }
83
+ return headers;
84
+ }
85
+ if (hasHeaderGet(input)) {
86
+ appendKnownHeaders(headers, input);
87
+ return headers;
88
+ }
89
+ if (isPlainHeaderRecord(input)) {
90
+ for (const [name, value] of Object.entries(input)) {
91
+ if (typeof value === "string") {
92
+ headers.append(name, value);
93
+ continue;
94
+ }
95
+ if (Array.isArray(value)) {
96
+ const separator = name.toLowerCase() === "cookie" ? "; " : ",";
97
+ const joined = value.filter((entry) => typeof entry === "string").join(separator);
98
+ if (joined) {
99
+ headers.append(name, joined);
100
+ }
101
+ }
102
+ }
103
+ }
104
+ return headers;
105
+ }
106
+ function getRequestFromLikeInput(input) {
107
+ return input.request ?? input.web?.request ?? (input.req instanceof Request ? input.req : void 0);
108
+ }
109
+ function getRequestLikeHeaders(input) {
110
+ return input.headers ?? (typeof input.req === "object" && !(input.req instanceof Request) ? input.req.headers : void 0) ?? input.node?.req?.headers;
111
+ }
112
+ function getRequestLikeMethod(input) {
113
+ return input.method ?? (typeof input.req === "object" && !(input.req instanceof Request) ? input.req.method : void 0) ?? input.node?.req?.method ?? "GET";
114
+ }
115
+ function getRequestLikeUrl(input, headers) {
116
+ const url = (typeof input.url === "string" ? input.url : input.url?.toString()) ?? (typeof input.req === "object" && !(input.req instanceof Request) ? input.req.url : void 0) ?? input.node?.req?.url ?? input.path ?? "/";
117
+ try {
118
+ return new URL(url).toString();
119
+ } catch {
120
+ const protocol = headers.get("x-forwarded-proto") ?? "http";
121
+ const host = headers.get("x-forwarded-host") ?? headers.get("host") ?? "localhost";
122
+ return new URL(url, `${protocol}://${host}`).toString();
123
+ }
124
+ }
125
+ function normalizeWorkosRequest(input) {
126
+ if (input instanceof Request) {
127
+ return input;
128
+ }
129
+ const request = getRequestFromLikeInput(input);
130
+ if (request) {
131
+ return request;
132
+ }
133
+ const headers = normalizeRequestHeaders(getRequestLikeHeaders(input));
134
+ return new Request(getRequestLikeUrl(input, headers), {
135
+ method: getRequestLikeMethod(input),
136
+ headers
137
+ });
138
+ }
26
139
  function getBindings() {
140
+ const workosBindings = getRuntimeState().bindings;
27
141
  if (!workosBindings?.identityStore) {
28
142
  throwUnconfigured();
29
143
  }
@@ -51,6 +165,14 @@ function parseJwt(token) {
51
165
  signingInput: Buffer.from(`${segments[0]}.${segments[1]}`, "utf8")
52
166
  };
53
167
  }
168
+ function getJwtStringClaim(token, claim) {
169
+ try {
170
+ const value = parseJwt(token).payload[claim];
171
+ return typeof value === "string" && value.trim() ? value : void 0;
172
+ } catch {
173
+ return void 0;
174
+ }
175
+ }
54
176
  function verifyJwtSignatureWithJwk(token, jwk) {
55
177
  const algorithm = typeof token.header.alg === "string" ? token.header.alg : "";
56
178
  const key = createPublicKey({ key: jwk, format: "jwk" });
@@ -113,29 +235,66 @@ async function verifyWorkosSessionToken(token, config) {
113
235
  throw new Error("[@holo-js/auth-workos] WorkOS token signature verification failed.");
114
236
  }
115
237
  const exp = typeof parsed.payload.exp === "number" ? parsed.payload.exp : void 0;
116
- 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()) {
117
242
  throw new Error("[@holo-js/auth-workos] WorkOS token has expired.");
118
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
+ }
119
255
  const nbf = typeof parsed.payload.nbf === "number" ? parsed.payload.nbf : void 0;
120
256
  if (typeof nbf === "number" && nbf * 1e3 > Date.now()) {
121
257
  throw new Error("[@holo-js/auth-workos] WorkOS token is not valid yet.");
122
258
  }
123
259
  return parsed.payload;
124
260
  }
261
+ function toWorkosJsonValue(value) {
262
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
263
+ return value;
264
+ }
265
+ if (Array.isArray(value)) {
266
+ return value.map((item) => toWorkosJsonValue(item)).filter((item) => typeof item !== "undefined");
267
+ }
268
+ if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
269
+ return Object.freeze(Object.fromEntries(
270
+ Object.entries(value).map(([key, item]) => [key, toWorkosJsonValue(item)]).filter((entry) => typeof entry[1] !== "undefined")
271
+ ));
272
+ }
273
+ return void 0;
274
+ }
275
+ function toWorkosJsonObject(value) {
276
+ const normalized = toWorkosJsonValue(value);
277
+ return normalized && typeof normalized === "object" && !Array.isArray(normalized) ? normalized : Object.freeze({});
278
+ }
125
279
  function normalizeWorkosUserProfile(user) {
126
280
  const firstName = typeof user.firstName === "string" ? user.firstName : typeof user.first_name === "string" ? user.first_name : void 0;
127
281
  const lastName = typeof user.lastName === "string" ? user.lastName : typeof user.last_name === "string" ? user.last_name : void 0;
128
- const name = typeof user.name === "string" ? user.name : [firstName, lastName].filter(Boolean).join(" ").trim() || void 0;
282
+ const name = typeof user.name === "string" ? user.name : [firstName, lastName].filter(Boolean).join(" ").trim();
283
+ const email = typeof user.email === "string" ? user.email.trim() : "";
129
284
  return Object.freeze({
130
285
  id: String(user.id ?? ""),
131
- email: typeof user.email === "string" ? user.email : void 0,
286
+ email,
132
287
  emailVerified: user.emailVerified === true || user.email_verified === true,
133
288
  firstName,
134
289
  lastName,
135
- name,
136
- avatar: typeof user.profilePictureUrl === "string" ? user.profilePictureUrl : typeof user.profile_picture_url === "string" ? user.profile_picture_url : void 0,
290
+ name: name || email || String(user.id ?? ""),
291
+ profilePictureUrl: typeof user.profilePictureUrl === "string" ? user.profilePictureUrl : typeof user.profile_picture_url === "string" ? user.profile_picture_url : void 0,
292
+ externalId: typeof user.externalId === "string" ? user.externalId : typeof user.external_id === "string" ? user.external_id : void 0,
137
293
  organizationId: typeof user.organizationId === "string" ? user.organizationId : typeof user.organization_id === "string" ? user.organization_id : void 0,
138
- raw: user
294
+ metadata: toWorkosJsonObject(user.metadata),
295
+ createdAt: typeof user.createdAt === "string" ? user.createdAt : typeof user.created_at === "string" ? user.created_at : void 0,
296
+ updatedAt: typeof user.updatedAt === "string" ? user.updatedAt : typeof user.updated_at === "string" ? user.updated_at : void 0,
297
+ raw: toWorkosJsonObject(user)
139
298
  });
140
299
  }
141
300
  async function fetchWorkosUserProfile(userId, config) {
@@ -193,7 +352,11 @@ function resolveConfiguredProviderName(provider) {
193
352
  if (provider?.trim()) {
194
353
  return provider.trim();
195
354
  }
196
- const configuredProviders = Object.keys(bindings.config.workos);
355
+ const configuredDefaultProvider = typeof bindings.config.workos.provider === "string" ? bindings.config.workos.provider.trim() : "";
356
+ if (configuredDefaultProvider) {
357
+ return configuredDefaultProvider;
358
+ }
359
+ const configuredProviders = Object.entries(bindings.config.workos).flatMap(([name, value]) => name !== "provider" && name !== "identityStore" && isNormalizedWorkosProviderConfig(value) ? [name] : []);
197
360
  if (configuredProviders.length === 0) {
198
361
  return "default";
199
362
  }
@@ -205,18 +368,20 @@ function resolveConfiguredProviderName(provider) {
205
368
  }
206
369
  throw new Error("[@holo-js/auth-workos] WorkOS provider name is required when multiple auth.workos entries exist.");
207
370
  }
371
+ function isNormalizedWorkosProviderConfig(value) {
372
+ return value !== null && typeof value === "object" && "sessionCookie" in value && typeof value.sessionCookie === "string";
373
+ }
208
374
  function getConfiguredProviderConfig(provider) {
209
375
  const providerName = resolveConfiguredProviderName(provider);
210
376
  const authBindings = authRuntimeInternals.getRuntimeBindings();
211
377
  const configured = authBindings.config.workos[providerName];
212
- if (!configured) {
378
+ if (!isNormalizedWorkosProviderConfig(configured)) {
213
379
  throw new Error(`[@holo-js/auth-workos] WorkOS provider "${providerName}" is not configured in auth.workos.`);
214
380
  }
215
381
  return {
216
382
  name: providerName,
217
383
  clientId: configured.clientId,
218
384
  apiKey: configured.apiKey,
219
- cookiePassword: configured.cookiePassword,
220
385
  redirectUri: configured.redirectUri,
221
386
  sessionCookie: configured.sessionCookie,
222
387
  guard: configured.guard,
@@ -227,6 +392,98 @@ function getProviderRuntime(provider) {
227
392
  const providerName = resolveConfiguredProviderName(provider);
228
393
  return getBindings().providers[providerName] ?? createDefaultProviderRuntime(providerName, getConfiguredProviderConfig(providerName));
229
394
  }
395
+ function requireWorkosClientConfig(config) {
396
+ const clientId = config.clientId?.trim();
397
+ const apiKey = config.apiKey?.trim();
398
+ const redirectUri = config.redirectUri?.trim();
399
+ if (!clientId) {
400
+ throw new Error("[@holo-js/auth-workos] WorkOS AuthKit requires clientId to be configured.");
401
+ }
402
+ if (!apiKey) {
403
+ throw new Error("[@holo-js/auth-workos] WorkOS AuthKit requires apiKey to be configured.");
404
+ }
405
+ if (!redirectUri) {
406
+ throw new Error("[@holo-js/auth-workos] WorkOS AuthKit requires redirectUri to be configured.");
407
+ }
408
+ return { clientId, apiKey, redirectUri };
409
+ }
410
+ function getWorkosStateCookieName(providerName) {
411
+ return `holo_workos_${providerName}_state`;
412
+ }
413
+ function createWorkosState() {
414
+ return randomBytes(32).toString("base64url");
415
+ }
416
+ function createStateCookie(providerName, state, request) {
417
+ return cookie(getWorkosStateCookieName(providerName), state, {
418
+ httpOnly: true,
419
+ maxAge: WORKOS_STATE_COOKIE_MAX_AGE_SECONDS,
420
+ sameSite: "lax",
421
+ secure: new URL(request.url).protocol === "https:"
422
+ });
423
+ }
424
+ function clearStateCookie(providerName, request) {
425
+ return cookie(getWorkosStateCookieName(providerName), "", {
426
+ expires: /* @__PURE__ */ new Date(0),
427
+ httpOnly: true,
428
+ sameSite: "lax",
429
+ secure: new URL(request.url).protocol === "https:"
430
+ });
431
+ }
432
+ function getWorkosStateFromRequest(request, providerName) {
433
+ const cookies = parseCookieHeader(request.headers.get("cookie"));
434
+ return cookies[getWorkosStateCookieName(providerName)]?.trim() || void 0;
435
+ }
436
+ function createAuthorizationUrl(config, screenHint, state) {
437
+ const { clientId, redirectUri } = requireWorkosClientConfig(config);
438
+ const url = new URL(WORKOS_AUTHORIZE_URL);
439
+ url.searchParams.set("provider", "authkit");
440
+ url.searchParams.set("response_type", "code");
441
+ url.searchParams.set("client_id", clientId);
442
+ url.searchParams.set("redirect_uri", redirectUri);
443
+ url.searchParams.set("screen_hint", screenHint);
444
+ url.searchParams.set("state", state);
445
+ return url.toString();
446
+ }
447
+ function createWorkosRedirect(request, config, screenHint) {
448
+ const state = createWorkosState();
449
+ return new Response(null, {
450
+ status: 302,
451
+ headers: {
452
+ location: createAuthorizationUrl(config, screenHint, state),
453
+ "set-cookie": createStateCookie(config.name, state, request)
454
+ }
455
+ });
456
+ }
457
+ function createLogoutUrl(sessionId, request, returnTo) {
458
+ const url = new URL(WORKOS_LOGOUT_URL);
459
+ url.searchParams.set("session_id", sessionId);
460
+ if (returnTo?.trim()) {
461
+ url.searchParams.set("return_to", createWorkosReturnUrl(returnTo, request));
462
+ }
463
+ return url.toString();
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
+ }
478
+ function createWorkosErrorResponse(error, code) {
479
+ return Response.json({
480
+ ok: false,
481
+ code,
482
+ message: getErrorMessage(error)
483
+ }, {
484
+ status: error instanceof WorkosAuthConflictError ? 409 : 422
485
+ });
486
+ }
230
487
  function resolveGuardAndProvider(provider) {
231
488
  const authBindings = authRuntimeInternals.getRuntimeBindings();
232
489
  const providerConfig = getConfiguredProviderConfig(provider);
@@ -265,6 +522,46 @@ function requireUserRecord(user, message) {
265
522
  }
266
523
  return user;
267
524
  }
525
+ function requireSerializedUser(user, message) {
526
+ if (typeof user.id === "string" || typeof user.id === "number") {
527
+ return user;
528
+ }
529
+ throw new Error(message);
530
+ }
531
+ function createWorkosSessionPayload(authenticated, session) {
532
+ const user = requireSerializedUser(
533
+ authenticated.user,
534
+ "[@holo-js/auth-workos] WorkOS-authenticated local users must expose a serializable id."
535
+ );
536
+ return Object.freeze({
537
+ guard: authenticated.guard,
538
+ provider: authenticated.authProvider,
539
+ userId: user.id,
540
+ user,
541
+ workos: Object.freeze({
542
+ provider: authenticated.provider,
543
+ sessionId: session.sessionId
544
+ })
545
+ });
546
+ }
547
+ function getWorkosLogoutSession(payload, providerName) {
548
+ if (!payload || typeof payload !== "object" || !("workos" in payload)) {
549
+ return null;
550
+ }
551
+ const workos = payload.workos;
552
+ if (!workos || typeof workos !== "object") {
553
+ return null;
554
+ }
555
+ const provider = workos.provider;
556
+ const sessionId = workos.sessionId;
557
+ if (provider !== providerName || typeof sessionId !== "string" || !sessionId.trim()) {
558
+ return null;
559
+ }
560
+ return Object.freeze({
561
+ provider,
562
+ sessionId
563
+ });
564
+ }
268
565
  function serializeLocalUser(adapter, user, providerName) {
269
566
  const id = adapter.getId(user);
270
567
  const serialized = adapter.serialize ? adapter.serialize(user) : { ...user };
@@ -277,22 +574,63 @@ function serializeLocalUser(adapter, user, providerName) {
277
574
  enumerable: false,
278
575
  configurable: true
279
576
  });
577
+ Object.defineProperty(result, "can", {
578
+ value: () => false,
579
+ enumerable: false,
580
+ configurable: true
581
+ });
280
582
  return Object.freeze(result);
281
583
  }
282
584
  function resolveDisplayName(profile) {
283
- if (profile.name?.trim()) {
585
+ if (typeof profile.name === "string" && profile.name.trim()) {
586
+ return profile.name.trim();
587
+ }
588
+ const fullName = [profile.firstName?.trim(), profile.lastName?.trim()].filter(Boolean).join(" ").trim();
589
+ return fullName || profile.email || profile.id;
590
+ }
591
+ function resolveDisplayNameForUpdate(profile) {
592
+ if (typeof profile.name === "string" && profile.name.trim()) {
284
593
  return profile.name.trim();
285
594
  }
286
595
  const fullName = [profile.firstName?.trim(), profile.lastName?.trim()].filter(Boolean).join(" ").trim();
287
596
  return fullName || void 0;
288
597
  }
289
598
  function resolveEmailForCreation(profile) {
290
- const normalized = profile.email?.trim();
599
+ const normalized = typeof profile.email === "string" ? profile.email.trim() : "";
291
600
  if (normalized) {
292
601
  return normalized;
293
602
  }
294
603
  return `${profile.id}@workos.hosted.local`;
295
604
  }
605
+ function toAdapterInput(input) {
606
+ return Object.freeze(Object.fromEntries(
607
+ Object.entries(input).filter((entry) => typeof entry[1] !== "undefined")
608
+ ));
609
+ }
610
+ function withRequiredUserFields(input, profile) {
611
+ return Object.freeze({
612
+ ...input,
613
+ email: typeof input.email === "undefined" ? resolveEmailForCreation(profile) : input.email,
614
+ name: typeof input.name === "undefined" ? resolveDisplayName(profile) : input.name
615
+ });
616
+ }
617
+ function resolveCreateUserInput(profile, mapper) {
618
+ return toAdapterInput(withRequiredUserFields({
619
+ password: null,
620
+ avatar: profile.profilePictureUrl ?? null,
621
+ email_verified_at: profile.emailVerified ? /* @__PURE__ */ new Date() : null,
622
+ ...mapper?.(profile) ?? {}
623
+ }, profile));
624
+ }
625
+ function resolveUpdateUserInput(profile, mapper) {
626
+ return toAdapterInput({
627
+ email: typeof profile.email === "string" ? profile.email.trim() || void 0 : void 0,
628
+ name: resolveDisplayNameForUpdate(profile),
629
+ avatar: profile.profilePictureUrl,
630
+ email_verified_at: profile.emailVerified ? /* @__PURE__ */ new Date() : void 0,
631
+ ...mapper?.(profile) ?? {}
632
+ });
633
+ }
296
634
  function normalizeHostedProfile(profile) {
297
635
  return Object.freeze({
298
636
  id: profile.id,
@@ -301,7 +639,7 @@ function normalizeHostedProfile(profile) {
301
639
  firstName: profile.firstName,
302
640
  lastName: profile.lastName,
303
641
  name: resolveDisplayName(profile),
304
- avatar: profile.avatar,
642
+ profilePictureUrl: profile.profilePictureUrl,
305
643
  organizationId: profile.organizationId,
306
644
  raw: profile.raw
307
645
  });
@@ -314,21 +652,20 @@ async function findUserByEmail(adapter, email) {
314
652
  return user ? requireUserRecord(user, "[@holo-js/auth-workos] Auth provider lookups must return object users.") : null;
315
653
  }
316
654
  async function updateLocalUser(adapter, user, input) {
317
- const nextInput = {
318
- name: input.name,
319
- email: input.email,
320
- avatar: typeof input.avatar === "undefined" ? void 0 : input.avatar,
321
- email_verified_at: input.emailVerified === true ? /* @__PURE__ */ new Date() : void 0
322
- };
323
655
  const current = user;
324
- const changed = typeof nextInput.name !== "undefined" && nextInput.name !== current.name || typeof nextInput.email !== "undefined" && nextInput.email !== current.email || typeof nextInput.avatar !== "undefined" && nextInput.avatar !== (current.avatar ?? void 0) || input.emailVerified === true && !current.email_verified_at;
656
+ const changed = Object.entries(input).some(([key, value]) => {
657
+ if (key === "email_verified_at") {
658
+ return value instanceof Date && !current.email_verified_at;
659
+ }
660
+ return !Object.is(value, user[key]);
661
+ });
325
662
  if (!changed) {
326
663
  return { user, changed: false };
327
664
  }
328
665
  if (adapter.update) {
329
666
  return {
330
667
  user: requireUserRecord(
331
- await adapter.update(user, nextInput),
668
+ await adapter.update(user, input),
332
669
  "[@holo-js/auth-workos] Auth provider updates must return object users."
333
670
  ),
334
671
  changed: true
@@ -338,8 +675,8 @@ async function updateLocalUser(adapter, user, input) {
338
675
  "[@holo-js/auth-workos] Auth provider adapters must implement update() to persist profile changes."
339
676
  );
340
677
  }
341
- async function ensureNoUnexpectedEmailCollision(adapter, authProvider, profile, currentUserId) {
342
- const email = profile.email?.trim();
678
+ async function ensureNoUnexpectedEmailCollision(adapter, providerName, profile, currentUserId) {
679
+ const email = typeof profile.email === "string" ? profile.email.trim() : "";
343
680
  if (!email) {
344
681
  return;
345
682
  }
@@ -353,28 +690,13 @@ async function ensureNoUnexpectedEmailCollision(adapter, authProvider, profile,
353
690
  "[@holo-js/auth-workos] Matched local users must expose a serializable id."
354
691
  ) !== currentUserId) {
355
692
  throw new WorkosAuthConflictError({
356
- provider: "workos",
693
+ provider: providerName,
357
694
  workosUserId: profile.id,
358
695
  email,
359
696
  message: `[@holo-js/auth-workos] WorkOS email "${email}" collides with a different local user.`
360
697
  });
361
698
  }
362
699
  }
363
- async function assertUserLinkAvailable(providerName, authProvider, adapter, user, workosUserId) {
364
- const existing = await getBindings().identityStore.findByUserId(
365
- providerName,
366
- authProvider,
367
- requireUserId(adapter, user, "[@holo-js/auth-workos] Linked users must expose a serializable id.")
368
- );
369
- if (existing && existing.providerUserId !== workosUserId) {
370
- throw new WorkosAuthConflictError({
371
- provider: providerName,
372
- workosUserId,
373
- email: existing.email,
374
- message: `[@holo-js/auth-workos] Local user is already linked to WorkOS identity "${existing.providerUserId}".`
375
- });
376
- }
377
- }
378
700
  function isEmailVerificationRequired() {
379
701
  return authRuntimeInternals.getRuntimeBindings().config.emailVerification.required === true;
380
702
  }
@@ -426,9 +748,16 @@ async function reuseExistingHoloSession(request, authenticated) {
426
748
  if (!payload || payload.guard !== authenticated.guard || payload.provider !== authenticated.authProvider || String(payload.userId) !== String(authenticated.user.id)) {
427
749
  return null;
428
750
  }
751
+ const source = payload;
752
+ if (source.workos && typeof source.workos === "object") {
753
+ if (typeof source.workos.provider !== "string" || source.workos.provider !== authenticated.provider) {
754
+ return null;
755
+ }
756
+ }
429
757
  bindings.context.setCachedUser(authenticated.guard, authenticated.user);
430
758
  return Object.freeze({
431
759
  guard: authenticated.guard,
760
+ provider: source.workos && typeof source.workos === "object" ? "workos" : payload.provider,
432
761
  user: authenticated.user,
433
762
  sessionId,
434
763
  cookies: Object.freeze([])
@@ -446,7 +775,8 @@ async function verifySession(token, provider) {
446
775
  config: providerConfig
447
776
  });
448
777
  }
449
- async function verifyRequest(request, provider) {
778
+ async function verifyRequest(input, provider) {
779
+ const request = normalizeWorkosRequest(input);
450
780
  const providerConfig = getConfiguredProviderConfig(provider);
451
781
  const runtime = getProviderRuntime(providerConfig.name);
452
782
  if (runtime.verifyRequest) {
@@ -462,13 +792,72 @@ async function verifyRequest(request, provider) {
462
792
  }
463
793
  return verifySession(token, providerConfig.name);
464
794
  }
465
- async function syncIdentity(session, provider) {
795
+ function getStringField(input, ...names) {
796
+ for (const name of names) {
797
+ const value = input[name];
798
+ const normalized = typeof value === "string" ? value.trim() : "";
799
+ if (normalized) {
800
+ return normalized;
801
+ }
802
+ }
803
+ return void 0;
804
+ }
805
+ function getRecordField(input, name) {
806
+ const value = input[name];
807
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
808
+ }
809
+ function getRequestIpAddress(request) {
810
+ return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
811
+ }
812
+ async function authenticateWorkosCode(request, code, config) {
813
+ const { clientId, apiKey } = requireWorkosClientConfig(config);
814
+ const body = {
815
+ grant_type: "authorization_code",
816
+ client_id: clientId,
817
+ client_secret: apiKey,
818
+ code,
819
+ ip_address: getRequestIpAddress(request),
820
+ user_agent: request.headers.get("user-agent")?.trim() || void 0
821
+ };
822
+ const response = await fetch(WORKOS_AUTHENTICATE_URL, {
823
+ method: "POST",
824
+ headers: {
825
+ accept: "application/json",
826
+ authorization: `Bearer ${apiKey}`,
827
+ "content-type": "application/json"
828
+ },
829
+ body: JSON.stringify(body)
830
+ });
831
+ const payload = await response.json();
832
+ if (!response.ok) {
833
+ const message = getStringField(payload, "message", "error_description", "error") ?? `WorkOS callback authentication failed with status ${response.status}.`;
834
+ throw new Error(message);
835
+ }
836
+ const user = getRecordField(payload, "user");
837
+ if (!user) {
838
+ throw new Error("[@holo-js/auth-workos] WorkOS callback did not return a user.");
839
+ }
840
+ const accessToken = getStringField(payload, "accessToken", "access_token");
841
+ const session = getRecordField(payload, "session");
842
+ const sessionId = getStringField(session ?? {}, "id") ?? getStringField(payload, "sessionId", "session_id") ?? (accessToken ? getJwtStringClaim(accessToken, "sid") : void 0) ?? accessToken ?? code;
843
+ return Object.freeze({
844
+ sessionId,
845
+ identity: normalizeWorkosUserProfile({
846
+ ...user,
847
+ organizationId: getStringField(payload, "organizationId", "organization_id") ?? user.organizationId ?? user.organization_id
848
+ }),
849
+ accessToken,
850
+ raw: toWorkosJsonObject(payload)
851
+ });
852
+ }
853
+ async function syncIdentity(session, provider, options = {}) {
466
854
  const providerConfig = getConfiguredProviderConfig(provider);
467
855
  const providerName = providerConfig.name;
468
856
  const profile = session.identity;
469
857
  const { guard, authProvider, adapter } = resolveGuardAndProvider(providerName);
470
858
  const verificationRequired = isEmailVerificationRequired();
471
- const verifiedEmail = profile.emailVerified === true && profile.email?.trim() ? profile.email.trim() : void 0;
859
+ const profileEmail = typeof profile.email === "string" ? profile.email.trim() : "";
860
+ const verifiedEmail = profile.emailVerified === true && profileEmail ? profileEmail : void 0;
472
861
  if (verificationRequired && !verifiedEmail) {
473
862
  throw new Error(`[@holo-js/auth-workos] WorkOS identity "${profile.id}" must provide a verified email address.`);
474
863
  }
@@ -478,25 +867,20 @@ async function syncIdentity(session, provider) {
478
867
  const existingLinkedUser = await adapter.findById(existingIdentity.userId);
479
868
  let linkedUser = existingLinkedUser ? requireUserRecord(existingLinkedUser, "[@holo-js/auth-workos] Auth provider lookups must return object users.") : null;
480
869
  if (!linkedUser) {
481
- linkedUser = verifiedEmail ? await findUserByEmail(adapter, verifiedEmail) : null;
482
- if (linkedUser) {
483
- await assertUserLinkAvailable(providerName, authProvider, adapter, linkedUser, profile.id);
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
+ });
484
878
  }
485
- if (!linkedUser) {
486
- linkedUser = requireUserRecord(await adapter.create({
487
- name: resolveDisplayName(profile),
488
- email: resolveEmailForCreation(profile),
489
- password: null,
490
- avatar: profile.avatar ?? null,
491
- email_verified_at: profile.emailVerified === true ? /* @__PURE__ */ new Date() : null
492
- }), "[@holo-js/auth-workos] Auth provider create() must return an object user.");
493
- }
494
- const relinked = await updateLocalUser(adapter, linkedUser, {
495
- name: resolveDisplayName(profile),
496
- email: profile.email?.trim(),
497
- avatar: profile.avatar,
498
- emailVerified: profile.emailVerified === true
499
- });
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
+ );
883
+ const relinked = await updateLocalUser(adapter, linkedUser, resolveUpdateUserInput(profile, options.user));
500
884
  const relinkedUser = relinked.user;
501
885
  const identity3 = createIdentityRecord({
502
886
  provider: providerName,
@@ -523,7 +907,7 @@ async function syncIdentity(session, provider) {
523
907
  }
524
908
  await ensureNoUnexpectedEmailCollision(
525
909
  adapter,
526
- authProvider,
910
+ providerName,
527
911
  profile,
528
912
  requireUserId(
529
913
  adapter,
@@ -531,12 +915,7 @@ async function syncIdentity(session, provider) {
531
915
  "[@holo-js/auth-workos] Linked local users must expose a serializable id."
532
916
  )
533
917
  );
534
- const updated = await updateLocalUser(adapter, linkedUser, {
535
- name: resolveDisplayName(profile),
536
- email: profile.email?.trim(),
537
- avatar: profile.avatar,
538
- emailVerified: profile.emailVerified === true
539
- });
918
+ const updated = await updateLocalUser(adapter, linkedUser, resolveUpdateUserInput(profile, options.user));
540
919
  const identity2 = createIdentityRecord({
541
920
  provider: providerName,
542
921
  guard,
@@ -562,42 +941,17 @@ async function syncIdentity(session, provider) {
562
941
  }
563
942
  let localUser = verifiedEmail ? await findUserByEmail(adapter, verifiedEmail) : null;
564
943
  if (localUser) {
565
- await assertUserLinkAvailable(providerName, authProvider, adapter, localUser, profile.id);
566
- const linked = await updateLocalUser(adapter, localUser, {
567
- name: resolveDisplayName(profile),
568
- email: profile.email?.trim(),
569
- avatar: profile.avatar,
570
- emailVerified: profile.emailVerified === true
571
- });
572
- const identity2 = createIdentityRecord({
573
- provider: providerName,
574
- guard,
575
- authProvider,
576
- userId: requireUserId(
577
- adapter,
578
- linked.user,
579
- "[@holo-js/auth-workos] Linked local users must expose a serializable id."
580
- ),
581
- profile
582
- });
583
- await identityStore.save(identity2);
584
- return Object.freeze({
944
+ throw new WorkosAuthConflictError({
585
945
  provider: providerName,
586
- guard,
587
- authProvider,
588
- status: "linked",
589
- user: serializeLocalUser(adapter, linked.user, authProvider),
590
- identity: identity2,
591
- 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.`
592
949
  });
593
950
  }
594
- localUser = requireUserRecord(await adapter.create({
595
- name: resolveDisplayName(profile),
596
- email: resolveEmailForCreation(profile),
597
- password: null,
598
- avatar: profile.avatar ?? null,
599
- email_verified_at: profile.emailVerified === true ? /* @__PURE__ */ new Date() : null
600
- }), "[@holo-js/auth-workos] Auth provider create() must return an object user.");
951
+ localUser = requireUserRecord(
952
+ await adapter.create(resolveCreateUserInput(profile, options.user)),
953
+ "[@holo-js/auth-workos] Auth provider create() must return an object user."
954
+ );
601
955
  const identity = createIdentityRecord({
602
956
  provider: providerName,
603
957
  guard,
@@ -620,7 +974,8 @@ async function syncIdentity(session, provider) {
620
974
  session
621
975
  });
622
976
  }
623
- async function authenticate(request, provider) {
977
+ async function authenticate(input, provider) {
978
+ const request = normalizeWorkosRequest(input);
624
979
  const session = await verifyRequest(request, provider);
625
980
  if (!session) {
626
981
  return null;
@@ -628,28 +983,175 @@ async function authenticate(request, provider) {
628
983
  const authenticated = await syncIdentity(session, provider);
629
984
  const authSession = await reuseExistingHoloSession(request, authenticated) ?? await authRuntimeInternals.establishSessionForUser(authenticated.user, {
630
985
  guard: authenticated.guard,
631
- provider: authenticated.authProvider
986
+ provider: authenticated.authProvider,
987
+ payload: createWorkosSessionPayload(authenticated, session)
632
988
  });
633
989
  return Object.freeze({
634
990
  ...authenticated,
635
991
  authSession
636
992
  });
637
993
  }
994
+ function getErrorMessage(error) {
995
+ return error instanceof Error ? error.message : "WorkOS authentication failed.";
996
+ }
997
+ function createHostedAuthSuccess(data) {
998
+ return Object.freeze({
999
+ data,
1000
+ error: null
1001
+ });
1002
+ }
1003
+ function createHostedAuthFailure(code, message, status) {
1004
+ return Object.freeze({
1005
+ data: null,
1006
+ error: Object.freeze({
1007
+ code,
1008
+ message,
1009
+ status,
1010
+ fields: Object.freeze({
1011
+ _root: Object.freeze([message])
1012
+ })
1013
+ })
1014
+ });
1015
+ }
1016
+ async function loginWithWorkos(input, options = {}) {
1017
+ try {
1018
+ const request = normalizeWorkosRequest(input);
1019
+ const providerConfig = getConfiguredProviderConfig(options.provider);
1020
+ return createWorkosRedirect(request, providerConfig, "sign-in");
1021
+ } catch (error) {
1022
+ return createWorkosErrorResponse(error, "workos_login_failed");
1023
+ }
1024
+ }
1025
+ async function registerWithWorkos(input, options = {}) {
1026
+ try {
1027
+ const request = normalizeWorkosRequest(input);
1028
+ const providerConfig = getConfiguredProviderConfig(options.provider);
1029
+ return createWorkosRedirect(request, providerConfig, "sign-up");
1030
+ } catch (error) {
1031
+ return createWorkosErrorResponse(error, "workos_register_failed");
1032
+ }
1033
+ }
1034
+ async function readCurrentWorkosLogoutSession(guard, providerName) {
1035
+ const bindings = authRuntimeInternals.getRuntimeBindings();
1036
+ const sessionId = bindings.context.getSessionId(guard);
1037
+ if (!sessionId) {
1038
+ return null;
1039
+ }
1040
+ const record = await bindings.session.read(sessionId);
1041
+ return getWorkosLogoutSession(authRuntimeInternals.readSessionPayload(record, guard), providerName);
1042
+ }
1043
+ async function logoutWithWorkos(input, options = {}) {
1044
+ const request = normalizeWorkosRequest(input);
1045
+ try {
1046
+ const providerConfig = getConfiguredProviderConfig(options.provider);
1047
+ const providerName = providerConfig.name;
1048
+ const { guard } = resolveGuardAndProvider(providerName);
1049
+ const requestSessionId = getHoloSessionIdFromRequest(request);
1050
+ if (requestSessionId) {
1051
+ authRuntimeInternals.getRuntimeBindings().context.setSessionId(guard, requestSessionId);
1052
+ }
1053
+ const workosSession = await readCurrentWorkosLogoutSession(guard, providerName);
1054
+ if (!workosSession) {
1055
+ return createHostedAuthFailure(
1056
+ "workos_session_missing",
1057
+ "The current Holo session was not created by WorkOS.",
1058
+ 422
1059
+ );
1060
+ }
1061
+ const local = await getAuthRuntime().guard(guard).logout();
1062
+ return createHostedAuthSuccess(Object.freeze({
1063
+ url: createLogoutUrl(workosSession.sessionId, request, options.returnTo),
1064
+ local
1065
+ }));
1066
+ } catch {
1067
+ return createHostedAuthFailure("workos_logout_failed", "Unable to complete WorkOS logout.", 500);
1068
+ }
1069
+ }
1070
+ async function completeWorkosAuth(input, options = {}) {
1071
+ try {
1072
+ const request = normalizeWorkosRequest(input);
1073
+ const url = new URL(request.url);
1074
+ const callbackError = url.searchParams.get("error")?.trim();
1075
+ if (callbackError) {
1076
+ return createHostedAuthFailure(
1077
+ callbackError,
1078
+ url.searchParams.get("error_description")?.trim() || "WorkOS authentication failed.",
1079
+ 422
1080
+ );
1081
+ }
1082
+ const code = url.searchParams.get("code")?.trim();
1083
+ if (!code) {
1084
+ return createHostedAuthFailure(
1085
+ "workos_code_required",
1086
+ "WorkOS callback did not include an authorization code.",
1087
+ 422
1088
+ );
1089
+ }
1090
+ const providerConfig = getConfiguredProviderConfig(options.provider);
1091
+ const callbackState = url.searchParams.get("state")?.trim();
1092
+ const expectedState = getWorkosStateFromRequest(request, providerConfig.name);
1093
+ if (!callbackState || !expectedState || !Object.is(callbackState, expectedState)) {
1094
+ return createHostedAuthFailure(
1095
+ "workos_state_mismatch",
1096
+ "WorkOS callback state did not match the login request.",
1097
+ 422
1098
+ );
1099
+ }
1100
+ const session = await authenticateWorkosCode(request, code, providerConfig);
1101
+ const authenticated = await syncIdentity(session, providerConfig.name, {
1102
+ user: options.user
1103
+ });
1104
+ const authSession = await authRuntimeInternals.establishSessionForUser(authenticated.user, {
1105
+ guard: authenticated.guard,
1106
+ provider: authenticated.authProvider,
1107
+ payload: createWorkosSessionPayload(authenticated, session)
1108
+ });
1109
+ return createHostedAuthSuccess(Object.freeze({
1110
+ provider: authenticated.provider,
1111
+ guard: authenticated.guard,
1112
+ authProvider: authenticated.authProvider,
1113
+ status: authenticated.status,
1114
+ user: authenticated.user,
1115
+ identity: authenticated.identity,
1116
+ session,
1117
+ authSession: Object.freeze({
1118
+ ...authSession,
1119
+ cookies: Object.freeze([
1120
+ ...authSession.cookies,
1121
+ clearStateCookie(providerConfig.name, request)
1122
+ ])
1123
+ })
1124
+ }));
1125
+ } catch (error) {
1126
+ return createHostedAuthFailure(
1127
+ error instanceof WorkosAuthConflictError ? error.code : "workos_auth_failed",
1128
+ getErrorMessage(error),
1129
+ error instanceof WorkosAuthConflictError ? 409 : 422
1130
+ );
1131
+ }
1132
+ }
638
1133
  function configureWorkosAuthRuntime(bindings) {
1134
+ const runtimeState = getRuntimeState();
639
1135
  if (!bindings) {
640
- workosBindings = void 0;
1136
+ runtimeState.bindings = void 0;
641
1137
  return;
642
1138
  }
643
- workosBindings = Object.freeze({
644
- providers: bindings.providers ?? workosBindings?.providers,
645
- identityStore: bindings.identityStore ?? workosBindings?.identityStore
1139
+ runtimeState.bindings = Object.freeze({
1140
+ providers: bindings.providers ?? runtimeState.bindings?.providers,
1141
+ identityStore: bindings.identityStore ?? runtimeState.bindings?.identityStore
646
1142
  });
647
1143
  }
648
1144
  function resetWorkosAuthRuntime() {
649
- workosBindings = void 0;
1145
+ getRuntimeState().bindings = void 0;
1146
+ workosDefaultProviderRuntimeCache.clear();
1147
+ workosJwksCache.clear();
650
1148
  }
651
1149
  var workosAuth = Object.freeze({
652
1150
  authenticate,
1151
+ completeWorkosAuth,
1152
+ loginWithWorkos,
1153
+ logoutWithWorkos,
1154
+ registerWithWorkos,
653
1155
  syncIdentity,
654
1156
  verifyRequest,
655
1157
  verifySession
@@ -667,7 +1169,11 @@ var workosAuthInternals = {
667
1169
  export {
668
1170
  WorkosAuthConflictError,
669
1171
  authenticate,
1172
+ completeWorkosAuth,
670
1173
  configureWorkosAuthRuntime,
1174
+ loginWithWorkos,
1175
+ logoutWithWorkos,
1176
+ registerWithWorkos,
671
1177
  resetWorkosAuthRuntime,
672
1178
  syncIdentity,
673
1179
  verifyRequest,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/auth-workos",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Holo-JS Framework - WorkOS auth integration contracts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,14 +23,14 @@
23
23
  "test": "vitest --run"
24
24
  },
25
25
  "dependencies": {
26
- "@holo-js/auth": "workspace:*",
27
- "@holo-js/config": "workspace:*",
28
- "@holo-js/session": "workspace:*"
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",
32
32
  "tsup": "^8.3.5",
33
33
  "typescript": "^5.7.2",
34
- "vitest": "^2.1.8"
34
+ "vitest": "^4.1.5"
35
35
  }
36
36
  }