@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 +7 -0
- package/dist/interface/adminInterface.js +1 -1
- package/dist/interface/clientInterface.d.ts +1 -0
- package/dist/interface/clientInterface.js +5 -0
- package/dist/schema-fields.d.ts +1 -0
- package/dist/schema-fields.js +6 -3
- package/dist/utils/env.js +28 -1
- package/dist/utils/hashes.d.ts +1 -0
- package/dist/utils/hashes.js +6 -0
- package/dist/utils/jwt.d.ts +17 -12
- package/dist/utils/jwt.js +12 -9
- package/dist/utils/node-http.d.ts +15 -0
- package/dist/utils/node-http.js +38 -0
- package/dist/utils/strings.js +3 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -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.
|
|
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",
|
package/dist/schema-fields.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/schema-fields.js
CHANGED
|
@@ -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",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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.
|
package/dist/utils/hashes.d.ts
CHANGED
|
@@ -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>;
|
package/dist/utils/hashes.js
CHANGED
|
@@ -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) {
|
package/dist/utils/jwt.d.ts
CHANGED
|
@@ -11,21 +11,26 @@ export declare function verifyJWT(options: {
|
|
|
11
11
|
issuer: string;
|
|
12
12
|
jwt: string;
|
|
13
13
|
}): Promise<jose.JWTPayload>;
|
|
14
|
-
export
|
|
15
|
-
kty:
|
|
16
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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(
|
|
69
|
-
const privateJwk = await getPrivateJwk(
|
|
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: [
|
|
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
|
+
}
|
package/dist/utils/strings.js
CHANGED
|
@@ -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.
|
|
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.
|
|
53
|
+
"@stackframe/stack-sc": "2.6.31"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@simplewebauthn/types": "^11.0.0",
|