@stackframe/stack-shared 2.6.33 → 2.6.34

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.6.34
4
+
5
+ ### Patch Changes
6
+
7
+ - Bugfixes
8
+ - @stackframe/stack-sc@2.6.34
9
+
3
10
  ## 2.6.33
4
11
 
5
12
  ### Patch Changes
@@ -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
- protected sendClientRequest(path: string, requestOptions: RequestInit, session: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<Response & {
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
  }
@@ -146,5 +146,7 @@ export declare const contactChannelValueSchema: yup.StringSchema<string | undefi
146
146
  export declare const contactChannelUsedForAuthSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
147
147
  export declare const contactChannelIsVerifiedSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
148
148
  export declare const contactChannelIsPrimarySchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
149
+ export declare const basicAuthorizationHeaderSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
150
+ export declare const neonAuthorizationHeaderSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
149
151
  export declare function yupDefinedWhen<S extends yup.AnyObject>(schema: S, triggerName: string, isValue: any): S;
150
152
  export {};
@@ -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: 'Invalid URL',
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) => {
@@ -360,6 +352,23 @@ export const contactChannelValueSchema = yupString().when('type', {
360
352
  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
353
  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
354
  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 } });
355
+ // Headers
356
+ export const basicAuthorizationHeaderSchema = yupString().test('is-basic-authorization-header', 'Authorization header must be in the format "Basic <base64>"', (value) => {
357
+ if (!value)
358
+ return true;
359
+ return decodeBasicAuthorizationHeader(value) !== null;
360
+ });
361
+ // Neon integration
362
+ 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) => {
363
+ if (!value)
364
+ return true;
365
+ const [clientId, clientSecret] = decodeBasicAuthorizationHeader(value) ?? throwErr(`Neon authz header invalid? This should've been validated by basicAuthorizationHeaderSchema: ${value}`);
366
+ for (const neonClientConfig of JSON.parse(process.env.STACK_NEON_INTEGRATION_CLIENTS_CONFIG || '[]')) {
367
+ if (clientId === neonClientConfig.client_id && clientSecret === neonClientConfig.client_secret)
368
+ return true;
369
+ }
370
+ return false;
371
+ });
363
372
  // Utils
364
373
  export function yupDefinedWhen(schema, triggerName, isValue) {
365
374
  return schema.when(triggerName, {
@@ -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;
@@ -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
+ }
@@ -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;
@@ -7,6 +7,9 @@ export function createUrlIfValid(...args) {
7
7
  return null;
8
8
  }
9
9
  }
10
+ export function isValidUrl(url) {
11
+ return !!createUrlIfValid(url);
12
+ }
10
13
  export function isLocalhost(urlOrString) {
11
14
  const url = createUrlIfValid(urlOrString);
12
15
  if (!url)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.6.33",
3
+ "version": "2.6.34",
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.33"
53
+ "@stackframe/stack-sc": "2.6.34"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@simplewebauthn/types": "^11.0.0",