@holo-js/auth-workos 0.1.3 → 0.1.5
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/dist/index.d.ts +122 -26
- package/dist/index.mjs +577 -69
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
|
7
|
-
readonly
|
|
9
|
+
readonly email: string;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly emailVerified: boolean;
|
|
8
12
|
readonly firstName?: string;
|
|
9
13
|
readonly lastName?: string;
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
14
|
+
readonly profilePictureUrl?: string;
|
|
15
|
+
readonly externalId?: string;
|
|
12
16
|
readonly organizationId?: string;
|
|
13
|
-
readonly
|
|
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:
|
|
73
|
+
readonly config: NormalizedAuthWorkosProviderConfig;
|
|
26
74
|
}
|
|
27
75
|
interface WorkosVerifySessionContext {
|
|
28
76
|
readonly provider: string;
|
|
29
77
|
readonly token: string;
|
|
30
|
-
readonly config:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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(
|
|
114
|
-
declare function syncIdentity(session: WorkosVerifiedSession, provider?: string): Promise<WorkosAuthenticationResult
|
|
115
|
-
declare function authenticate(
|
|
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
|
-
|
|
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" });
|
|
@@ -122,20 +244,43 @@ async function verifyWorkosSessionToken(token, config) {
|
|
|
122
244
|
}
|
|
123
245
|
return parsed.payload;
|
|
124
246
|
}
|
|
247
|
+
function toWorkosJsonValue(value) {
|
|
248
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
if (Array.isArray(value)) {
|
|
252
|
+
return value.map((item) => toWorkosJsonValue(item)).filter((item) => typeof item !== "undefined");
|
|
253
|
+
}
|
|
254
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
255
|
+
return Object.freeze(Object.fromEntries(
|
|
256
|
+
Object.entries(value).map(([key, item]) => [key, toWorkosJsonValue(item)]).filter((entry) => typeof entry[1] !== "undefined")
|
|
257
|
+
));
|
|
258
|
+
}
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
function toWorkosJsonObject(value) {
|
|
262
|
+
const normalized = toWorkosJsonValue(value);
|
|
263
|
+
return normalized && typeof normalized === "object" && !Array.isArray(normalized) ? normalized : Object.freeze({});
|
|
264
|
+
}
|
|
125
265
|
function normalizeWorkosUserProfile(user) {
|
|
126
266
|
const firstName = typeof user.firstName === "string" ? user.firstName : typeof user.first_name === "string" ? user.first_name : void 0;
|
|
127
267
|
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()
|
|
268
|
+
const name = typeof user.name === "string" ? user.name : [firstName, lastName].filter(Boolean).join(" ").trim();
|
|
269
|
+
const email = typeof user.email === "string" ? user.email.trim() : "";
|
|
129
270
|
return Object.freeze({
|
|
130
271
|
id: String(user.id ?? ""),
|
|
131
|
-
email
|
|
272
|
+
email,
|
|
132
273
|
emailVerified: user.emailVerified === true || user.email_verified === true,
|
|
133
274
|
firstName,
|
|
134
275
|
lastName,
|
|
135
|
-
name,
|
|
136
|
-
|
|
276
|
+
name: name || email || String(user.id ?? ""),
|
|
277
|
+
profilePictureUrl: typeof user.profilePictureUrl === "string" ? user.profilePictureUrl : typeof user.profile_picture_url === "string" ? user.profile_picture_url : void 0,
|
|
278
|
+
externalId: typeof user.externalId === "string" ? user.externalId : typeof user.external_id === "string" ? user.external_id : void 0,
|
|
137
279
|
organizationId: typeof user.organizationId === "string" ? user.organizationId : typeof user.organization_id === "string" ? user.organization_id : void 0,
|
|
138
|
-
|
|
280
|
+
metadata: toWorkosJsonObject(user.metadata),
|
|
281
|
+
createdAt: typeof user.createdAt === "string" ? user.createdAt : typeof user.created_at === "string" ? user.created_at : void 0,
|
|
282
|
+
updatedAt: typeof user.updatedAt === "string" ? user.updatedAt : typeof user.updated_at === "string" ? user.updated_at : void 0,
|
|
283
|
+
raw: toWorkosJsonObject(user)
|
|
139
284
|
});
|
|
140
285
|
}
|
|
141
286
|
async function fetchWorkosUserProfile(userId, config) {
|
|
@@ -193,7 +338,11 @@ function resolveConfiguredProviderName(provider) {
|
|
|
193
338
|
if (provider?.trim()) {
|
|
194
339
|
return provider.trim();
|
|
195
340
|
}
|
|
196
|
-
const
|
|
341
|
+
const configuredDefaultProvider = typeof bindings.config.workos.provider === "string" ? bindings.config.workos.provider.trim() : "";
|
|
342
|
+
if (configuredDefaultProvider) {
|
|
343
|
+
return configuredDefaultProvider;
|
|
344
|
+
}
|
|
345
|
+
const configuredProviders = Object.entries(bindings.config.workos).flatMap(([name, value]) => name !== "provider" && name !== "identityStore" && isNormalizedWorkosProviderConfig(value) ? [name] : []);
|
|
197
346
|
if (configuredProviders.length === 0) {
|
|
198
347
|
return "default";
|
|
199
348
|
}
|
|
@@ -205,18 +354,20 @@ function resolveConfiguredProviderName(provider) {
|
|
|
205
354
|
}
|
|
206
355
|
throw new Error("[@holo-js/auth-workos] WorkOS provider name is required when multiple auth.workos entries exist.");
|
|
207
356
|
}
|
|
357
|
+
function isNormalizedWorkosProviderConfig(value) {
|
|
358
|
+
return value !== null && typeof value === "object" && "sessionCookie" in value && typeof value.sessionCookie === "string";
|
|
359
|
+
}
|
|
208
360
|
function getConfiguredProviderConfig(provider) {
|
|
209
361
|
const providerName = resolveConfiguredProviderName(provider);
|
|
210
362
|
const authBindings = authRuntimeInternals.getRuntimeBindings();
|
|
211
363
|
const configured = authBindings.config.workos[providerName];
|
|
212
|
-
if (!configured) {
|
|
364
|
+
if (!isNormalizedWorkosProviderConfig(configured)) {
|
|
213
365
|
throw new Error(`[@holo-js/auth-workos] WorkOS provider "${providerName}" is not configured in auth.workos.`);
|
|
214
366
|
}
|
|
215
367
|
return {
|
|
216
368
|
name: providerName,
|
|
217
369
|
clientId: configured.clientId,
|
|
218
370
|
apiKey: configured.apiKey,
|
|
219
|
-
cookiePassword: configured.cookiePassword,
|
|
220
371
|
redirectUri: configured.redirectUri,
|
|
221
372
|
sessionCookie: configured.sessionCookie,
|
|
222
373
|
guard: configured.guard,
|
|
@@ -227,6 +378,85 @@ function getProviderRuntime(provider) {
|
|
|
227
378
|
const providerName = resolveConfiguredProviderName(provider);
|
|
228
379
|
return getBindings().providers[providerName] ?? createDefaultProviderRuntime(providerName, getConfiguredProviderConfig(providerName));
|
|
229
380
|
}
|
|
381
|
+
function requireWorkosClientConfig(config) {
|
|
382
|
+
const clientId = config.clientId?.trim();
|
|
383
|
+
const apiKey = config.apiKey?.trim();
|
|
384
|
+
const redirectUri = config.redirectUri?.trim();
|
|
385
|
+
if (!clientId) {
|
|
386
|
+
throw new Error("[@holo-js/auth-workos] WorkOS AuthKit requires clientId to be configured.");
|
|
387
|
+
}
|
|
388
|
+
if (!apiKey) {
|
|
389
|
+
throw new Error("[@holo-js/auth-workos] WorkOS AuthKit requires apiKey to be configured.");
|
|
390
|
+
}
|
|
391
|
+
if (!redirectUri) {
|
|
392
|
+
throw new Error("[@holo-js/auth-workos] WorkOS AuthKit requires redirectUri to be configured.");
|
|
393
|
+
}
|
|
394
|
+
return { clientId, apiKey, redirectUri };
|
|
395
|
+
}
|
|
396
|
+
function getWorkosStateCookieName(providerName) {
|
|
397
|
+
return `holo_workos_${providerName}_state`;
|
|
398
|
+
}
|
|
399
|
+
function createWorkosState() {
|
|
400
|
+
return randomBytes(32).toString("base64url");
|
|
401
|
+
}
|
|
402
|
+
function createStateCookie(providerName, state, request) {
|
|
403
|
+
return cookie(getWorkosStateCookieName(providerName), state, {
|
|
404
|
+
httpOnly: true,
|
|
405
|
+
maxAge: WORKOS_STATE_COOKIE_MAX_AGE_SECONDS,
|
|
406
|
+
sameSite: "lax",
|
|
407
|
+
secure: new URL(request.url).protocol === "https:"
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
function clearStateCookie(providerName, request) {
|
|
411
|
+
return cookie(getWorkosStateCookieName(providerName), "", {
|
|
412
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
413
|
+
httpOnly: true,
|
|
414
|
+
sameSite: "lax",
|
|
415
|
+
secure: new URL(request.url).protocol === "https:"
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
function getWorkosStateFromRequest(request, providerName) {
|
|
419
|
+
const cookies = parseCookieHeader(request.headers.get("cookie"));
|
|
420
|
+
return cookies[getWorkosStateCookieName(providerName)]?.trim() || void 0;
|
|
421
|
+
}
|
|
422
|
+
function createAuthorizationUrl(config, screenHint, state) {
|
|
423
|
+
const { clientId, redirectUri } = requireWorkosClientConfig(config);
|
|
424
|
+
const url = new URL(WORKOS_AUTHORIZE_URL);
|
|
425
|
+
url.searchParams.set("provider", "authkit");
|
|
426
|
+
url.searchParams.set("response_type", "code");
|
|
427
|
+
url.searchParams.set("client_id", clientId);
|
|
428
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
429
|
+
url.searchParams.set("screen_hint", screenHint);
|
|
430
|
+
url.searchParams.set("state", state);
|
|
431
|
+
return url.toString();
|
|
432
|
+
}
|
|
433
|
+
function createWorkosRedirect(request, config, screenHint) {
|
|
434
|
+
const state = createWorkosState();
|
|
435
|
+
return new Response(null, {
|
|
436
|
+
status: 302,
|
|
437
|
+
headers: {
|
|
438
|
+
location: createAuthorizationUrl(config, screenHint, state),
|
|
439
|
+
"set-cookie": createStateCookie(config.name, state, request)
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function createLogoutUrl(sessionId, request, returnTo) {
|
|
444
|
+
const url = new URL(WORKOS_LOGOUT_URL);
|
|
445
|
+
url.searchParams.set("session_id", sessionId);
|
|
446
|
+
if (returnTo?.trim()) {
|
|
447
|
+
url.searchParams.set("return_to", new URL(returnTo, request.url).toString());
|
|
448
|
+
}
|
|
449
|
+
return url.toString();
|
|
450
|
+
}
|
|
451
|
+
function createWorkosErrorResponse(error, code) {
|
|
452
|
+
return Response.json({
|
|
453
|
+
ok: false,
|
|
454
|
+
code,
|
|
455
|
+
message: getErrorMessage(error)
|
|
456
|
+
}, {
|
|
457
|
+
status: error instanceof WorkosAuthConflictError ? 409 : 422
|
|
458
|
+
});
|
|
459
|
+
}
|
|
230
460
|
function resolveGuardAndProvider(provider) {
|
|
231
461
|
const authBindings = authRuntimeInternals.getRuntimeBindings();
|
|
232
462
|
const providerConfig = getConfiguredProviderConfig(provider);
|
|
@@ -265,6 +495,46 @@ function requireUserRecord(user, message) {
|
|
|
265
495
|
}
|
|
266
496
|
return user;
|
|
267
497
|
}
|
|
498
|
+
function requireSerializedUser(user, message) {
|
|
499
|
+
if (typeof user.id === "string" || typeof user.id === "number") {
|
|
500
|
+
return user;
|
|
501
|
+
}
|
|
502
|
+
throw new Error(message);
|
|
503
|
+
}
|
|
504
|
+
function createWorkosSessionPayload(authenticated, session) {
|
|
505
|
+
const user = requireSerializedUser(
|
|
506
|
+
authenticated.user,
|
|
507
|
+
"[@holo-js/auth-workos] WorkOS-authenticated local users must expose a serializable id."
|
|
508
|
+
);
|
|
509
|
+
return Object.freeze({
|
|
510
|
+
guard: authenticated.guard,
|
|
511
|
+
provider: authenticated.authProvider,
|
|
512
|
+
userId: user.id,
|
|
513
|
+
user,
|
|
514
|
+
workos: Object.freeze({
|
|
515
|
+
provider: authenticated.provider,
|
|
516
|
+
sessionId: session.sessionId
|
|
517
|
+
})
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
function getWorkosLogoutSession(payload, providerName) {
|
|
521
|
+
if (!payload || typeof payload !== "object" || !("workos" in payload)) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
const workos = payload.workos;
|
|
525
|
+
if (!workos || typeof workos !== "object") {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
const provider = workos.provider;
|
|
529
|
+
const sessionId = workos.sessionId;
|
|
530
|
+
if (provider !== providerName || typeof sessionId !== "string" || !sessionId.trim()) {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
return Object.freeze({
|
|
534
|
+
provider,
|
|
535
|
+
sessionId
|
|
536
|
+
});
|
|
537
|
+
}
|
|
268
538
|
function serializeLocalUser(adapter, user, providerName) {
|
|
269
539
|
const id = adapter.getId(user);
|
|
270
540
|
const serialized = adapter.serialize ? adapter.serialize(user) : { ...user };
|
|
@@ -277,22 +547,63 @@ function serializeLocalUser(adapter, user, providerName) {
|
|
|
277
547
|
enumerable: false,
|
|
278
548
|
configurable: true
|
|
279
549
|
});
|
|
550
|
+
Object.defineProperty(result, "can", {
|
|
551
|
+
value: () => false,
|
|
552
|
+
enumerable: false,
|
|
553
|
+
configurable: true
|
|
554
|
+
});
|
|
280
555
|
return Object.freeze(result);
|
|
281
556
|
}
|
|
282
557
|
function resolveDisplayName(profile) {
|
|
283
|
-
if (profile.name
|
|
558
|
+
if (typeof profile.name === "string" && profile.name.trim()) {
|
|
559
|
+
return profile.name.trim();
|
|
560
|
+
}
|
|
561
|
+
const fullName = [profile.firstName?.trim(), profile.lastName?.trim()].filter(Boolean).join(" ").trim();
|
|
562
|
+
return fullName || profile.email || profile.id;
|
|
563
|
+
}
|
|
564
|
+
function resolveDisplayNameForUpdate(profile) {
|
|
565
|
+
if (typeof profile.name === "string" && profile.name.trim()) {
|
|
284
566
|
return profile.name.trim();
|
|
285
567
|
}
|
|
286
568
|
const fullName = [profile.firstName?.trim(), profile.lastName?.trim()].filter(Boolean).join(" ").trim();
|
|
287
569
|
return fullName || void 0;
|
|
288
570
|
}
|
|
289
571
|
function resolveEmailForCreation(profile) {
|
|
290
|
-
const normalized = profile.email
|
|
572
|
+
const normalized = typeof profile.email === "string" ? profile.email.trim() : "";
|
|
291
573
|
if (normalized) {
|
|
292
574
|
return normalized;
|
|
293
575
|
}
|
|
294
576
|
return `${profile.id}@workos.hosted.local`;
|
|
295
577
|
}
|
|
578
|
+
function toAdapterInput(input) {
|
|
579
|
+
return Object.freeze(Object.fromEntries(
|
|
580
|
+
Object.entries(input).filter((entry) => typeof entry[1] !== "undefined")
|
|
581
|
+
));
|
|
582
|
+
}
|
|
583
|
+
function withRequiredUserFields(input, profile) {
|
|
584
|
+
return Object.freeze({
|
|
585
|
+
...input,
|
|
586
|
+
email: typeof input.email === "undefined" ? resolveEmailForCreation(profile) : input.email,
|
|
587
|
+
name: typeof input.name === "undefined" ? resolveDisplayName(profile) : input.name
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
function resolveCreateUserInput(profile, mapper) {
|
|
591
|
+
return toAdapterInput(withRequiredUserFields({
|
|
592
|
+
password: null,
|
|
593
|
+
avatar: profile.profilePictureUrl ?? null,
|
|
594
|
+
email_verified_at: profile.emailVerified ? /* @__PURE__ */ new Date() : null,
|
|
595
|
+
...mapper?.(profile) ?? {}
|
|
596
|
+
}, profile));
|
|
597
|
+
}
|
|
598
|
+
function resolveUpdateUserInput(profile, mapper) {
|
|
599
|
+
return toAdapterInput({
|
|
600
|
+
email: typeof profile.email === "string" ? profile.email.trim() || void 0 : void 0,
|
|
601
|
+
name: resolveDisplayNameForUpdate(profile),
|
|
602
|
+
avatar: profile.profilePictureUrl,
|
|
603
|
+
email_verified_at: profile.emailVerified ? /* @__PURE__ */ new Date() : void 0,
|
|
604
|
+
...mapper?.(profile) ?? {}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
296
607
|
function normalizeHostedProfile(profile) {
|
|
297
608
|
return Object.freeze({
|
|
298
609
|
id: profile.id,
|
|
@@ -301,7 +612,7 @@ function normalizeHostedProfile(profile) {
|
|
|
301
612
|
firstName: profile.firstName,
|
|
302
613
|
lastName: profile.lastName,
|
|
303
614
|
name: resolveDisplayName(profile),
|
|
304
|
-
|
|
615
|
+
profilePictureUrl: profile.profilePictureUrl,
|
|
305
616
|
organizationId: profile.organizationId,
|
|
306
617
|
raw: profile.raw
|
|
307
618
|
});
|
|
@@ -314,21 +625,20 @@ async function findUserByEmail(adapter, email) {
|
|
|
314
625
|
return user ? requireUserRecord(user, "[@holo-js/auth-workos] Auth provider lookups must return object users.") : null;
|
|
315
626
|
}
|
|
316
627
|
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
628
|
const current = user;
|
|
324
|
-
const changed =
|
|
629
|
+
const changed = Object.entries(input).some(([key, value]) => {
|
|
630
|
+
if (key === "email_verified_at") {
|
|
631
|
+
return value instanceof Date && !current.email_verified_at;
|
|
632
|
+
}
|
|
633
|
+
return !Object.is(value, user[key]);
|
|
634
|
+
});
|
|
325
635
|
if (!changed) {
|
|
326
636
|
return { user, changed: false };
|
|
327
637
|
}
|
|
328
638
|
if (adapter.update) {
|
|
329
639
|
return {
|
|
330
640
|
user: requireUserRecord(
|
|
331
|
-
await adapter.update(user,
|
|
641
|
+
await adapter.update(user, input),
|
|
332
642
|
"[@holo-js/auth-workos] Auth provider updates must return object users."
|
|
333
643
|
),
|
|
334
644
|
changed: true
|
|
@@ -338,8 +648,8 @@ async function updateLocalUser(adapter, user, input) {
|
|
|
338
648
|
"[@holo-js/auth-workos] Auth provider adapters must implement update() to persist profile changes."
|
|
339
649
|
);
|
|
340
650
|
}
|
|
341
|
-
async function ensureNoUnexpectedEmailCollision(adapter,
|
|
342
|
-
const email = profile.email
|
|
651
|
+
async function ensureNoUnexpectedEmailCollision(adapter, providerName, profile, currentUserId) {
|
|
652
|
+
const email = typeof profile.email === "string" ? profile.email.trim() : "";
|
|
343
653
|
if (!email) {
|
|
344
654
|
return;
|
|
345
655
|
}
|
|
@@ -353,7 +663,7 @@ async function ensureNoUnexpectedEmailCollision(adapter, authProvider, profile,
|
|
|
353
663
|
"[@holo-js/auth-workos] Matched local users must expose a serializable id."
|
|
354
664
|
) !== currentUserId) {
|
|
355
665
|
throw new WorkosAuthConflictError({
|
|
356
|
-
provider:
|
|
666
|
+
provider: providerName,
|
|
357
667
|
workosUserId: profile.id,
|
|
358
668
|
email,
|
|
359
669
|
message: `[@holo-js/auth-workos] WorkOS email "${email}" collides with a different local user.`
|
|
@@ -426,9 +736,16 @@ async function reuseExistingHoloSession(request, authenticated) {
|
|
|
426
736
|
if (!payload || payload.guard !== authenticated.guard || payload.provider !== authenticated.authProvider || String(payload.userId) !== String(authenticated.user.id)) {
|
|
427
737
|
return null;
|
|
428
738
|
}
|
|
739
|
+
const source = payload;
|
|
740
|
+
if (source.workos && typeof source.workos === "object") {
|
|
741
|
+
if (typeof source.workos.provider !== "string" || source.workos.provider !== authenticated.provider) {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
429
745
|
bindings.context.setCachedUser(authenticated.guard, authenticated.user);
|
|
430
746
|
return Object.freeze({
|
|
431
747
|
guard: authenticated.guard,
|
|
748
|
+
provider: source.workos && typeof source.workos === "object" ? "workos" : payload.provider,
|
|
432
749
|
user: authenticated.user,
|
|
433
750
|
sessionId,
|
|
434
751
|
cookies: Object.freeze([])
|
|
@@ -446,7 +763,8 @@ async function verifySession(token, provider) {
|
|
|
446
763
|
config: providerConfig
|
|
447
764
|
});
|
|
448
765
|
}
|
|
449
|
-
async function verifyRequest(
|
|
766
|
+
async function verifyRequest(input, provider) {
|
|
767
|
+
const request = normalizeWorkosRequest(input);
|
|
450
768
|
const providerConfig = getConfiguredProviderConfig(provider);
|
|
451
769
|
const runtime = getProviderRuntime(providerConfig.name);
|
|
452
770
|
if (runtime.verifyRequest) {
|
|
@@ -462,13 +780,72 @@ async function verifyRequest(request, provider) {
|
|
|
462
780
|
}
|
|
463
781
|
return verifySession(token, providerConfig.name);
|
|
464
782
|
}
|
|
465
|
-
|
|
783
|
+
function getStringField(input, ...names) {
|
|
784
|
+
for (const name of names) {
|
|
785
|
+
const value = input[name];
|
|
786
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
787
|
+
if (normalized) {
|
|
788
|
+
return normalized;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return void 0;
|
|
792
|
+
}
|
|
793
|
+
function getRecordField(input, name) {
|
|
794
|
+
const value = input[name];
|
|
795
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
796
|
+
}
|
|
797
|
+
function getRequestIpAddress(request) {
|
|
798
|
+
return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip")?.trim() || void 0;
|
|
799
|
+
}
|
|
800
|
+
async function authenticateWorkosCode(request, code, config) {
|
|
801
|
+
const { clientId, apiKey } = requireWorkosClientConfig(config);
|
|
802
|
+
const body = {
|
|
803
|
+
grant_type: "authorization_code",
|
|
804
|
+
client_id: clientId,
|
|
805
|
+
client_secret: apiKey,
|
|
806
|
+
code,
|
|
807
|
+
ip_address: getRequestIpAddress(request),
|
|
808
|
+
user_agent: request.headers.get("user-agent")?.trim() || void 0
|
|
809
|
+
};
|
|
810
|
+
const response = await fetch(WORKOS_AUTHENTICATE_URL, {
|
|
811
|
+
method: "POST",
|
|
812
|
+
headers: {
|
|
813
|
+
accept: "application/json",
|
|
814
|
+
authorization: `Bearer ${apiKey}`,
|
|
815
|
+
"content-type": "application/json"
|
|
816
|
+
},
|
|
817
|
+
body: JSON.stringify(body)
|
|
818
|
+
});
|
|
819
|
+
const payload = await response.json();
|
|
820
|
+
if (!response.ok) {
|
|
821
|
+
const message = getStringField(payload, "message", "error_description", "error") ?? `WorkOS callback authentication failed with status ${response.status}.`;
|
|
822
|
+
throw new Error(message);
|
|
823
|
+
}
|
|
824
|
+
const user = getRecordField(payload, "user");
|
|
825
|
+
if (!user) {
|
|
826
|
+
throw new Error("[@holo-js/auth-workos] WorkOS callback did not return a user.");
|
|
827
|
+
}
|
|
828
|
+
const accessToken = getStringField(payload, "accessToken", "access_token");
|
|
829
|
+
const session = getRecordField(payload, "session");
|
|
830
|
+
const sessionId = getStringField(session ?? {}, "id") ?? getStringField(payload, "sessionId", "session_id") ?? (accessToken ? getJwtStringClaim(accessToken, "sid") : void 0) ?? accessToken ?? code;
|
|
831
|
+
return Object.freeze({
|
|
832
|
+
sessionId,
|
|
833
|
+
identity: normalizeWorkosUserProfile({
|
|
834
|
+
...user,
|
|
835
|
+
organizationId: getStringField(payload, "organizationId", "organization_id") ?? user.organizationId ?? user.organization_id
|
|
836
|
+
}),
|
|
837
|
+
accessToken,
|
|
838
|
+
raw: toWorkosJsonObject(payload)
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
async function syncIdentity(session, provider, options = {}) {
|
|
466
842
|
const providerConfig = getConfiguredProviderConfig(provider);
|
|
467
843
|
const providerName = providerConfig.name;
|
|
468
844
|
const profile = session.identity;
|
|
469
845
|
const { guard, authProvider, adapter } = resolveGuardAndProvider(providerName);
|
|
470
846
|
const verificationRequired = isEmailVerificationRequired();
|
|
471
|
-
const
|
|
847
|
+
const profileEmail = typeof profile.email === "string" ? profile.email.trim() : "";
|
|
848
|
+
const verifiedEmail = profile.emailVerified === true && profileEmail ? profileEmail : void 0;
|
|
472
849
|
if (verificationRequired && !verifiedEmail) {
|
|
473
850
|
throw new Error(`[@holo-js/auth-workos] WorkOS identity "${profile.id}" must provide a verified email address.`);
|
|
474
851
|
}
|
|
@@ -483,20 +860,12 @@ async function syncIdentity(session, provider) {
|
|
|
483
860
|
await assertUserLinkAvailable(providerName, authProvider, adapter, linkedUser, profile.id);
|
|
484
861
|
}
|
|
485
862
|
if (!linkedUser) {
|
|
486
|
-
linkedUser = requireUserRecord(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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.");
|
|
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
|
+
);
|
|
493
867
|
}
|
|
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
|
-
});
|
|
868
|
+
const relinked = await updateLocalUser(adapter, linkedUser, resolveUpdateUserInput(profile, options.user));
|
|
500
869
|
const relinkedUser = relinked.user;
|
|
501
870
|
const identity3 = createIdentityRecord({
|
|
502
871
|
provider: providerName,
|
|
@@ -523,7 +892,7 @@ async function syncIdentity(session, provider) {
|
|
|
523
892
|
}
|
|
524
893
|
await ensureNoUnexpectedEmailCollision(
|
|
525
894
|
adapter,
|
|
526
|
-
|
|
895
|
+
providerName,
|
|
527
896
|
profile,
|
|
528
897
|
requireUserId(
|
|
529
898
|
adapter,
|
|
@@ -531,12 +900,7 @@ async function syncIdentity(session, provider) {
|
|
|
531
900
|
"[@holo-js/auth-workos] Linked local users must expose a serializable id."
|
|
532
901
|
)
|
|
533
902
|
);
|
|
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
|
-
});
|
|
903
|
+
const updated = await updateLocalUser(adapter, linkedUser, resolveUpdateUserInput(profile, options.user));
|
|
540
904
|
const identity2 = createIdentityRecord({
|
|
541
905
|
provider: providerName,
|
|
542
906
|
guard,
|
|
@@ -563,12 +927,7 @@ async function syncIdentity(session, provider) {
|
|
|
563
927
|
let localUser = verifiedEmail ? await findUserByEmail(adapter, verifiedEmail) : null;
|
|
564
928
|
if (localUser) {
|
|
565
929
|
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
|
-
});
|
|
930
|
+
const linked = await updateLocalUser(adapter, localUser, resolveUpdateUserInput(profile, options.user));
|
|
572
931
|
const identity2 = createIdentityRecord({
|
|
573
932
|
provider: providerName,
|
|
574
933
|
guard,
|
|
@@ -591,13 +950,10 @@ async function syncIdentity(session, provider) {
|
|
|
591
950
|
session
|
|
592
951
|
});
|
|
593
952
|
}
|
|
594
|
-
localUser = requireUserRecord(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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.");
|
|
953
|
+
localUser = requireUserRecord(
|
|
954
|
+
await adapter.create(resolveCreateUserInput(profile, options.user)),
|
|
955
|
+
"[@holo-js/auth-workos] Auth provider create() must return an object user."
|
|
956
|
+
);
|
|
601
957
|
const identity = createIdentityRecord({
|
|
602
958
|
provider: providerName,
|
|
603
959
|
guard,
|
|
@@ -620,7 +976,8 @@ async function syncIdentity(session, provider) {
|
|
|
620
976
|
session
|
|
621
977
|
});
|
|
622
978
|
}
|
|
623
|
-
async function authenticate(
|
|
979
|
+
async function authenticate(input, provider) {
|
|
980
|
+
const request = normalizeWorkosRequest(input);
|
|
624
981
|
const session = await verifyRequest(request, provider);
|
|
625
982
|
if (!session) {
|
|
626
983
|
return null;
|
|
@@ -628,28 +985,175 @@ async function authenticate(request, provider) {
|
|
|
628
985
|
const authenticated = await syncIdentity(session, provider);
|
|
629
986
|
const authSession = await reuseExistingHoloSession(request, authenticated) ?? await authRuntimeInternals.establishSessionForUser(authenticated.user, {
|
|
630
987
|
guard: authenticated.guard,
|
|
631
|
-
provider: authenticated.authProvider
|
|
988
|
+
provider: authenticated.authProvider,
|
|
989
|
+
payload: createWorkosSessionPayload(authenticated, session)
|
|
632
990
|
});
|
|
633
991
|
return Object.freeze({
|
|
634
992
|
...authenticated,
|
|
635
993
|
authSession
|
|
636
994
|
});
|
|
637
995
|
}
|
|
996
|
+
function getErrorMessage(error) {
|
|
997
|
+
return error instanceof Error ? error.message : "WorkOS authentication failed.";
|
|
998
|
+
}
|
|
999
|
+
function createHostedAuthSuccess(data) {
|
|
1000
|
+
return Object.freeze({
|
|
1001
|
+
data,
|
|
1002
|
+
error: null
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
function createHostedAuthFailure(code, message, status) {
|
|
1006
|
+
return Object.freeze({
|
|
1007
|
+
data: null,
|
|
1008
|
+
error: Object.freeze({
|
|
1009
|
+
code,
|
|
1010
|
+
message,
|
|
1011
|
+
status,
|
|
1012
|
+
fields: Object.freeze({
|
|
1013
|
+
_root: Object.freeze([message])
|
|
1014
|
+
})
|
|
1015
|
+
})
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
async function loginWithWorkos(input, options = {}) {
|
|
1019
|
+
try {
|
|
1020
|
+
const request = normalizeWorkosRequest(input);
|
|
1021
|
+
const providerConfig = getConfiguredProviderConfig(options.provider);
|
|
1022
|
+
return createWorkosRedirect(request, providerConfig, "sign-in");
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
return createWorkosErrorResponse(error, "workos_login_failed");
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
async function registerWithWorkos(input, options = {}) {
|
|
1028
|
+
try {
|
|
1029
|
+
const request = normalizeWorkosRequest(input);
|
|
1030
|
+
const providerConfig = getConfiguredProviderConfig(options.provider);
|
|
1031
|
+
return createWorkosRedirect(request, providerConfig, "sign-up");
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
return createWorkosErrorResponse(error, "workos_register_failed");
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async function readCurrentWorkosLogoutSession(guard, providerName) {
|
|
1037
|
+
const bindings = authRuntimeInternals.getRuntimeBindings();
|
|
1038
|
+
const sessionId = bindings.context.getSessionId(guard);
|
|
1039
|
+
if (!sessionId) {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
const record = await bindings.session.read(sessionId);
|
|
1043
|
+
return getWorkosLogoutSession(authRuntimeInternals.readSessionPayload(record, guard), providerName);
|
|
1044
|
+
}
|
|
1045
|
+
async function logoutWithWorkos(input, options = {}) {
|
|
1046
|
+
const request = normalizeWorkosRequest(input);
|
|
1047
|
+
try {
|
|
1048
|
+
const providerConfig = getConfiguredProviderConfig(options.provider);
|
|
1049
|
+
const providerName = providerConfig.name;
|
|
1050
|
+
const { guard } = resolveGuardAndProvider(providerName);
|
|
1051
|
+
const requestSessionId = getHoloSessionIdFromRequest(request);
|
|
1052
|
+
if (requestSessionId) {
|
|
1053
|
+
authRuntimeInternals.getRuntimeBindings().context.setSessionId(guard, requestSessionId);
|
|
1054
|
+
}
|
|
1055
|
+
const workosSession = await readCurrentWorkosLogoutSession(guard, providerName);
|
|
1056
|
+
if (!workosSession) {
|
|
1057
|
+
return createHostedAuthFailure(
|
|
1058
|
+
"workos_session_missing",
|
|
1059
|
+
"The current Holo session was not created by WorkOS.",
|
|
1060
|
+
422
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
const local = await getAuthRuntime().guard(guard).logout();
|
|
1064
|
+
return createHostedAuthSuccess(Object.freeze({
|
|
1065
|
+
url: createLogoutUrl(workosSession.sessionId, request, options.returnTo),
|
|
1066
|
+
local
|
|
1067
|
+
}));
|
|
1068
|
+
} catch {
|
|
1069
|
+
return createHostedAuthFailure("workos_logout_failed", "Unable to complete WorkOS logout.", 500);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async function completeWorkosAuth(input, options = {}) {
|
|
1073
|
+
try {
|
|
1074
|
+
const request = normalizeWorkosRequest(input);
|
|
1075
|
+
const url = new URL(request.url);
|
|
1076
|
+
const callbackError = url.searchParams.get("error")?.trim();
|
|
1077
|
+
if (callbackError) {
|
|
1078
|
+
return createHostedAuthFailure(
|
|
1079
|
+
callbackError,
|
|
1080
|
+
url.searchParams.get("error_description")?.trim() || "WorkOS authentication failed.",
|
|
1081
|
+
422
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
const code = url.searchParams.get("code")?.trim();
|
|
1085
|
+
if (!code) {
|
|
1086
|
+
return createHostedAuthFailure(
|
|
1087
|
+
"workos_code_required",
|
|
1088
|
+
"WorkOS callback did not include an authorization code.",
|
|
1089
|
+
422
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
const providerConfig = getConfiguredProviderConfig(options.provider);
|
|
1093
|
+
const callbackState = url.searchParams.get("state")?.trim();
|
|
1094
|
+
const expectedState = getWorkosStateFromRequest(request, providerConfig.name);
|
|
1095
|
+
if (!callbackState || !expectedState || !Object.is(callbackState, expectedState)) {
|
|
1096
|
+
return createHostedAuthFailure(
|
|
1097
|
+
"workos_state_mismatch",
|
|
1098
|
+
"WorkOS callback state did not match the login request.",
|
|
1099
|
+
422
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
const session = await authenticateWorkosCode(request, code, providerConfig);
|
|
1103
|
+
const authenticated = await syncIdentity(session, providerConfig.name, {
|
|
1104
|
+
user: options.user
|
|
1105
|
+
});
|
|
1106
|
+
const authSession = await authRuntimeInternals.establishSessionForUser(authenticated.user, {
|
|
1107
|
+
guard: authenticated.guard,
|
|
1108
|
+
provider: authenticated.authProvider,
|
|
1109
|
+
payload: createWorkosSessionPayload(authenticated, session)
|
|
1110
|
+
});
|
|
1111
|
+
return createHostedAuthSuccess(Object.freeze({
|
|
1112
|
+
provider: authenticated.provider,
|
|
1113
|
+
guard: authenticated.guard,
|
|
1114
|
+
authProvider: authenticated.authProvider,
|
|
1115
|
+
status: authenticated.status,
|
|
1116
|
+
user: authenticated.user,
|
|
1117
|
+
identity: authenticated.identity,
|
|
1118
|
+
session,
|
|
1119
|
+
authSession: Object.freeze({
|
|
1120
|
+
...authSession,
|
|
1121
|
+
cookies: Object.freeze([
|
|
1122
|
+
...authSession.cookies,
|
|
1123
|
+
clearStateCookie(providerConfig.name, request)
|
|
1124
|
+
])
|
|
1125
|
+
})
|
|
1126
|
+
}));
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
return createHostedAuthFailure(
|
|
1129
|
+
error instanceof WorkosAuthConflictError ? error.code : "workos_auth_failed",
|
|
1130
|
+
getErrorMessage(error),
|
|
1131
|
+
error instanceof WorkosAuthConflictError ? 409 : 422
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
638
1135
|
function configureWorkosAuthRuntime(bindings) {
|
|
1136
|
+
const runtimeState = getRuntimeState();
|
|
639
1137
|
if (!bindings) {
|
|
640
|
-
|
|
1138
|
+
runtimeState.bindings = void 0;
|
|
641
1139
|
return;
|
|
642
1140
|
}
|
|
643
|
-
|
|
644
|
-
providers: bindings.providers ??
|
|
645
|
-
identityStore: bindings.identityStore ??
|
|
1141
|
+
runtimeState.bindings = Object.freeze({
|
|
1142
|
+
providers: bindings.providers ?? runtimeState.bindings?.providers,
|
|
1143
|
+
identityStore: bindings.identityStore ?? runtimeState.bindings?.identityStore
|
|
646
1144
|
});
|
|
647
1145
|
}
|
|
648
1146
|
function resetWorkosAuthRuntime() {
|
|
649
|
-
|
|
1147
|
+
getRuntimeState().bindings = void 0;
|
|
1148
|
+
workosDefaultProviderRuntimeCache.clear();
|
|
1149
|
+
workosJwksCache.clear();
|
|
650
1150
|
}
|
|
651
1151
|
var workosAuth = Object.freeze({
|
|
652
1152
|
authenticate,
|
|
1153
|
+
completeWorkosAuth,
|
|
1154
|
+
loginWithWorkos,
|
|
1155
|
+
logoutWithWorkos,
|
|
1156
|
+
registerWithWorkos,
|
|
653
1157
|
syncIdentity,
|
|
654
1158
|
verifyRequest,
|
|
655
1159
|
verifySession
|
|
@@ -667,7 +1171,11 @@ var workosAuthInternals = {
|
|
|
667
1171
|
export {
|
|
668
1172
|
WorkosAuthConflictError,
|
|
669
1173
|
authenticate,
|
|
1174
|
+
completeWorkosAuth,
|
|
670
1175
|
configureWorkosAuthRuntime,
|
|
1176
|
+
loginWithWorkos,
|
|
1177
|
+
logoutWithWorkos,
|
|
1178
|
+
registerWithWorkos,
|
|
671
1179
|
resetWorkosAuthRuntime,
|
|
672
1180
|
syncIdentity,
|
|
673
1181
|
verifyRequest,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/auth-workos",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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": "
|
|
27
|
-
"@holo-js/config": "
|
|
28
|
-
"@holo-js/session": "
|
|
26
|
+
"@holo-js/auth": "^0.1.5",
|
|
27
|
+
"@holo-js/config": "^0.1.5",
|
|
28
|
+
"@holo-js/session": "^0.1.5"
|
|
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": "^
|
|
34
|
+
"vitest": "^4.1.5"
|
|
35
35
|
}
|
|
36
36
|
}
|