@holo-js/auth-workos 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -13
- package/dist/index.mjs +40 -173
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthResult,
|
|
1
|
+
import { AuthResult, AuthenticatedAuthUser, AuthEstablishedSession, AuthFieldErrors, AuthLogoutResult, authRuntimeInternals } from '@holo-js/auth';
|
|
2
2
|
import { NormalizedAuthWorkosProviderConfig } from '@holo-js/config';
|
|
3
3
|
|
|
4
4
|
type WorkosJsonValue = string | number | boolean | null | readonly WorkosJsonValue[] | {
|
|
@@ -99,16 +99,7 @@ interface HostedIdentityStore {
|
|
|
99
99
|
save(record: HostedIdentityRecord): Promise<void>;
|
|
100
100
|
}
|
|
101
101
|
type WorkosSyncStatus = 'created' | 'updated' | 'linked' | 'relinked';
|
|
102
|
-
|
|
103
|
-
readonly provider: string;
|
|
104
|
-
readonly guard: string;
|
|
105
|
-
readonly authProvider: string;
|
|
106
|
-
readonly status: WorkosSyncStatus;
|
|
107
|
-
readonly user: WorkosAuthenticatedUser<TUserAttributes>;
|
|
108
|
-
readonly identity: HostedIdentityRecord;
|
|
109
|
-
readonly session: WorkosVerifiedSession;
|
|
110
|
-
readonly authSession?: AuthEstablishedSession;
|
|
111
|
-
}
|
|
102
|
+
type WorkosAuthenticationResult<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes> = WorkosCompleteAuthData<TUserAttributes>;
|
|
112
103
|
interface WorkosAuthBindings {
|
|
113
104
|
readonly providers: Readonly<Record<string, WorkosProviderRuntime>>;
|
|
114
105
|
readonly identityStore: HostedIdentityStore;
|
|
@@ -127,7 +118,7 @@ interface WorkosAuthFacade {
|
|
|
127
118
|
logoutWithWorkos(request: Request, options?: {
|
|
128
119
|
readonly provider?: string;
|
|
129
120
|
readonly returnTo?: string;
|
|
130
|
-
}): Promise<
|
|
121
|
+
}): Promise<Response>;
|
|
131
122
|
completeWorkosAuth<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes>(request: Request, options?: WorkosCompleteAuthOptions<TUserAttributes>): Promise<WorkosCompleteAuthResult<TUserAttributes>>;
|
|
132
123
|
verifyRequest(request: Request, provider?: string): Promise<WorkosVerifiedSession | null>;
|
|
133
124
|
verifySession(token: string, provider?: string): Promise<WorkosVerifiedSession | null>;
|
|
@@ -203,7 +194,7 @@ declare function registerWithWorkos(input: WorkosRequestInput, options?: {
|
|
|
203
194
|
declare function logoutWithWorkos(input: WorkosRequestInput, options?: {
|
|
204
195
|
readonly provider?: string;
|
|
205
196
|
readonly returnTo?: string;
|
|
206
|
-
}): Promise<
|
|
197
|
+
}): Promise<Response>;
|
|
207
198
|
declare function completeWorkosAuth<TUserAttributes extends WorkosUserAttributes = WorkosDefaultUserAttributes>(input: WorkosRequestInput, options?: WorkosCompleteAuthOptions<TUserAttributes>): Promise<WorkosCompleteAuthResult<TUserAttributes>>;
|
|
208
199
|
declare function configureWorkosAuthRuntime(bindings?: ConfigureWorkosAuthRuntimeOptions): void;
|
|
209
200
|
declare function resetWorkosAuthRuntime(): void;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import { randomBytes } from "crypto";
|
|
3
3
|
import { authRuntimeInternals, getAuthRuntime } from "@holo-js/auth";
|
|
4
4
|
import { cookie, parseCookieHeader } from "@holo-js/session";
|
|
5
5
|
|
|
@@ -43,98 +43,8 @@ function getRuntimeState() {
|
|
|
43
43
|
function throwUnconfigured() {
|
|
44
44
|
throw new Error("[@holo-js/auth-workos] WorkOS auth runtime is not configured yet.");
|
|
45
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
46
|
function normalizeWorkosRequest(input) {
|
|
126
|
-
|
|
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
|
-
});
|
|
47
|
+
return authRuntimeInternals.normalizeRequestInput(input);
|
|
138
48
|
}
|
|
139
49
|
function getBindings() {
|
|
140
50
|
const workosBindings = getRuntimeState().bindings;
|
|
@@ -146,75 +56,31 @@ function getBindings() {
|
|
|
146
56
|
identityStore: workosBindings.identityStore
|
|
147
57
|
};
|
|
148
58
|
}
|
|
149
|
-
function decodeJwtSegment(value, label) {
|
|
150
|
-
try {
|
|
151
|
-
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
152
|
-
} catch {
|
|
153
|
-
throw new Error(`[@holo-js/auth-workos] WorkOS token ${label} was not valid JSON.`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
59
|
function parseJwt(token) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
header: decodeJwtSegment(segments[0], "header"),
|
|
163
|
-
payload: decodeJwtSegment(segments[1], "payload"),
|
|
164
|
-
signature: Buffer.from(segments[2], "base64url"),
|
|
165
|
-
signingInput: Buffer.from(`${segments[0]}.${segments[1]}`, "utf8")
|
|
166
|
-
};
|
|
60
|
+
return authRuntimeInternals.jwt.parseJwt(token, {
|
|
61
|
+
errorPrefix: "[@holo-js/auth-workos] WorkOS token",
|
|
62
|
+
malformedMessage: "[@holo-js/auth-workos] WorkOS token was not a valid JWT."
|
|
63
|
+
});
|
|
167
64
|
}
|
|
168
65
|
function getJwtStringClaim(token, claim) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
return void 0;
|
|
174
|
-
}
|
|
66
|
+
return authRuntimeInternals.jwt.getJwtStringClaim(token, claim, {
|
|
67
|
+
errorPrefix: "[@holo-js/auth-workos] WorkOS token",
|
|
68
|
+
malformedMessage: "[@holo-js/auth-workos] WorkOS token was not a valid JWT."
|
|
69
|
+
});
|
|
175
70
|
}
|
|
176
71
|
function verifyJwtSignatureWithJwk(token, jwk) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
case "RS256":
|
|
181
|
-
return verifySignature("RSA-SHA256", token.signingInput, key, token.signature);
|
|
182
|
-
case "RS384":
|
|
183
|
-
return verifySignature("RSA-SHA384", token.signingInput, key, token.signature);
|
|
184
|
-
case "RS512":
|
|
185
|
-
return verifySignature("RSA-SHA512", token.signingInput, key, token.signature);
|
|
186
|
-
default:
|
|
187
|
-
throw new Error(`[@holo-js/auth-workos] Unsupported WorkOS JWT algorithm "${algorithm || "unknown"}".`);
|
|
188
|
-
}
|
|
72
|
+
return authRuntimeInternals.jwt.verifyJwtSignatureWithJwk(token, jwk, {
|
|
73
|
+
unsupportedAlgorithmMessage: (algorithm) => `[@holo-js/auth-workos] Unsupported WorkOS JWT algorithm "${algorithm}".`
|
|
74
|
+
});
|
|
189
75
|
}
|
|
190
76
|
async function fetchWorkosJwks(clientId, options = {}) {
|
|
191
77
|
const normalizedClientId = clientId.trim();
|
|
192
|
-
|
|
193
|
-
workosJwksCache
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
const pending = (async () => {
|
|
200
|
-
const response = await fetch(`${WORKOS_API_BASE_URL}/sso/jwks/${encodeURIComponent(normalizedClientId)}`, {
|
|
201
|
-
headers: {
|
|
202
|
-
accept: "application/json"
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
if (!response.ok) {
|
|
206
|
-
throw new Error(`[@holo-js/auth-workos] Failed to load WorkOS JWKS for "${normalizedClientId}".`);
|
|
207
|
-
}
|
|
208
|
-
const payload = await response.json();
|
|
209
|
-
return payload.keys ?? [];
|
|
210
|
-
})();
|
|
211
|
-
workosJwksCache.set(normalizedClientId, pending);
|
|
212
|
-
try {
|
|
213
|
-
return await pending;
|
|
214
|
-
} catch (error) {
|
|
215
|
-
workosJwksCache.delete(normalizedClientId);
|
|
216
|
-
throw error;
|
|
217
|
-
}
|
|
78
|
+
return authRuntimeInternals.jwt.fetchCachedJwks(normalizedClientId, {
|
|
79
|
+
cache: workosJwksCache,
|
|
80
|
+
requestUrl: `${WORKOS_API_BASE_URL}/sso/jwks/${encodeURIComponent(normalizedClientId)}`,
|
|
81
|
+
refresh: options.refresh,
|
|
82
|
+
errorMessage: `[@holo-js/auth-workos] Failed to load WorkOS JWKS for "${normalizedClientId}".`
|
|
83
|
+
});
|
|
218
84
|
}
|
|
219
85
|
async function verifyWorkosSessionToken(token, config) {
|
|
220
86
|
const clientId = config.clientId?.trim();
|
|
@@ -1014,21 +880,18 @@ function createHostedAuthFailure(code, message, status) {
|
|
|
1014
880
|
});
|
|
1015
881
|
}
|
|
1016
882
|
async function loginWithWorkos(input, options = {}) {
|
|
1017
|
-
|
|
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
|
-
}
|
|
883
|
+
return createWorkosHostedAuthRedirect(input, options.provider, "sign-in", "workos_login_failed");
|
|
1024
884
|
}
|
|
1025
885
|
async function registerWithWorkos(input, options = {}) {
|
|
886
|
+
return createWorkosHostedAuthRedirect(input, options.provider, "sign-up", "workos_register_failed");
|
|
887
|
+
}
|
|
888
|
+
function createWorkosHostedAuthRedirect(input, provider, screenHint, failureCode) {
|
|
1026
889
|
try {
|
|
1027
890
|
const request = normalizeWorkosRequest(input);
|
|
1028
|
-
const providerConfig = getConfiguredProviderConfig(
|
|
1029
|
-
return createWorkosRedirect(request, providerConfig,
|
|
891
|
+
const providerConfig = getConfiguredProviderConfig(provider);
|
|
892
|
+
return createWorkosRedirect(request, providerConfig, screenHint);
|
|
1030
893
|
} catch (error) {
|
|
1031
|
-
return createWorkosErrorResponse(error,
|
|
894
|
+
return createWorkosErrorResponse(error, failureCode);
|
|
1032
895
|
}
|
|
1033
896
|
}
|
|
1034
897
|
async function readCurrentWorkosLogoutSession(guard, providerName) {
|
|
@@ -1052,19 +915,23 @@ async function logoutWithWorkos(input, options = {}) {
|
|
|
1052
915
|
}
|
|
1053
916
|
const workosSession = await readCurrentWorkosLogoutSession(guard, providerName);
|
|
1054
917
|
if (!workosSession) {
|
|
1055
|
-
return
|
|
1056
|
-
"workos_session_missing",
|
|
1057
|
-
"The current Holo session was not created by WorkOS.",
|
|
1058
|
-
422
|
|
1059
|
-
);
|
|
918
|
+
return Response.redirect(new URL("/", request.url), 303);
|
|
1060
919
|
}
|
|
1061
920
|
const local = await getAuthRuntime().guard(guard).logout();
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
921
|
+
const response = new Response(null, {
|
|
922
|
+
status: 303,
|
|
923
|
+
headers: {
|
|
924
|
+
Location: createLogoutUrl(workosSession.sessionId, request, options.returnTo)
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
for (const cookie2 of local.cookies) {
|
|
928
|
+
response.headers.append("Set-Cookie", cookie2);
|
|
929
|
+
}
|
|
930
|
+
return response;
|
|
1066
931
|
} catch {
|
|
1067
|
-
return createHostedAuthFailure("workos_logout_failed", "Unable to complete WorkOS logout.", 500)
|
|
932
|
+
return Response.json(createHostedAuthFailure("workos_logout_failed", "Unable to complete WorkOS logout.", 500), {
|
|
933
|
+
status: 500
|
|
934
|
+
});
|
|
1068
935
|
}
|
|
1069
936
|
}
|
|
1070
937
|
async function completeWorkosAuth(input, options = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/auth-workos",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Holo-JS Framework - WorkOS auth integration contracts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"test": "vitest --run"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@holo-js/auth": "^0.1
|
|
27
|
-
"@holo-js/config": "^0.1
|
|
28
|
-
"@holo-js/session": "^0.1
|
|
26
|
+
"@holo-js/auth": "^0.2.1",
|
|
27
|
+
"@holo-js/config": "^0.2.1",
|
|
28
|
+
"@holo-js/session": "^0.2.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^22.10.2",
|