Skip to content

Commit 091d3f2

Browse files
authored
Update SDK dependencies to latest version and handle the breaking changes (#1100)
We update the sdk dependencies (the ones present in `package-template.json`) to the latest versions. Since several packages have major version bumps, this results in a variety of breaking changes that have been handled here. Incidentally, when possible, we update similar dependencies across the codebase. We decide to defer the tailwind update to another PR owing to its scale. The rest of the updates and changes have been catalogued below: 1. [Bumping](https://github.com/panva/oauth4webapi/blob/v3.x/CHANGELOG.md) `oauth4webapi` to 3.8.3: this was a major version changed. While there were no compatibility issues in the sdk, there were several breaking changes in `stack-shared`. Namely: a. The removal of `isOauth2Error`. We used this to check if the results of our `oauth4webapi` api invocations had issues. The functions were changed to explicitly throw either `ResponseBodyErrors` or `AuthorizationResponseErrors`, so the code was reworked to account for that with no loss in error handling. b. Dropping of support for http broadly: `oauth4webapi` now only accepts https. This is desired, but I add a carve out for our test environments only. c. `refreshTokenGrantRequest` and `authorizationCodeGrantRequest` now require `clientAuthentication` to be passed explicitly to them. d. Changes in how we handle our `MultiFactorAuthenticationRequired` error: This is an error that we created and is passed to the `oauth4webapi` API if there are MFA issues. Since the `processAuthorizationCodeResponse` now explicitly throws a `ResponseBodyError`, we access the error cause from the body of the error instead. 2. [Bumping](https://github.com/Qix-/color/releases) `color` to 5.0.4: this was a major version bump. Simple type checking change, I checked the API for the correct interface. 3. [Bumping](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md) `simplewebauthn` to 13.2.2: two major version bumps, but no incompatibilities surprisingly 4. [Bumping](https://github.com/jshttp/cookie/releases) `cookie` to 1.1.1: this was a major version bump. a. Changing `parse` to `parseCookie`. In the most recent version, `parse` is still maintained as an alias for `parseCookie` for backwards compatibility, but I thought it would be best to change it over now. No change in functionality. b. Typing is now strongly enforced. A cookie can be `string | undefined`, and the `Cookies` are now `Record<string, string | undefined>`. We already have code to handle if a cookie is returned as undefined/ null, so the changes here were more to ensure type compatibility rather than big changes in functionality. 5. [Bumping ](https://github.com/isaacs/rimraf#readme)`rimraf` to 6.1.2: No breaking changes, mostly just bug fixes. 6. [Bumping](https://github.com/panva/jose/releases?page=1) `jose` to 6.1.3: This is another major version bump. We update it across the codebase to ensure compatibility. We use this for importing and processing jwk tokens. There are a few big changes in the version bump, but the only one that applies to us is that `importJwk` now yields a `CryptoKey` instead of a `KeyObject` in Node.js. However, this doesn't appear to break our code. We use `importJwk` in `stack-auth/packages/stack-shared/src/utils/jwt.tsx`. 7. [Bumping](https://github.com/react-hook-form/resolvers/releases) `hookform/resolvers` to 5.2.2 (two major version jumps), and consequently bumping `react-hook-form` to 7.70.0: We already use the patterns that `hookform/resolvers`' latest versions seem to be enforcing. The only other breaking change is that it requires version 7.55.0+ of `react-hook-form`. Though we should pay attention to any interactions with zod and `hookform/resolvers`, some people have reported compatibility issues if they aren't using the latest compatible versions of both. 8. [Bumping](https://github.com/jquense/yup/blob/master/CHANGELOG.md) `yup` to 1.7.1: this was a minor version change, but we had incompatibility issues with this change. Versions 1.4.1 and 1.7.1 cannot exist in the same codebase due to incompatibility, so we bumped it up across the codebase, including in peer dependencies. 9. Some minor version changes for some packages, but these were mostly bug fixes. 10. **Edited to add**: Bumping freestyle to 0.1.6, and reworking the freestyle mock server. In 0.1.6, freestyle changed their API in two ways: a. We're now supposed to hit their `execute/v2/...` endpoint and b. They've flattened the `config` argument to `serverless.runs.create`. These changes are minor, but are important. As part of a general suite of dependency bumps, this was judged to fit here. We have linked the changelogs for the packages on each line.
1 parent 01c890d commit 091d3f2

16 files changed

Lines changed: 792 additions & 337 deletions

File tree

apps/backend/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@
8383
"chokidar-cli": "^3.0.0",
8484
"dotenv": "^16.4.5",
8585
"dotenv-cli": "^7.3.0",
86-
"freestyle-sandboxes": "^0.1.5",
87-
"jose": "^5.2.2",
86+
"freestyle-sandboxes": "^0.1.6",
87+
"jose": "^6.1.3",
8888
"json-diff": "^1.0.6",
8989
"next": "16.1.1",
9090
"nodemailer": "^6.9.10",
@@ -102,7 +102,7 @@
102102
"svix": "^1.25.0",
103103
"vite": "^6.1.0",
104104
"yaml": "^2.4.5",
105-
"yup": "^1.4.0",
105+
"yup": "^1.7.1",
106106
"zod": "^3.23.8"
107107
},
108108
"devDependencies": {

apps/backend/src/lib/js-execution.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ function createFreestyleEngine(): JsEngine {
3838

3939
const response = await freestyle.serverless.runs.create({
4040
code,
41-
config: {
42-
nodeModules: options.nodeModules ?? {},
43-
},
41+
nodeModules: options.nodeModules ?? {},
4442
});
4543

4644
if (response.result === undefined) {

apps/dashboard/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"export-to-csv": "^1.4.0",
7878
"geist": "^1",
7979
"input-otp": "^1.4.1",
80-
"jose": "^5.2.2",
80+
"jose": "^6.1.3",
8181
"lodash": "^4.17.21",
8282
"next": "16.1.1",
8383
"next-themes": "^0.2.1",
@@ -99,7 +99,7 @@
9999
"tailwindcss-animate": "^1.0.7",
100100
"three": "^0.169.0",
101101
"use-debounce": "^10.0.5",
102-
"yup": "^1.4.0"
102+
"yup": "^1.7.1"
103103
},
104104
"devDependencies": {
105105
"@types/canvas-confetti": "^1.6.4",

docker/dependencies/freestyle-mock/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ serve({
3939
port: 8080,
4040
async fetch(req) {
4141
const url = new URL(req.url);
42-
if (!(req.method === "POST" && url.pathname === "/execute/v1/script")) {
42+
const isValidEndpoint = req.method === "POST" && (url.pathname === "/execute/v1/script" || url.pathname === "/execute/v2/script");
43+
if (!isValidEndpoint) {
4344
return new Response("Not found", { status: 404 });
4445
}
4546

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"fumadocs-openapi": "^8.1.12",
4242
"fumadocs-typescript": "^4.0.5",
4343
"fumadocs-ui": "15.3.3",
44-
"jose": "^6.1.0",
44+
"jose": "^6.1.3",
4545
"js-yaml": "^4.1.0",
4646
"lucide-react": "^0.511.0",
4747
"mermaid": "^11.6.0",

packages/js/package.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,20 @@
5050
"LICENSE"
5151
],
5252
"dependencies": {
53-
"@hookform/resolvers": "^3.3.4",
54-
"@simplewebauthn/browser": "^11.0.0",
53+
"@hookform/resolvers": "^5.2.2",
54+
"@simplewebauthn/browser": "^13.2.2",
5555
"@stackframe/stack-shared": "workspace:*",
56-
"@tanstack/react-table": "^8.20.5",
57-
"color": "^4.2.3",
58-
"cookie": "^0.6.0",
59-
"jose": "^5.2.2",
56+
"@tanstack/react-table": "^8.21.3",
57+
"color": "^5.0.3",
58+
"cookie": "^1.1.1",
59+
"jose": "^6.1.3",
6060
"js-cookie": "^3.0.5",
61-
"oauth4webapi": "^2.10.3",
61+
"oauth4webapi": "^3.8.3",
6262
"@oslojs/otp": "^1.1.0",
6363
"qrcode": "^1.5.4",
64-
"rimraf": "^5.0.5",
65-
"tsx": "^4.7.2",
66-
"yup": "^1.4.0"
64+
"rimraf": "^6.1.2",
65+
"tsx": "^4.21.0",
66+
"yup": "^1.7.1"
6767
},
6868
"devDependencies": {
6969
"@quetzallabs/i18n": "^0.1.19",

packages/react/package.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,28 @@
5858
"LICENSE"
5959
],
6060
"dependencies": {
61-
"@hookform/resolvers": "^3.3.4",
61+
"@hookform/resolvers": "^5.2.2",
6262
"@stripe/react-stripe-js": "^3.8.1",
6363
"@stripe/stripe-js": "^7.7.0",
64-
"@simplewebauthn/browser": "^11.0.0",
64+
"@simplewebauthn/browser": "^13.2.2",
6565
"@stackframe/stack-shared": "workspace:*",
6666
"@stackframe/stack-ui": "workspace:*",
67-
"@tanstack/react-table": "^8.20.5",
67+
"@tanstack/react-table": "^8.21.3",
6868
"browser-image-compression": "^2.0.2",
69-
"color": "^4.2.3",
70-
"cookie": "^0.6.0",
71-
"jose": "^5.2.2",
69+
"color": "^5.0.3",
70+
"cookie": "^1.1.1",
71+
"jose": "^6.1.3",
7272
"js-cookie": "^3.0.5",
7373
"lucide-react": "^0.378.0",
74-
"oauth4webapi": "^2.10.3",
74+
"oauth4webapi": "^3.8.3",
7575
"@oslojs/otp": "^1.1.0",
7676
"qrcode": "^1.5.4",
77-
"react-easy-crop": "^5.4.1",
78-
"react-hook-form": "^7.51.4",
79-
"rimraf": "^5.0.5",
77+
"react-easy-crop": "^5.5.6",
78+
"react-hook-form": "^7.70.0",
79+
"rimraf": "^6.1.2",
8080
"tailwindcss-animate": "^1.0.7",
81-
"tsx": "^4.7.2",
82-
"yup": "^1.4.0"
81+
"tsx": "^4.21.0",
82+
"yup": "^1.7.1"
8383
},
8484
"peerDependencies": {
8585
"@types/react": ">=18.3.0",

packages/stack-shared/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"@types/react-dom": ">=19.0.0",
3838
"react": ">=19.0.0",
3939
"react-dom": ">=19.0.0",
40-
"yup": "^1.4.0"
40+
"yup": "^1.7.1"
4141
},
4242
"peerDependenciesMeta": {
4343
"react": {
@@ -64,8 +64,8 @@
6464
"elliptic": "^6.5.7",
6565
"esbuild-wasm": "^0.20.2",
6666
"ip-regex": "^5.0.0",
67-
"jose": "^5.2.2",
68-
"oauth4webapi": "^2.10.3",
67+
"jose": "^6.1.3",
68+
"oauth4webapi": "^3.8.3",
6969
"semver": "^7.6.3",
7070
"uuid": "^9.0.1"
7171
},

packages/stack-shared/src/interface/client-interface.ts

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { KnownError, KnownErrors } from '../known-errors';
55
import { inlineProductSchema } from '../schema-fields';
66
import { AccessToken, InternalSession, RefreshToken } from '../sessions';
77
import { generateSecureRandomString } from '../utils/crypto';
8+
import { getNodeEnvironment } from '../utils/env';
89
import { StackAssertionError, throwErr } from '../utils/errors';
910
import { globalVar } from '../utils/globals';
1011
import { HTTP_METHODS, HttpMethod } from '../utils/http';
@@ -153,22 +154,27 @@ export class StackClientInterface {
153154
throw new Error("Admin session token is currently not supported for fetching new access token. Did you try to log in on a StackApp initiated with the admin session?");
154155
}
155156

157+
const tokenEndpoint = this.getApiUrl() + '/auth/oauth/token';
156158
const as = {
157159
issuer: this.options.getBaseUrl(),
158160
algorithm: 'oauth2',
159-
token_endpoint: this.getApiUrl() + '/auth/oauth/token',
161+
token_endpoint: tokenEndpoint,
160162
};
161163
const client: oauth.Client = {
162164
client_id: this.projectId,
163165
client_secret: this.options.publishableClientKey,
164-
token_endpoint_auth_method: 'client_secret_post',
165166
};
166167

168+
const clientAuthentication = oauth.ClientSecretPost(this.options.publishableClientKey);
169+
const allowInsecure = getNodeEnvironment() === 'test' && tokenEndpoint.startsWith('http://');
170+
167171
const response = await this._networkRetryException(async () => {
168172
const rawResponse = await oauth.refreshTokenGrantRequest(
169173
as,
170174
client,
175+
clientAuthentication,
171176
refreshToken.token,
177+
allowInsecure ? { [oauth.allowInsecureRequests]: true } : undefined,
172178
);
173179

174180
const response = await this._processResponse(rawResponse);
@@ -190,17 +196,26 @@ export class StackClientInterface {
190196
});
191197
if (!response) return null;
192198

193-
const result = await oauth.processRefreshTokenResponse(as, client, response);
194-
if (oauth.isOAuth2Error(result)) {
195-
// TODO Handle OAuth 2.0 response body error
196-
throw new StackAssertionError("OAuth error", { result });
199+
let result: oauth.TokenEndpointResponse;
200+
try {
201+
result = await oauth.processRefreshTokenResponse(as, client, response);
202+
} catch (e){
203+
if (e instanceof oauth.ResponseBodyError) {
204+
throw new StackAssertionError("ResponseBodyError when processing refresh token response", {
205+
cause: e.cause,
206+
code: e.code,
207+
error: e.error,
208+
});
209+
}
210+
throw new StackAssertionError("Unexpected error when processing refresh token response", { cause: e });
197211
}
198212

199213
if (!result.access_token) {
200214
throw new StackAssertionError("Access token not found in token endpoint response, this is weird!");
201215
}
202216

203217
return AccessToken.createIfValid(result.access_token) ?? throwErr("Access token in fetchNewAccessToken is invalid, looks like the backend is returning an invalid token!", { result });
218+
204219
}
205220

206221
public async sendClientRequest(
@@ -1015,37 +1030,60 @@ export class StackClientInterface {
10151030
// TODO fix
10161031
throw new Error("Admin session token is currently not supported for OAuth");
10171032
}
1033+
const tokenEndpoint = this.getApiUrl() + '/auth/oauth/token';
10181034
const as = {
10191035
issuer: this.options.getBaseUrl(),
10201036
algorithm: 'oauth2',
1021-
token_endpoint: this.getApiUrl() + '/auth/oauth/token',
1037+
token_endpoint: tokenEndpoint,
10221038
};
10231039
const client: oauth.Client = {
10241040
client_id: this.projectId,
10251041
client_secret: this.options.publishableClientKey,
1026-
token_endpoint_auth_method: 'client_secret_post',
10271042
};
1028-
const params = await this._networkRetryException(
1029-
async () => oauth.validateAuthResponse(as, client, options.oauthParams, options.state),
1030-
);
1031-
if (oauth.isOAuth2Error(params)) {
1032-
throw new StackAssertionError("Error validating outer OAuth response", { params }); // Handle OAuth 2.0 redirect error
1043+
const clientAuthentication = oauth.ClientSecretPost(this.options.publishableClientKey);
1044+
// Allow insecure HTTP requests only in test environment (for localhost testing)
1045+
const allowInsecure = getNodeEnvironment() === 'test' && tokenEndpoint.startsWith('http://');
1046+
1047+
let params: URLSearchParams;
1048+
try {
1049+
params = oauth.validateAuthResponse(as, client, options.oauthParams, options.state);
1050+
} catch (e) {
1051+
if (e instanceof oauth.AuthorizationResponseError) {
1052+
throw new StackAssertionError("Authorization response error when validating outer OAuth response", {
1053+
//cause is a URLSearchParams object for this error, so we need to serialize it better
1054+
cause: Object.fromEntries(e.cause),
1055+
code: e.code,
1056+
error: e.error,
1057+
});
1058+
}
1059+
throw new StackAssertionError("Unexpected error when validating outer OAuth response", { cause: e });
10331060
}
10341061
const response = await oauth.authorizationCodeGrantRequest(
10351062
as,
10361063
client,
1064+
clientAuthentication,
10371065
params,
10381066
options.redirectUri,
10391067
options.codeVerifier,
1068+
allowInsecure ? { [oauth.allowInsecureRequests]: true } : undefined,
10401069
);
10411070

1042-
const result = await oauth.processAuthorizationCodeOAuth2Response(as, client, response);
1043-
if (oauth.isOAuth2Error(result)) {
1044-
if ("code" in result && result.code === "MULTI_FACTOR_AUTHENTICATION_REQUIRED") {
1045-
throw new KnownErrors.MultiFactorAuthenticationRequired((result as any).details.attempt_code);
1071+
let result;
1072+
try {
1073+
result = await oauth.processAuthorizationCodeResponse(as, client, response);
1074+
} catch (e) {
1075+
if (e instanceof oauth.ResponseBodyError) {
1076+
if ((e.cause as any).code === "MULTI_FACTOR_AUTHENTICATION_REQUIRED") {
1077+
throw new KnownErrors.MultiFactorAuthenticationRequired((e.cause as any).details.attempt_code);
1078+
}
1079+
// TODO Handle OAuth 2.0 response body error
1080+
throw new StackAssertionError("Outer OAuth error during authorization code response", {
1081+
cause: e.cause,
1082+
code: e.code,
1083+
error: e.error,
1084+
});
10461085
}
1047-
// TODO Handle OAuth 2.0 response body error
1048-
throw new StackAssertionError("Outer OAuth error during authorization code response", { result });
1086+
throw new StackAssertionError("Unexpected error when processing authorization code response", { cause: e });
10491087
}
10501088
return {
10511089
newUser: result.is_new_user as boolean,

packages/stack-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"@types/react-dom": ">=19.0.0",
3030
"react": ">=19.0.0",
3131
"react-dom": ">=19.0.0",
32-
"yup": "^1.4.0"
32+
"yup": "^1.7.1"
3333
},
3434
"peerDependenciesMeta": {
3535
"@types/react": {

0 commit comments

Comments
 (0)