@stackframe/stack-shared 2.6.33 → 2.6.36
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/CHANGELOG.md +21 -0
- package/dist/helpers/production-mode.js +7 -2
- package/dist/interface/clientInterface.d.ts +1 -1
- package/dist/interface/clientInterface.js +2 -2
- package/dist/known-errors.js +2 -2
- package/dist/schema-fields.d.ts +7 -4
- package/dist/schema-fields.js +28 -18
- package/dist/sessions.d.ts +5 -2
- package/dist/sessions.js +9 -2
- package/dist/utils/browser-compat.d.ts +6 -0
- package/dist/utils/browser-compat.js +17 -0
- package/dist/utils/errors.d.ts +5 -11
- package/dist/utils/errors.js +11 -10
- package/dist/utils/http.d.ts +2 -0
- package/dist/utils/http.js +20 -0
- package/dist/utils/jwt.js +4 -0
- package/dist/utils/results.d.ts +1 -1
- package/dist/utils/results.js +3 -3
- package/dist/utils/sentry.d.ts +2 -0
- package/dist/utils/sentry.js +15 -0
- package/dist/utils/urls.d.ts +1 -0
- package/dist/utils/urls.js +3 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @stackframe/stack-shared
|
|
2
2
|
|
|
3
|
+
## 2.6.36
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Various updates
|
|
8
|
+
- @stackframe/stack-sc@2.6.36
|
|
9
|
+
|
|
10
|
+
## 2.6.35
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Bugfixes
|
|
15
|
+
- @stackframe/stack-sc@2.6.35
|
|
16
|
+
|
|
17
|
+
## 2.6.34
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Bugfixes
|
|
22
|
+
- @stackframe/stack-sc@2.6.34
|
|
23
|
+
|
|
3
24
|
## 2.6.33
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StackAssertionError } from "../utils/errors";
|
|
1
|
+
import { StackAssertionError, captureError } from "../utils/errors";
|
|
2
2
|
import { isLocalhost } from "../utils/urls";
|
|
3
3
|
export function getProductionModeErrors(project) {
|
|
4
4
|
const errors = [];
|
|
@@ -15,10 +15,15 @@ export function getProductionModeErrors(project) {
|
|
|
15
15
|
url = new URL(domain);
|
|
16
16
|
}
|
|
17
17
|
catch (e) {
|
|
18
|
-
|
|
18
|
+
captureError("production-mode-domain-not-valid", new StackAssertionError("Domain was somehow not a valid URL; we should've caught this when setting the domain in the first place", {
|
|
19
19
|
domain,
|
|
20
20
|
projectId: project
|
|
21
|
+
}));
|
|
22
|
+
errors.push({
|
|
23
|
+
message: "Trusted domain is not a valid URL: " + domain,
|
|
24
|
+
relativeFixUrl: domainsFixUrl,
|
|
21
25
|
});
|
|
26
|
+
continue;
|
|
22
27
|
}
|
|
23
28
|
if (isLocalhost(url)) {
|
|
24
29
|
errors.push({
|
|
@@ -37,7 +37,7 @@ export declare class StackClientInterface {
|
|
|
37
37
|
protected _networkRetry<T>(cb: () => Promise<Result<T, any>>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
|
|
38
38
|
protected _networkRetryException<T>(cb: () => Promise<T>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
|
|
39
39
|
fetchNewAccessToken(refreshToken: RefreshToken): Promise<AccessToken | null>;
|
|
40
|
-
|
|
40
|
+
sendClientRequest(path: string, requestOptions: RequestInit, session: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<Response & {
|
|
41
41
|
usedTokens: {
|
|
42
42
|
accessToken: AccessToken;
|
|
43
43
|
refreshToken: RefreshToken | null;
|
|
@@ -88,7 +88,7 @@ export class StackClientInterface {
|
|
|
88
88
|
if (globalVar.navigator && !globalVar.navigator.onLine) {
|
|
89
89
|
throw new Error("Failed to send Stack network request. It seems like you are offline. (window.navigator.onLine is falsy)", { cause: retriedResult.error });
|
|
90
90
|
}
|
|
91
|
-
throw this._createNetworkError(retriedResult.error, session, requestType);
|
|
91
|
+
throw await this._createNetworkError(retriedResult.error, session, requestType);
|
|
92
92
|
}
|
|
93
93
|
return retriedResult.data;
|
|
94
94
|
}
|
|
@@ -230,7 +230,7 @@ export class StackClientInterface {
|
|
|
230
230
|
return Result.error(e);
|
|
231
231
|
}
|
|
232
232
|
else {
|
|
233
|
-
throw this._createNetworkError(e, session, requestType);
|
|
233
|
+
throw await this._createNetworkError(e, session, requestType);
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
throw e;
|
package/dist/known-errors.js
CHANGED
|
@@ -149,7 +149,7 @@ const AccessTypeWithoutProjectId = createKnownErrorConstructor(InvalidProjectAut
|
|
|
149
149
|
deindent `
|
|
150
150
|
The x-stack-access-type header was '${accessType}', but the x-stack-project-id header was not provided.
|
|
151
151
|
|
|
152
|
-
For more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/
|
|
152
|
+
For more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/overview#authentication
|
|
153
153
|
`,
|
|
154
154
|
{
|
|
155
155
|
request_type: accessType,
|
|
@@ -160,7 +160,7 @@ const AccessTypeRequired = createKnownErrorConstructor(InvalidProjectAuthenticat
|
|
|
160
160
|
deindent `
|
|
161
161
|
You must specify an access level for this Stack project. Make sure project API keys are provided (eg. x-stack-publishable-client-key) and you set the x-stack-access-type header to 'client', 'server', or 'admin'.
|
|
162
162
|
|
|
163
|
-
For more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/
|
|
163
|
+
For more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/overview#authentication
|
|
164
164
|
`,
|
|
165
165
|
], () => []);
|
|
166
166
|
const InsufficientAccessType = createKnownErrorConstructor(InvalidProjectAuthentication, "INSUFFICIENT_ACCESS_TYPE", (actualAccessType, allowedAccessTypes) => [
|
package/dist/schema-fields.d.ts
CHANGED
|
@@ -33,16 +33,17 @@ export declare const jsonStringOrEmptySchema: yup.StringSchema<string | undefine
|
|
|
33
33
|
export declare const base64Schema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
34
34
|
export declare const passwordSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
35
35
|
/**
|
|
36
|
-
* A stricter email schema that does some additional checks for UX input.
|
|
36
|
+
* A stricter email schema that does some additional checks for UX input. (Some emails are allowed by the spec, for
|
|
37
|
+
* example `test@localhost` or `abc@gmail`, but almost certainly a user input error.)
|
|
37
38
|
*
|
|
38
39
|
* Note that some users in the DB have an email that doesn't match this regex, so most of the time you should use
|
|
39
40
|
* `emailSchema` instead until we do the DB migration.
|
|
40
41
|
*/
|
|
41
42
|
export declare const strictEmailSchema: (message: string | undefined) => yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
42
43
|
export declare const emailSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
43
|
-
export declare const clientOrHigherAuthTypeSchema: yup.StringSchema<"client" | "server" | "admin"
|
|
44
|
-
export declare const serverOrHigherAuthTypeSchema: yup.StringSchema<"server" | "admin"
|
|
45
|
-
export declare const adminAuthTypeSchema: yup.StringSchema<"admin"
|
|
44
|
+
export declare const clientOrHigherAuthTypeSchema: yup.StringSchema<"client" | "server" | "admin", yup.AnyObject, undefined, "">;
|
|
45
|
+
export declare const serverOrHigherAuthTypeSchema: yup.StringSchema<"server" | "admin", yup.AnyObject, undefined, "">;
|
|
46
|
+
export declare const adminAuthTypeSchema: yup.StringSchema<"admin", yup.AnyObject, undefined, "">;
|
|
46
47
|
export declare const projectIdSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
47
48
|
export declare const projectDisplayNameSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
48
49
|
export declare const projectDescriptionSchema: yup.StringSchema<string | null | undefined, yup.AnyObject, undefined, "">;
|
|
@@ -146,5 +147,7 @@ export declare const contactChannelValueSchema: yup.StringSchema<string | undefi
|
|
|
146
147
|
export declare const contactChannelUsedForAuthSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
|
|
147
148
|
export declare const contactChannelIsVerifiedSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
|
|
148
149
|
export declare const contactChannelIsPrimarySchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
|
|
150
|
+
export declare const basicAuthorizationHeaderSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
151
|
+
export declare const neonAuthorizationHeaderSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
|
|
149
152
|
export declare function yupDefinedWhen<S extends yup.AnyObject>(schema: S, triggerName: string, isValue: any): S;
|
|
150
153
|
export {};
|
package/dist/schema-fields.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as yup from "yup";
|
|
2
2
|
import { KnownErrors } from ".";
|
|
3
3
|
import { isBase64 } from "./utils/bytes";
|
|
4
|
-
import { StackAssertionError } from "./utils/errors";
|
|
4
|
+
import { StackAssertionError, throwErr } from "./utils/errors";
|
|
5
|
+
import { decodeBasicAuthorizationHeader } from "./utils/http";
|
|
5
6
|
import { allProviders } from "./utils/oauth";
|
|
6
7
|
import { deepPlainClone, omit } from "./utils/objects";
|
|
8
|
+
import { isValidUrl } from "./utils/urls";
|
|
7
9
|
import { isUuid } from "./utils/uuids";
|
|
8
10
|
// eslint-disable-next-line no-restricted-syntax
|
|
9
11
|
yup.addMethod(yup.string, "nonEmpty", function (message) {
|
|
@@ -160,18 +162,8 @@ export const adaptSchema = yupMixed();
|
|
|
160
162
|
*/
|
|
161
163
|
export const urlSchema = yupString().test({
|
|
162
164
|
name: 'url',
|
|
163
|
-
message:
|
|
164
|
-
test: (value) =>
|
|
165
|
-
if (!value)
|
|
166
|
-
return true;
|
|
167
|
-
try {
|
|
168
|
-
new URL(value);
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
},
|
|
165
|
+
message: (params) => `${params.path} is not a valid URL`,
|
|
166
|
+
test: (value) => value == null || isValidUrl(value)
|
|
175
167
|
});
|
|
176
168
|
export const jsonSchema = yupMixed().nullable().defined().transform((value) => JSON.parse(JSON.stringify(value)));
|
|
177
169
|
export const jsonStringSchema = yupString().test("json", (params) => `${params.path} is not valid JSON`, (value) => {
|
|
@@ -203,19 +195,20 @@ export const base64Schema = yupString().test("is-base64", (params) => `${params.
|
|
|
203
195
|
});
|
|
204
196
|
export const passwordSchema = yupString().max(70);
|
|
205
197
|
/**
|
|
206
|
-
* A stricter email schema that does some additional checks for UX input.
|
|
198
|
+
* A stricter email schema that does some additional checks for UX input. (Some emails are allowed by the spec, for
|
|
199
|
+
* example `test@localhost` or `abc@gmail`, but almost certainly a user input error.)
|
|
207
200
|
*
|
|
208
201
|
* Note that some users in the DB have an email that doesn't match this regex, so most of the time you should use
|
|
209
202
|
* `emailSchema` instead until we do the DB migration.
|
|
210
203
|
*/
|
|
211
204
|
// eslint-disable-next-line no-restricted-syntax
|
|
212
|
-
export const strictEmailSchema = (message) => yupString().email(message).matches(
|
|
205
|
+
export const strictEmailSchema = (message) => yupString().email(message).matches(/^.*@.*\.[^.][^.]+$/, message);
|
|
213
206
|
// eslint-disable-next-line no-restricted-syntax
|
|
214
207
|
export const emailSchema = yupString().email();
|
|
215
208
|
// Request auth
|
|
216
|
-
export const clientOrHigherAuthTypeSchema = yupString().oneOf(['client', 'server', 'admin']);
|
|
217
|
-
export const serverOrHigherAuthTypeSchema = yupString().oneOf(['server', 'admin']);
|
|
218
|
-
export const adminAuthTypeSchema = yupString().oneOf(['admin']);
|
|
209
|
+
export const clientOrHigherAuthTypeSchema = yupString().oneOf(['client', 'server', 'admin']).defined();
|
|
210
|
+
export const serverOrHigherAuthTypeSchema = yupString().oneOf(['server', 'admin']).defined();
|
|
211
|
+
export const adminAuthTypeSchema = yupString().oneOf(['admin']).defined();
|
|
219
212
|
// Projects
|
|
220
213
|
export const projectIdSchema = yupString().test((v) => v === undefined || v === "internal" || isUuid(v)).meta({ openapiField: { description: _idDescription('project'), exampleValue: 'e0b52f4d-dece-408c-af49-d23061bb0f8d' } });
|
|
221
214
|
export const projectDisplayNameSchema = yupString().meta({ openapiField: { description: _displayNameDescription('project'), exampleValue: 'MyMusic' } });
|
|
@@ -360,6 +353,23 @@ export const contactChannelValueSchema = yupString().when('type', {
|
|
|
360
353
|
export const contactChannelUsedForAuthSchema = yupBoolean().meta({ openapiField: { description: 'Whether the contact channel is used for authentication. If this is set to `true`, the user will be able to sign in with the contact channel with password or OTP.', exampleValue: true } });
|
|
361
354
|
export const contactChannelIsVerifiedSchema = yupBoolean().meta({ openapiField: { description: 'Whether the contact channel has been verified. If this is set to `true`, the contact channel has been verified to belong to the user.', exampleValue: true } });
|
|
362
355
|
export const contactChannelIsPrimarySchema = yupBoolean().meta({ openapiField: { description: 'Whether the contact channel is the primary contact channel. If this is set to `true`, it will be used for authentication and notifications by default.', exampleValue: true } });
|
|
356
|
+
// Headers
|
|
357
|
+
export const basicAuthorizationHeaderSchema = yupString().test('is-basic-authorization-header', 'Authorization header must be in the format "Basic <base64>"', (value) => {
|
|
358
|
+
if (!value)
|
|
359
|
+
return true;
|
|
360
|
+
return decodeBasicAuthorizationHeader(value) !== null;
|
|
361
|
+
});
|
|
362
|
+
// Neon integration
|
|
363
|
+
export const neonAuthorizationHeaderSchema = basicAuthorizationHeaderSchema.test('is-neon-authorization-header', 'Invalid client_id:client_secret values; did you use the correct values for the Neon integration?', (value) => {
|
|
364
|
+
if (!value)
|
|
365
|
+
return true;
|
|
366
|
+
const [clientId, clientSecret] = decodeBasicAuthorizationHeader(value) ?? throwErr(`Neon authz header invalid? This should've been validated by basicAuthorizationHeaderSchema: ${value}`);
|
|
367
|
+
for (const neonClientConfig of JSON.parse(process.env.STACK_NEON_INTEGRATION_CLIENTS_CONFIG || '[]')) {
|
|
368
|
+
if (clientId === neonClientConfig.client_id && clientSecret === neonClientConfig.client_secret)
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
});
|
|
363
373
|
// Utils
|
|
364
374
|
export function yupDefinedWhen(schema, triggerName, isValue) {
|
|
365
375
|
return schema.when(triggerName, {
|
package/dist/sessions.d.ts
CHANGED
|
@@ -27,9 +27,12 @@ export declare class InternalSession {
|
|
|
27
27
|
private _accessToken;
|
|
28
28
|
private readonly _refreshToken;
|
|
29
29
|
/**
|
|
30
|
-
* Whether the session as a whole is known to be invalid. Used as a cache to avoid making multiple requests to the server (sessions never go back to being valid after being invalidated).
|
|
30
|
+
* Whether the session as a whole is known to be invalid (ie. both access and refresh tokens are invalid). Used as a cache to avoid making multiple requests to the server (sessions never go back to being valid after being invalidated).
|
|
31
31
|
*
|
|
32
|
-
*
|
|
32
|
+
* It is possible for the access token to be invalid but the refresh token to be valid, in which case the session is
|
|
33
|
+
* still valid (just needs a refresh). It is also possible for the access token to be valid but the refresh token to
|
|
34
|
+
* be invalid, in which case the session is also valid (eg. if the refresh token is null because the user only passed
|
|
35
|
+
* in an access token, eg. in a server-side request handler).
|
|
33
36
|
*/
|
|
34
37
|
private _knownToBeInvalid;
|
|
35
38
|
private _refreshPromise;
|
package/dist/sessions.js
CHANGED
|
@@ -25,14 +25,21 @@ export class InternalSession {
|
|
|
25
25
|
constructor(_options) {
|
|
26
26
|
this._options = _options;
|
|
27
27
|
/**
|
|
28
|
-
* Whether the session as a whole is known to be invalid. Used as a cache to avoid making multiple requests to the server (sessions never go back to being valid after being invalidated).
|
|
28
|
+
* Whether the session as a whole is known to be invalid (ie. both access and refresh tokens are invalid). Used as a cache to avoid making multiple requests to the server (sessions never go back to being valid after being invalidated).
|
|
29
29
|
*
|
|
30
|
-
*
|
|
30
|
+
* It is possible for the access token to be invalid but the refresh token to be valid, in which case the session is
|
|
31
|
+
* still valid (just needs a refresh). It is also possible for the access token to be valid but the refresh token to
|
|
32
|
+
* be invalid, in which case the session is also valid (eg. if the refresh token is null because the user only passed
|
|
33
|
+
* in an access token, eg. in a server-side request handler).
|
|
31
34
|
*/
|
|
32
35
|
this._knownToBeInvalid = new Store(false);
|
|
33
36
|
this._refreshPromise = null;
|
|
34
37
|
this._accessToken = new Store(_options.accessToken ? new AccessToken(_options.accessToken) : null);
|
|
35
38
|
this._refreshToken = _options.refreshToken ? new RefreshToken(_options.refreshToken) : null;
|
|
39
|
+
if (_options.accessToken === null && _options.refreshToken === null) {
|
|
40
|
+
// this session is already invalid
|
|
41
|
+
this._knownToBeInvalid.set(true);
|
|
42
|
+
}
|
|
36
43
|
this.sessionKey = InternalSession.calculateSessionKey({ accessToken: _options.accessToken ?? null, refreshToken: _options.refreshToken });
|
|
37
44
|
}
|
|
38
45
|
static calculateSessionKey(ofTokens) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function getBrowserCompatibilityReport() {
|
|
2
|
+
const test = (snippet) => {
|
|
3
|
+
try {
|
|
4
|
+
(0, eval)(snippet);
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch (e) {
|
|
8
|
+
return `FAILED: ${e}`;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
return {
|
|
12
|
+
optionalChaining: test("({})?.b?.c"),
|
|
13
|
+
nullishCoalescing: test("0 ?? 1"),
|
|
14
|
+
weakRef: test("new WeakRef({})"),
|
|
15
|
+
cryptoUuid: test("crypto.randomUUID()"),
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/utils/errors.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ export declare function throwErr(errorMessage: string, extraData?: any): never;
|
|
|
3
3
|
export declare function throwErr(error: Error): never;
|
|
4
4
|
export declare function throwErr(...args: StatusErrorConstructorParameters): never;
|
|
5
5
|
/**
|
|
6
|
-
* Concatenates the stacktraces of the given errors onto the first.
|
|
6
|
+
* Concatenates the (original) stacktraces of the given errors onto the first.
|
|
7
7
|
*
|
|
8
8
|
* Useful when you invoke an async function to receive a promise without awaiting it immediately. Browsers are smart
|
|
9
9
|
* enough to keep track of the call stack in async function calls when you invoke `.then` within the same async tick,
|
|
10
|
-
* but if you don't,
|
|
10
|
+
* but if you don't, the stacktrace will be lost.
|
|
11
11
|
*
|
|
12
12
|
* Here's an example of the unwanted behavior:
|
|
13
13
|
*
|
|
@@ -24,16 +24,10 @@ export declare function throwErr(...args: StatusErrorConstructorParameters): nev
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
export declare function concatStacktraces(first: Error, ...errors: Error[]): void;
|
|
27
|
-
export declare class StackAssertionError extends Error
|
|
28
|
-
readonly extraData?: Record<string, any> | undefined;
|
|
29
|
-
constructor(message: string, extraData?: Record<string, any> | undefined
|
|
30
|
-
customCaptureExtraArgs: {
|
|
31
|
-
cause?: {} | undefined;
|
|
32
|
-
}[];
|
|
27
|
+
export declare class StackAssertionError extends Error {
|
|
28
|
+
readonly extraData?: (Record<string, any> & ErrorOptions) | undefined;
|
|
29
|
+
constructor(message: string, extraData?: (Record<string, any> & ErrorOptions) | undefined);
|
|
33
30
|
}
|
|
34
|
-
export type ErrorWithCustomCapture = {
|
|
35
|
-
customCaptureExtraArgs: any[];
|
|
36
|
-
};
|
|
37
31
|
export declare function registerErrorSink(sink: (location: string, error: unknown) => void): void;
|
|
38
32
|
export declare function captureError(location: string, error: unknown): void;
|
|
39
33
|
type Status = {
|
package/dist/utils/errors.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { globalVar } from "./globals";
|
|
2
|
+
import { pick } from "./objects";
|
|
2
3
|
export function throwErr(...args) {
|
|
3
4
|
if (typeof args[0] === "string") {
|
|
4
5
|
throw new StackAssertionError(args[0], args[1]);
|
|
@@ -17,11 +18,11 @@ function removeStacktraceNameLine(stack) {
|
|
|
17
18
|
return stack.split("\n").slice(addsNameLine ? 1 : 0).join("\n");
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
|
-
* Concatenates the stacktraces of the given errors onto the first.
|
|
21
|
+
* Concatenates the (original) stacktraces of the given errors onto the first.
|
|
21
22
|
*
|
|
22
23
|
* Useful when you invoke an async function to receive a promise without awaiting it immediately. Browsers are smart
|
|
23
24
|
* enough to keep track of the call stack in async function calls when you invoke `.then` within the same async tick,
|
|
24
|
-
* but if you don't,
|
|
25
|
+
* but if you don't, the stacktrace will be lost.
|
|
25
26
|
*
|
|
26
27
|
* Here's an example of the unwanted behavior:
|
|
27
28
|
*
|
|
@@ -50,16 +51,16 @@ export function concatStacktraces(first, ...errors) {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
export class StackAssertionError extends Error {
|
|
53
|
-
constructor(message, extraData
|
|
54
|
+
constructor(message, extraData) {
|
|
54
55
|
const disclaimer = `\n\nThis is likely an error in Stack. Please make sure you are running the newest version and report it.`;
|
|
55
|
-
super(`${message}${message.endsWith(disclaimer) ? "" : disclaimer}`,
|
|
56
|
+
super(`${message}${message.endsWith(disclaimer) ? "" : disclaimer}`, pick(extraData ?? {}, ["cause"]));
|
|
56
57
|
this.extraData = extraData;
|
|
57
|
-
this
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
...this.cause ? { cause: this.cause } : {},
|
|
58
|
+
Object.defineProperty(this, "customCaptureExtraArgs", {
|
|
59
|
+
get() {
|
|
60
|
+
return [this.extraData];
|
|
61
61
|
},
|
|
62
|
-
|
|
62
|
+
enumerable: false,
|
|
63
|
+
});
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
StackAssertionError.prototype.name = "StackAssertionError";
|
|
@@ -92,7 +93,7 @@ export class StatusError extends Error {
|
|
|
92
93
|
this.name = "StatusError";
|
|
93
94
|
this.statusCode = status;
|
|
94
95
|
if (!message) {
|
|
95
|
-
throw new StackAssertionError("StatusError always requires a message unless a Status object is passed", {
|
|
96
|
+
throw new StackAssertionError("StatusError always requires a message unless a Status object is passed", { cause: this });
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
isClientError() {
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -37,3 +37,5 @@ export declare const HTTP_METHODS: {
|
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
39
|
export type HttpMethod = keyof typeof HTTP_METHODS;
|
|
40
|
+
export declare function decodeBasicAuthorizationHeader(value: string): [string, string] | null;
|
|
41
|
+
export declare function encodeBasicAuthorizationHeader(id: string, password: string): string;
|
package/dist/utils/http.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeBase64, encodeBase64, isBase64 } from "./bytes";
|
|
1
2
|
export const HTTP_METHODS = {
|
|
2
3
|
"GET": {
|
|
3
4
|
safe: true,
|
|
@@ -36,3 +37,22 @@ export const HTTP_METHODS = {
|
|
|
36
37
|
idempotent: false,
|
|
37
38
|
},
|
|
38
39
|
};
|
|
40
|
+
export function decodeBasicAuthorizationHeader(value) {
|
|
41
|
+
const [type, encoded, ...rest] = value.split(' ');
|
|
42
|
+
if (rest.length > 0)
|
|
43
|
+
return null;
|
|
44
|
+
if (!encoded)
|
|
45
|
+
return null;
|
|
46
|
+
if (type !== 'Basic')
|
|
47
|
+
return null;
|
|
48
|
+
if (!isBase64(encoded))
|
|
49
|
+
return null;
|
|
50
|
+
const decoded = new TextDecoder().decode(decodeBase64(encoded));
|
|
51
|
+
const split = decoded.split(':');
|
|
52
|
+
return [split[0], split.slice(1).join(':')];
|
|
53
|
+
}
|
|
54
|
+
export function encodeBasicAuthorizationHeader(id, password) {
|
|
55
|
+
if (id.includes(':'))
|
|
56
|
+
throw new Error("Basic authorization header id cannot contain ':'");
|
|
57
|
+
return `Basic ${encodeBase64(new TextEncoder().encode(`${id}:${password}`))}`;
|
|
58
|
+
}
|
package/dist/utils/jwt.js
CHANGED
|
@@ -3,6 +3,7 @@ import elliptic from "elliptic";
|
|
|
3
3
|
import * as jose from "jose";
|
|
4
4
|
import { JOSEError } from "jose/errors";
|
|
5
5
|
import { encodeBase64Url } from "./bytes";
|
|
6
|
+
import { StackAssertionError } from "./errors";
|
|
6
7
|
import { globalVar } from "./globals";
|
|
7
8
|
import { pick } from "./objects";
|
|
8
9
|
const STACK_SERVER_SECRET = process.env.STACK_SERVER_SECRET ?? "";
|
|
@@ -74,6 +75,9 @@ export async function getPublicJwkSet(secretOrPrivateJwk) {
|
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
export function getPerAudienceSecret(options) {
|
|
78
|
+
if (options.audience === "kid") {
|
|
79
|
+
throw new StackAssertionError("You cannot use the 'kid' audience for a per-audience secret, see comment below in jwt.tsx");
|
|
80
|
+
}
|
|
77
81
|
return jose.base64url.encode(crypto
|
|
78
82
|
.createHash('sha256')
|
|
79
83
|
// TODO we should prefix a string like "stack-audience-secret" before we hash so you can't use `getKid(...)` to get the secret for eg. the "kid" audience if the same secret value is used
|
package/dist/utils/results.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ declare class RetryError extends AggregateError {
|
|
|
69
69
|
constructor(errors: unknown[]);
|
|
70
70
|
get retries(): number;
|
|
71
71
|
}
|
|
72
|
-
declare function retry<T>(fn: () => Result<T> | Promise<Result<T>>, retries: number, { exponentialDelayBase }
|
|
72
|
+
declare function retry<T>(fn: (attempt: number) => Result<T> | Promise<Result<T>>, retries: number, { exponentialDelayBase }?: {
|
|
73
73
|
exponentialDelayBase?: number | undefined;
|
|
74
74
|
}): Promise<Result<T, RetryError>>;
|
|
75
75
|
export {};
|
package/dist/utils/results.js
CHANGED
|
@@ -116,17 +116,17 @@ class RetryError extends AggregateError {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
RetryError.prototype.name = "RetryError";
|
|
119
|
-
async function retry(fn, retries, { exponentialDelayBase =
|
|
119
|
+
async function retry(fn, retries, { exponentialDelayBase = 1000 } = {}) {
|
|
120
120
|
const errors = [];
|
|
121
121
|
for (let i = 0; i < retries; i++) {
|
|
122
|
-
const res = await fn();
|
|
122
|
+
const res = await fn(i);
|
|
123
123
|
if (res.status === "ok") {
|
|
124
124
|
return Result.ok(res.data);
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
127
|
errors.push(res.error);
|
|
128
128
|
if (i < retries - 1) {
|
|
129
|
-
await wait(Math.random() * exponentialDelayBase * 2 ** i);
|
|
129
|
+
await wait((Math.random() + 0.5) * exponentialDelayBase * (2 ** i));
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const sentryBaseConfig = {
|
|
2
|
+
ignoreErrors: [
|
|
3
|
+
// React throws these errors when used with some browser extensions (eg. Google Translate)
|
|
4
|
+
"NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.",
|
|
5
|
+
"NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.",
|
|
6
|
+
],
|
|
7
|
+
normalizeDepth: 5,
|
|
8
|
+
maxValueLength: 5000,
|
|
9
|
+
// Adjust this value in production, or use tracesSampler for greater control
|
|
10
|
+
tracesSampleRate: 1.0,
|
|
11
|
+
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
|
12
|
+
debug: false,
|
|
13
|
+
replaysOnErrorSampleRate: 1.0,
|
|
14
|
+
replaysSessionSampleRate: 1.0,
|
|
15
|
+
};
|
package/dist/utils/urls.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function createUrlIfValid(...args: ConstructorParameters<typeof URL>): URL | null;
|
|
2
|
+
export declare function isValidUrl(url: string): boolean;
|
|
2
3
|
export declare function isLocalhost(urlOrString: string | URL): boolean;
|
|
3
4
|
export declare function isRelative(url: string): boolean;
|
|
4
5
|
export declare function getRelativePart(url: URL): string;
|
package/dist/utils/urls.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackframe/stack-shared",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.36",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"oauth4webapi": "^2.10.3",
|
|
51
51
|
"semver": "^7.6.3",
|
|
52
52
|
"uuid": "^9.0.1",
|
|
53
|
-
"@stackframe/stack-sc": "2.6.
|
|
53
|
+
"@stackframe/stack-sc": "2.6.36"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@simplewebauthn/types": "^11.0.0",
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
"rimraf": "^5.0.5",
|
|
62
62
|
"react": "^18.2",
|
|
63
63
|
"react-dom": "^18.2",
|
|
64
|
-
"next": "^14.1.0"
|
|
64
|
+
"next": "^14.1.0",
|
|
65
|
+
"@sentry/nextjs": "^8.40.0"
|
|
65
66
|
},
|
|
66
67
|
"scripts": {
|
|
67
68
|
"build": "tsc",
|