@stackframe/stack-shared 2.6.30 → 2.6.31

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.31
4
+
5
+ ### Patch Changes
6
+
7
+ - Bugfixes
8
+ - @stackframe/stack-sc@2.6.31
9
+
3
10
  ## 2.6.30
4
11
 
5
12
  ### Patch Changes
@@ -30,7 +30,7 @@ export class StackAdminInterface extends StackServerInterface {
30
30
  return await response.json();
31
31
  }
32
32
  async createApiKey(options) {
33
- const response = await this.sendServerRequest("/internal/api-keys", {
33
+ const response = await this.sendAdminRequest("/internal/api-keys", {
34
34
  method: "POST",
35
35
  headers: {
36
36
  "content-type": "application/json",
@@ -186,6 +186,7 @@ export declare class StackClientInterface {
186
186
  createProject(project: InternalProjectsCrud['Client']['Create'], session: InternalSession): Promise<InternalProjectsCrud['Client']['Read']>;
187
187
  createProviderAccessToken(provider: string, scope: string, session: InternalSession): Promise<ConnectedAccountAccessTokenCrud['Client']['Read']>;
188
188
  createClientTeam(data: TeamsCrud['Client']['Create'], session: InternalSession): Promise<TeamsCrud['Client']['Read']>;
189
+ deleteTeam(teamId: string, session: InternalSession): Promise<void>;
189
190
  deleteCurrentUser(session: InternalSession): Promise<void>;
190
191
  createClientContactChannel(data: ContactChannelsCrud['Client']['Create'], session: InternalSession): Promise<ContactChannelsCrud['Client']['Read']>;
191
192
  updateClientContactChannel(id: string, data: ContactChannelsCrud['Client']['Update'], session: InternalSession): Promise<ContactChannelsCrud['Client']['Read']>;
@@ -812,6 +812,11 @@ export class StackClientInterface {
812
812
  }, session);
813
813
  return await response.json();
814
814
  }
815
+ async deleteTeam(teamId, session) {
816
+ await this.sendClientRequest(`/teams/${teamId}`, {
817
+ method: "DELETE",
818
+ }, session);
819
+ }
815
820
  async deleteCurrentUser(session) {
816
821
  await this.sendClientRequest("/users/me", {
817
822
  method: "DELETE",
@@ -20,6 +20,7 @@ export declare function yupMixed<A extends {}>(...args: Parameters<typeof yup.mi
20
20
  export declare function yupArray<A extends yup.Maybe<yup.AnyObject> = yup.AnyObject, B = any>(...args: Parameters<typeof yup.array<A, B>>): yup.ArraySchema<B[] | undefined, A, undefined, "">;
21
21
  export declare function yupTuple<T extends [unknown, ...unknown[]]>(...args: Parameters<typeof yup.tuple<T>>): yup.TupleSchema<T | undefined, yup.AnyObject, undefined, "">;
22
22
  export declare function yupObject<A extends yup.Maybe<yup.AnyObject>, B extends yup.ObjectShape>(...args: Parameters<typeof yup.object<A, B>>): yup.ObjectSchema<yup.TypeFromShape<B, yup.AnyObject> extends infer T ? T extends yup.TypeFromShape<B, yup.AnyObject> ? T extends {} ? { [k in keyof T]: T[k]; } : T : never : never, yup.AnyObject, yup.DefaultFromShape<B> extends infer T_1 ? T_1 extends yup.DefaultFromShape<B> ? T_1 extends {} ? { [k_1 in keyof T_1]: T_1[k_1]; } : T_1 : never : never, "">;
23
+ export declare function yupNever(): yup.MixedSchema<never>;
23
24
  export declare function yupUnion<T extends yup.ISchema<any>[]>(...args: T): yup.MixedSchema<yup.InferType<T[number]>>;
24
25
  export declare const adaptSchema: yup.MixedSchema<typeof StackAdaptSentinel | undefined, yup.AnyObject, undefined, "">;
25
26
  /**
@@ -123,6 +123,9 @@ export function yupObject(...args) {
123
123
  // we don't want to update the type of `object` to have a default flag
124
124
  return object.default(undefined);
125
125
  }
126
+ export function yupNever() {
127
+ return yupMixed().test('never', 'This value should never be reached', () => false);
128
+ }
126
129
  export function yupUnion(...args) {
127
130
  if (args.length === 0)
128
131
  throw new Error('yupUnion must have at least one schema');
@@ -171,7 +174,7 @@ export const urlSchema = yupString().test({
171
174
  },
172
175
  });
173
176
  export const jsonSchema = yupMixed().nullable().defined().transform((value) => JSON.parse(JSON.stringify(value)));
174
- export const jsonStringSchema = yupString().test("json", "Invalid JSON format", (value) => {
177
+ export const jsonStringSchema = yupString().test("json", (params) => `${params.path} is not valid JSON`, (value) => {
175
178
  if (value == null)
176
179
  return true;
177
180
  try {
@@ -182,7 +185,7 @@ export const jsonStringSchema = yupString().test("json", "Invalid JSON format",
182
185
  return false;
183
186
  }
184
187
  });
185
- export const jsonStringOrEmptySchema = yupString().test("json", "Invalid JSON format", (value) => {
188
+ export const jsonStringOrEmptySchema = yupString().test("json", (params) => `${params.path} is not valid JSON`, (value) => {
186
189
  if (!value)
187
190
  return true;
188
191
  try {
@@ -193,7 +196,7 @@ export const jsonStringOrEmptySchema = yupString().test("json", "Invalid JSON fo
193
196
  return false;
194
197
  }
195
198
  });
196
- export const base64Schema = yupString().test("is-base64", "Invalid base64 format", (value) => {
199
+ export const base64Schema = yupString().test("is-base64", (params) => `${params.path} is not valid base64`, (value) => {
197
200
  if (value == null)
198
201
  return true;
199
202
  return isBase64(value);
package/dist/utils/env.js CHANGED
@@ -3,6 +3,10 @@ import { deindent } from "./strings";
3
3
  export function isBrowserLike() {
4
4
  return typeof window !== "undefined" && typeof document !== "undefined" && typeof document.createElement !== "undefined";
5
5
  }
6
+ // newName: oldName
7
+ const ENV_VAR_RENAME = {
8
+ NEXT_PUBLIC_STACK_API_URL: ['STACK_BASE_URL', 'NEXT_PUBLIC_STACK_URL'],
9
+ };
6
10
  /**
7
11
  * Returns the environment variable with the given name, returning the default (if given) or throwing an error (otherwise) if it's undefined or the empty string.
8
12
  */
@@ -21,7 +25,30 @@ export function getEnvVariable(name, defaultValue) {
21
25
  Use getNextRuntime() instead.
22
26
  `);
23
27
  }
24
- return ((process.env[name] || defaultValue) ?? throwErr(`Missing environment variable: ${name}`)) || (defaultValue ?? throwErr(`Empty environment variable: ${name}`));
28
+ // throw error if the old name is used as the retrieve key
29
+ for (const [newName, oldNames] of Object.entries(ENV_VAR_RENAME)) {
30
+ if (oldNames.includes(name)) {
31
+ throwErr(`Environment variable ${name} has been renamed to ${newName}. Please update your configuration to use the new name.`);
32
+ }
33
+ }
34
+ let value = process.env[name];
35
+ // check the key under the old name if the new name is not found
36
+ if (!value && ENV_VAR_RENAME[name]) {
37
+ for (const oldName of ENV_VAR_RENAME[name]) {
38
+ value = process.env[oldName];
39
+ if (value)
40
+ break;
41
+ }
42
+ }
43
+ if (value === undefined) {
44
+ if (defaultValue !== undefined) {
45
+ value = defaultValue;
46
+ }
47
+ else {
48
+ throwErr(`Missing environment variable: ${name}`);
49
+ }
50
+ }
51
+ return value;
25
52
  }
26
53
  export function getNextRuntime() {
27
54
  // This variable is compiled into the client bundle, so we can't use getEnvVariable here.
@@ -1,3 +1,4 @@
1
+ export declare function sha512(input: Uint8Array | string): Promise<string>;
1
2
  export declare function hashPassword(password: string): Promise<string>;
2
3
  export declare function comparePassword(password: string, hash: string): Promise<boolean>;
3
4
  export declare function isPasswordHashValid(hash: string): Promise<boolean>;
@@ -1,5 +1,11 @@
1
1
  import bcrypt from 'bcrypt';
2
2
  import { StackAssertionError } from './errors';
3
+ export async function sha512(input) {
4
+ const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
5
+ return await crypto.subtle.digest("SHA-512", bytes).then(buf => {
6
+ return Array.prototype.map.call(new Uint8Array(buf), x => (('00' + x.toString(16)).slice(-2))).join('');
7
+ });
8
+ }
3
9
  export async function hashPassword(password) {
4
10
  const passwordBytes = new TextEncoder().encode(password);
5
11
  if (passwordBytes.length >= 72) {
@@ -11,21 +11,26 @@ export declare function verifyJWT(options: {
11
11
  issuer: string;
12
12
  jwt: string;
13
13
  }): Promise<jose.JWTPayload>;
14
- export declare function getPrivateJwk(secret: string): Promise<{
15
- kty: string;
16
- crv: string;
14
+ export type PrivateJwk = {
15
+ kty: "EC";
16
+ alg: "ES256";
17
+ crv: "P-256";
18
+ kid: string;
17
19
  d: string;
18
20
  x: string;
19
21
  y: string;
20
- }>;
21
- export declare function getPublicJwkSet(secret: string): Promise<{
22
- keys: {
23
- kid: string;
24
- x: string;
25
- kty: string;
26
- crv: string;
27
- y: string;
28
- }[];
22
+ };
23
+ export declare function getPrivateJwk(secret: string): Promise<PrivateJwk>;
24
+ export type PublicJwk = {
25
+ kty: "EC";
26
+ alg: "ES256";
27
+ crv: "P-256";
28
+ kid: string;
29
+ x: string;
30
+ y: string;
31
+ };
32
+ export declare function getPublicJwkSet(secretOrPrivateJwk: string | PrivateJwk): Promise<{
33
+ keys: PublicJwk[];
29
34
  }>;
30
35
  export declare function getPerAudienceSecret(options: {
31
36
  audience: string;
package/dist/utils/jwt.js CHANGED
@@ -1,12 +1,11 @@
1
+ import crypto from "crypto";
1
2
  import elliptic from "elliptic";
2
3
  import * as jose from "jose";
4
+ import { JOSEError } from "jose/errors";
3
5
  import { encodeBase64Url } from "./bytes";
4
- import { getEnvVariable } from "./env";
5
6
  import { globalVar } from "./globals";
6
7
  import { pick } from "./objects";
7
- import crypto from "crypto";
8
- import { JOSEError } from "jose/errors";
9
- const STACK_SERVER_SECRET = getEnvVariable("STACK_SERVER_SECRET");
8
+ const STACK_SERVER_SECRET = process.env.STACK_SERVER_SECRET ?? "";
10
9
  try {
11
10
  jose.base64url.decode(STACK_SERVER_SECRET);
12
11
  }
@@ -60,21 +59,25 @@ export async function getPrivateJwk(secret) {
60
59
  return {
61
60
  kty: 'EC',
62
61
  crv: 'P-256',
62
+ alg: 'ES256',
63
+ kid: getKid({ secret }),
63
64
  d: encodeBase64Url(priv),
64
65
  x: encodeBase64Url(publicKey.getX().toBuffer()),
65
66
  y: encodeBase64Url(publicKey.getY().toBuffer()),
66
67
  };
67
68
  }
68
- export async function getPublicJwkSet(secret) {
69
- const privateJwk = await getPrivateJwk(secret);
70
- const jwk = pick(privateJwk, ["kty", "crv", "x", "y"]);
69
+ export async function getPublicJwkSet(secretOrPrivateJwk) {
70
+ const privateJwk = typeof secretOrPrivateJwk === "string" ? await getPrivateJwk(secretOrPrivateJwk) : secretOrPrivateJwk;
71
+ const jwk = pick(privateJwk, ["kty", "alg", "crv", "x", "y", "kid"]);
71
72
  return {
72
- keys: [{ ...jwk, kid: getKid({ secret }) }],
73
+ keys: [jwk],
73
74
  };
74
75
  }
75
76
  export function getPerAudienceSecret(options) {
76
77
  return jose.base64url.encode(crypto
77
78
  .createHash('sha256')
79
+ // 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
80
+ // Sadly doing this modification is a bit annoying as we need to leave the old keys to be valid for a little longer
78
81
  .update(JSON.stringify([options.secret, options.audience]))
79
82
  .digest());
80
83
  }
@@ -82,6 +85,6 @@ export function getPerAudienceSecret(options) {
82
85
  export function getKid(options) {
83
86
  return jose.base64url.encode(crypto
84
87
  .createHash('sha256')
85
- .update(JSON.stringify([options.secret, "kid"]))
88
+ .update(JSON.stringify([options.secret, "kid"])) // TODO see above in getPerAudienceSecret
86
89
  .digest()).slice(0, 12);
87
90
  }
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { IncomingMessage, ServerResponse } from "http";
4
+ declare class ServerResponseWithBodyChunks extends ServerResponse {
5
+ bodyChunks: Uint8Array[];
6
+ _send(data: string, encoding: BufferEncoding, callback?: (() => void) | null, byteLength?: number): void;
7
+ }
8
+ export declare function createNodeHttpServerDuplex(options: {
9
+ method: string;
10
+ originalUrl?: URL;
11
+ url: URL;
12
+ headers: Headers;
13
+ body: Uint8Array;
14
+ }): Promise<[IncomingMessage, ServerResponseWithBodyChunks]>;
15
+ export {};
@@ -0,0 +1,38 @@
1
+ import { IncomingMessage, ServerResponse } from "http";
2
+ import { getRelativePart } from "./urls";
3
+ class ServerResponseWithBodyChunks extends ServerResponse {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.bodyChunks = [];
7
+ }
8
+ // note: we actually override this, even though it's private in the parent
9
+ _send(data, encoding, callback, byteLength) {
10
+ if (typeof encoding === "function") {
11
+ callback = encoding;
12
+ encoding = "utf-8";
13
+ }
14
+ const encodedBuffer = new Uint8Array(Buffer.from(data, encoding));
15
+ this.bodyChunks.push(encodedBuffer);
16
+ callback?.();
17
+ }
18
+ }
19
+ export async function createNodeHttpServerDuplex(options) {
20
+ // See https://github.com/nodejs/node/blob/main/lib/_http_incoming.js
21
+ // and https://github.com/nodejs/node/blob/main/lib/_http_common.js (particularly the `parserXyz` functions)
22
+ const incomingMessage = new IncomingMessage({
23
+ encrypted: options.originalUrl?.protocol === "https:", // trick frameworks into believing this is an HTTPS request
24
+ });
25
+ incomingMessage.httpVersionMajor = 1;
26
+ incomingMessage.httpVersionMinor = 1;
27
+ incomingMessage.httpVersion = '1.1';
28
+ incomingMessage.method = options.method;
29
+ incomingMessage.url = getRelativePart(options.url);
30
+ incomingMessage.originalUrl = options.originalUrl && getRelativePart(options.originalUrl); // originalUrl is an extension used by some servers; for example, oidc-provider reads it to construct the paths for the .well-known/openid-configuration
31
+ const rawHeaders = [...options.headers.entries()].flat();
32
+ incomingMessage._addHeaderLines(rawHeaders, rawHeaders.length);
33
+ incomingMessage.push(Buffer.from(options.body));
34
+ incomingMessage.complete = true;
35
+ incomingMessage.push(null); // to emit end event, see: https://github.com/nodejs/node/blob/4cf6fabce20eb3050c5b543d249e931ea3d3cad5/lib/_http_common.js#L150
36
+ const serverResponse = new ServerResponseWithBodyChunks(incomingMessage);
37
+ return [incomingMessage, serverResponse];
38
+ }
@@ -210,6 +210,9 @@ export function nicify(value, options = {}) {
210
210
  if (value instanceof URL) {
211
211
  return `URL(${JSON.stringify(value.toString())})`;
212
212
  }
213
+ if (ArrayBuffer.isView(value)) {
214
+ return `${value.constructor.name}([${value.toString()}])`;
215
+ }
213
216
  const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : (nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name);
214
217
  const constructorString = constructorName ? `${nicifyPropertyString(constructorName)} ` : "";
215
218
  const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.6.30",
3
+ "version": "2.6.31",
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.30"
53
+ "@stackframe/stack-sc": "2.6.31"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@simplewebauthn/types": "^11.0.0",