@farm.js/workos 0.1.0-beta.97 → 0.1.0-beta.98
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.map +1 -1
- package/dist/index.js +65 -20
- package/package.json +3 -3
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAuC,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAWhG,OAAO,KAAK,EAAuB,oBAAoB,EAAuB,MAAM,aAAa,CAAC;AAQlG,MAAM,WAAW,sBAAsB;IACrC,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACpC,GAAG,CAAC,EAAE,qBAAqB,CAAC;CAC7B;AAED,MAAM,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAc/C,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAqFD,wBAAgB,MAAM,CAAC,KAAK,GAAE,sBAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAqMG,MAAM;;;;yBAoE7B,OAAO;;GAe3C"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
1
2
|
import { WorkOS } from "@workos-inc/node";
|
|
2
3
|
import { defineIntegration, integrationRoute } from "@farm.js/core";
|
|
3
4
|
import { clearRequestCookie, createPathInferredClientApi, createDocumentNavigationMatchers, createRequestCookie, getCookieValue, getReturnTo, integrationConfig, normalizeMatchers, } from "@farm.js/integration-utils";
|
|
@@ -80,19 +81,62 @@ export function workos(input = {}) {
|
|
|
80
81
|
apiKey,
|
|
81
82
|
clientId,
|
|
82
83
|
});
|
|
84
|
+
// The OAuth `state` must be unguessable and bound to the browser that started
|
|
85
|
+
// the flow. We keep a random nonce in an HMAC-signed, http-only cookie and
|
|
86
|
+
// require the callback's state to match it, so an attacker cannot feed a
|
|
87
|
+
// victim a code/state pair from their own login (login CSRF / code injection).
|
|
88
|
+
const stateCookieName = "farm_workos_state";
|
|
89
|
+
function signState(payload) {
|
|
90
|
+
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
91
|
+
const signature = createHmac("sha256", cookiePassword).update(encoded).digest("hex");
|
|
92
|
+
return `${encoded}.${signature}`;
|
|
93
|
+
}
|
|
94
|
+
function unsignState(signed) {
|
|
95
|
+
if (!signed)
|
|
96
|
+
return null;
|
|
97
|
+
const separator = signed.lastIndexOf(".");
|
|
98
|
+
if (separator <= 0)
|
|
99
|
+
return null;
|
|
100
|
+
const encoded = signed.slice(0, separator);
|
|
101
|
+
const signature = signed.slice(separator + 1);
|
|
102
|
+
const expected = createHmac("sha256", cookiePassword).update(encoded).digest("hex");
|
|
103
|
+
if (signature.length !== expected.length)
|
|
104
|
+
return null;
|
|
105
|
+
if (!timingSafeEqual(Buffer.from(signature, "utf8"), Buffer.from(expected, "utf8"))) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
110
|
+
if (!parsed || typeof parsed !== "object")
|
|
111
|
+
return null;
|
|
112
|
+
const { state, returnTo } = parsed;
|
|
113
|
+
if (typeof state !== "string" || typeof returnTo !== "string")
|
|
114
|
+
return null;
|
|
115
|
+
return { state, returnTo };
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
83
121
|
async function redirectToAuth(request, screenHint) {
|
|
84
122
|
const requestUrl = new URL(request.url);
|
|
85
123
|
const returnTo = getReturnTo(requestUrl.searchParams.get("returnTo"), "/dashboard");
|
|
86
124
|
const callbackUrl = new URL(callbackPath, requestUrl.origin);
|
|
125
|
+
const state = randomBytes(16).toString("hex");
|
|
87
126
|
const authorizationUrl = workos.userManagement.getAuthorizationUrl({
|
|
88
127
|
provider: "authkit",
|
|
89
128
|
clientId,
|
|
90
129
|
redirectUri: callbackUrl.toString(),
|
|
91
130
|
screenHint,
|
|
92
|
-
state
|
|
131
|
+
state,
|
|
93
132
|
});
|
|
133
|
+
const headers = new Headers();
|
|
134
|
+
headers.append("set-cookie", createRequestCookie(stateCookieName, signState({ state, returnTo }), request, {
|
|
135
|
+
maxAge: 600,
|
|
136
|
+
}));
|
|
94
137
|
return {
|
|
95
|
-
redirectTo: authorizationUrl,
|
|
138
|
+
result: { redirectTo: authorizationUrl },
|
|
139
|
+
headers,
|
|
96
140
|
};
|
|
97
141
|
}
|
|
98
142
|
return defineIntegration({
|
|
@@ -135,21 +179,23 @@ export function workos(input = {}) {
|
|
|
135
179
|
integrationRoute.get(loginPath, {
|
|
136
180
|
responseFormat: "json",
|
|
137
181
|
async handler(request) {
|
|
138
|
-
const result = await redirectToAuth(request, "sign-in");
|
|
182
|
+
const { result, headers } = await redirectToAuth(request, "sign-in");
|
|
139
183
|
if (request.headers.get("x-farm-integration-client") === "1") {
|
|
140
|
-
return Response.json(result);
|
|
184
|
+
return Response.json(result, { headers });
|
|
141
185
|
}
|
|
142
|
-
|
|
186
|
+
headers.set("location", result.redirectTo);
|
|
187
|
+
return new Response(null, { status: 302, headers });
|
|
143
188
|
},
|
|
144
189
|
}),
|
|
145
190
|
integrationRoute.get(signUpPath, {
|
|
146
191
|
responseFormat: "json",
|
|
147
192
|
async handler(request) {
|
|
148
|
-
const result = await redirectToAuth(request, "sign-up");
|
|
193
|
+
const { result, headers } = await redirectToAuth(request, "sign-up");
|
|
149
194
|
if (request.headers.get("x-farm-integration-client") === "1") {
|
|
150
|
-
return Response.json(result);
|
|
195
|
+
return Response.json(result, { headers });
|
|
151
196
|
}
|
|
152
|
-
|
|
197
|
+
headers.set("location", result.redirectTo);
|
|
198
|
+
return new Response(null, { status: 302, headers });
|
|
153
199
|
},
|
|
154
200
|
}),
|
|
155
201
|
integrationRoute.get(callbackPath, {
|
|
@@ -164,6 +210,13 @@ export function workos(input = {}) {
|
|
|
164
210
|
if (!code) {
|
|
165
211
|
return new Response("Missing WorkOS authorization code.", { status: 400 });
|
|
166
212
|
}
|
|
213
|
+
// Bind the callback to the browser that started the flow. Verify before
|
|
214
|
+
// the code is exchanged so an injected code is never redeemed.
|
|
215
|
+
const statePayload = unsignState(getCookieValue(request.headers, stateCookieName));
|
|
216
|
+
const state = requestUrl.searchParams.get("state");
|
|
217
|
+
if (!statePayload || !state || statePayload.state !== state) {
|
|
218
|
+
return new Response("Invalid WorkOS authentication state.", { status: 400 });
|
|
219
|
+
}
|
|
167
220
|
const authentication = await workos.userManagement.authenticateWithCode({
|
|
168
221
|
clientId,
|
|
169
222
|
code,
|
|
@@ -175,19 +228,11 @@ export function workos(input = {}) {
|
|
|
175
228
|
if (!authentication.sealedSession) {
|
|
176
229
|
return new Response("WorkOS did not return a sealed session.", { status: 500 });
|
|
177
230
|
}
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
if (rawState) {
|
|
181
|
-
try {
|
|
182
|
-
const parsed = JSON.parse(rawState);
|
|
183
|
-
returnTo = getReturnTo(parsed.returnTo ?? null, "/dashboard");
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
returnTo = "/dashboard";
|
|
187
|
-
}
|
|
188
|
-
}
|
|
231
|
+
// Taken from the signed cookie, never from the untrusted query string.
|
|
232
|
+
const returnTo = getReturnTo(statePayload.returnTo, "/dashboard");
|
|
189
233
|
const headers = new Headers();
|
|
190
|
-
headers.
|
|
234
|
+
headers.append("set-cookie", createRequestCookie(cookieName, authentication.sealedSession, request));
|
|
235
|
+
headers.append("set-cookie", clearRequestCookie(stateCookieName, request));
|
|
191
236
|
headers.set("location", new URL(returnTo, requestUrl.origin).toString());
|
|
192
237
|
return new Response(null, {
|
|
193
238
|
status: 302,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/workos",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.98",
|
|
4
4
|
"description": "WorkOS integration for Farm.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@workos-inc/node": "^8.9.0",
|
|
33
|
-
"@farm.js/core": "0.1.0-beta.
|
|
34
|
-
"@farm.js/integration-utils": "0.1.0-beta.
|
|
33
|
+
"@farm.js/core": "0.1.0-beta.98",
|
|
34
|
+
"@farm.js/integration-utils": "0.1.0-beta.98"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc",
|