@stackframe/stack-shared 2.6.30 → 2.6.32
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 +14 -0
- package/dist/interface/adminInterface.js +1 -1
- package/dist/interface/clientInterface.d.ts +2 -0
- package/dist/interface/clientInterface.js +42 -12
- package/dist/schema-fields.d.ts +1 -0
- package/dist/schema-fields.js +6 -3
- package/dist/utils/bytes.d.ts +1 -0
- package/dist/utils/bytes.js +11 -0
- 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/http.d.ts +39 -2
- package/dist/utils/http.js +38 -1
- 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",
|
|
@@ -33,6 +33,7 @@ export declare class StackClientInterface {
|
|
|
33
33
|
prodDashboard: string;
|
|
34
34
|
prodBackend: string;
|
|
35
35
|
}>;
|
|
36
|
+
protected _createNetworkError(cause: Error, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<Error>;
|
|
36
37
|
protected _networkRetry<T>(cb: () => Promise<Result<T, any>>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
|
|
37
38
|
protected _networkRetryException<T>(cb: () => Promise<T>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
|
|
38
39
|
fetchNewAccessToken(refreshToken: RefreshToken): Promise<AccessToken | null>;
|
|
@@ -186,6 +187,7 @@ export declare class StackClientInterface {
|
|
|
186
187
|
createProject(project: InternalProjectsCrud['Client']['Create'], session: InternalSession): Promise<InternalProjectsCrud['Client']['Read']>;
|
|
187
188
|
createProviderAccessToken(provider: string, scope: string, session: InternalSession): Promise<ConnectedAccountAccessTokenCrud['Client']['Read']>;
|
|
188
189
|
createClientTeam(data: TeamsCrud['Client']['Create'], session: InternalSession): Promise<TeamsCrud['Client']['Read']>;
|
|
190
|
+
deleteTeam(teamId: string, session: InternalSession): Promise<void>;
|
|
189
191
|
deleteCurrentUser(session: InternalSession): Promise<void>;
|
|
190
192
|
createClientContactChannel(data: ContactChannelsCrud['Client']['Create'], session: InternalSession): Promise<ContactChannelsCrud['Client']['Read']>;
|
|
191
193
|
updateClientContactChannel(id: string, data: ContactChannelsCrud['Client']['Update'], session: InternalSession): Promise<ContactChannelsCrud['Client']['Read']>;
|
|
@@ -5,7 +5,9 @@ import { AccessToken, InternalSession } from '../sessions';
|
|
|
5
5
|
import { generateSecureRandomString } from '../utils/crypto';
|
|
6
6
|
import { StackAssertionError, throwErr } from '../utils/errors';
|
|
7
7
|
import { globalVar } from '../utils/globals';
|
|
8
|
+
import { HTTP_METHODS } from '../utils/http';
|
|
8
9
|
import { filterUndefined } from '../utils/objects';
|
|
10
|
+
import { wait } from '../utils/promises';
|
|
9
11
|
import { Result } from "../utils/results";
|
|
10
12
|
import { deindent } from '../utils/strings';
|
|
11
13
|
export class StackClientInterface {
|
|
@@ -68,6 +70,17 @@ export class StackClientInterface {
|
|
|
68
70
|
prodBackend,
|
|
69
71
|
};
|
|
70
72
|
}
|
|
73
|
+
async _createNetworkError(cause, session, requestType) {
|
|
74
|
+
return new Error(deindent `
|
|
75
|
+
Stack Auth is unable to connect to the server. Please check your internet connection and try again.
|
|
76
|
+
|
|
77
|
+
If the problem persists, please contact support and provide a screenshot of your entire browser console.
|
|
78
|
+
|
|
79
|
+
${cause}
|
|
80
|
+
|
|
81
|
+
${JSON.stringify(await this.runNetworkDiagnostics(session, requestType), null, 2)}
|
|
82
|
+
`, { cause: cause });
|
|
83
|
+
}
|
|
71
84
|
async _networkRetry(cb, session, requestType) {
|
|
72
85
|
const retriedResult = await Result.retry(cb, 5, { exponentialDelayBase: 1000 });
|
|
73
86
|
// try to diagnose the error for the user
|
|
@@ -75,15 +88,7 @@ export class StackClientInterface {
|
|
|
75
88
|
if (globalVar.navigator && !globalVar.navigator.onLine) {
|
|
76
89
|
throw new Error("Failed to send Stack network request. It seems like you are offline. (window.navigator.onLine is falsy)", { cause: retriedResult.error });
|
|
77
90
|
}
|
|
78
|
-
throw
|
|
79
|
-
Stack Auth is unable to connect to the server. Please check your internet connection and try again.
|
|
80
|
-
|
|
81
|
-
If the problem persists, please contact support and provide a screenshot of your entire browser console.
|
|
82
|
-
|
|
83
|
-
${retriedResult.error}
|
|
84
|
-
|
|
85
|
-
${JSON.stringify(await this.runNetworkDiagnostics(session, requestType), null, 2)}
|
|
86
|
-
`, { cause: retriedResult.error });
|
|
91
|
+
throw this._createNetworkError(retriedResult.error, session, requestType);
|
|
87
92
|
}
|
|
88
93
|
return retriedResult.data;
|
|
89
94
|
}
|
|
@@ -220,8 +225,13 @@ export class StackClientInterface {
|
|
|
220
225
|
}
|
|
221
226
|
catch (e) {
|
|
222
227
|
if (e instanceof TypeError) {
|
|
223
|
-
//
|
|
224
|
-
|
|
228
|
+
// Likely to be a network error. Retry if the request is idempotent, throw network error otherwise.
|
|
229
|
+
if (HTTP_METHODS[(params.method ?? "GET")].idempotent) {
|
|
230
|
+
return Result.error(e);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
throw this._createNetworkError(e, session, requestType);
|
|
234
|
+
}
|
|
225
235
|
}
|
|
226
236
|
throw e;
|
|
227
237
|
}
|
|
@@ -254,10 +264,25 @@ export class StackClientInterface {
|
|
|
254
264
|
if (res.ok) {
|
|
255
265
|
return Result.ok(res);
|
|
256
266
|
}
|
|
267
|
+
else if (res.status === 429) {
|
|
268
|
+
// Rate limited, so retry if we can
|
|
269
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
270
|
+
if (retryAfter !== null) {
|
|
271
|
+
await wait(Number(retryAfter) * 1000);
|
|
272
|
+
return Result.error(new Error(`Rate limited, retrying after ${retryAfter} seconds`));
|
|
273
|
+
}
|
|
274
|
+
return Result.error(new Error("Rate limited, no retry-after header received"));
|
|
275
|
+
}
|
|
257
276
|
else {
|
|
258
277
|
const error = await res.text();
|
|
278
|
+
const errorObj = new StackAssertionError(`Failed to send request to ${url}: ${res.status} ${error}`, { request: params, res });
|
|
279
|
+
if (res.status === 508 && error.includes("INFINITE_LOOP_DETECTED")) {
|
|
280
|
+
// Some Vercel deployments seem to have an odd infinite loop bug. In that case, retry.
|
|
281
|
+
// See: https://github.com/stack-auth/stack/issues/319
|
|
282
|
+
return Result.error(errorObj);
|
|
283
|
+
}
|
|
259
284
|
// Do not retry, throw error instead of returning one
|
|
260
|
-
throw
|
|
285
|
+
throw errorObj;
|
|
261
286
|
}
|
|
262
287
|
}
|
|
263
288
|
async _processResponse(rawRes) {
|
|
@@ -812,6 +837,11 @@ export class StackClientInterface {
|
|
|
812
837
|
}, session);
|
|
813
838
|
return await response.json();
|
|
814
839
|
}
|
|
840
|
+
async deleteTeam(teamId, session) {
|
|
841
|
+
await this.sendClientRequest(`/teams/${teamId}`, {
|
|
842
|
+
method: "DELETE",
|
|
843
|
+
}, session);
|
|
844
|
+
}
|
|
815
845
|
async deleteCurrentUser(session) {
|
|
816
846
|
await this.sendClientRequest("/users/me", {
|
|
817
847
|
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/bytes.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export declare function encodeBase64(input: Uint8Array): string;
|
|
|
4
4
|
export declare function decodeBase64(input: string): Uint8Array;
|
|
5
5
|
export declare function encodeBase64Url(input: Uint8Array): string;
|
|
6
6
|
export declare function decodeBase64Url(input: string): Uint8Array;
|
|
7
|
+
export declare function decodeBase64OrBase64Url(input: string): Uint8Array;
|
|
7
8
|
export declare function isBase32(input: string): boolean;
|
|
8
9
|
export declare function isBase64(input: string): boolean;
|
|
9
10
|
export declare function isBase64Url(input: string): boolean;
|
package/dist/utils/bytes.js
CHANGED
|
@@ -82,6 +82,17 @@ export function decodeBase64Url(input) {
|
|
|
82
82
|
}
|
|
83
83
|
return decodeBase64(input.replace(/-/g, "+").replace(/_/g, "/") + "====".slice((input.length - 1) % 4 + 1));
|
|
84
84
|
}
|
|
85
|
+
export function decodeBase64OrBase64Url(input) {
|
|
86
|
+
if (isBase64Url(input)) {
|
|
87
|
+
return decodeBase64Url(input);
|
|
88
|
+
}
|
|
89
|
+
else if (isBase64(input)) {
|
|
90
|
+
return decodeBase64(input);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new StackAssertionError("Invalid base64 or base64url string");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
85
96
|
export function isBase32(input) {
|
|
86
97
|
for (const char of input) {
|
|
87
98
|
if (char === " ")
|
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/http.d.ts
CHANGED
|
@@ -1,2 +1,39 @@
|
|
|
1
|
-
export declare const HTTP_METHODS:
|
|
2
|
-
|
|
1
|
+
export declare const HTTP_METHODS: {
|
|
2
|
+
readonly GET: {
|
|
3
|
+
readonly safe: true;
|
|
4
|
+
readonly idempotent: true;
|
|
5
|
+
};
|
|
6
|
+
readonly POST: {
|
|
7
|
+
readonly safe: false;
|
|
8
|
+
readonly idempotent: false;
|
|
9
|
+
};
|
|
10
|
+
readonly PUT: {
|
|
11
|
+
readonly safe: false;
|
|
12
|
+
readonly idempotent: true;
|
|
13
|
+
};
|
|
14
|
+
readonly DELETE: {
|
|
15
|
+
readonly safe: false;
|
|
16
|
+
readonly idempotent: true;
|
|
17
|
+
};
|
|
18
|
+
readonly PATCH: {
|
|
19
|
+
readonly safe: false;
|
|
20
|
+
readonly idempotent: false;
|
|
21
|
+
};
|
|
22
|
+
readonly OPTIONS: {
|
|
23
|
+
readonly safe: true;
|
|
24
|
+
readonly idempotent: true;
|
|
25
|
+
};
|
|
26
|
+
readonly HEAD: {
|
|
27
|
+
readonly safe: true;
|
|
28
|
+
readonly idempotent: true;
|
|
29
|
+
};
|
|
30
|
+
readonly TRACE: {
|
|
31
|
+
readonly safe: true;
|
|
32
|
+
readonly idempotent: true;
|
|
33
|
+
};
|
|
34
|
+
readonly CONNECT: {
|
|
35
|
+
readonly safe: false;
|
|
36
|
+
readonly idempotent: false;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
export type HttpMethod = keyof typeof HTTP_METHODS;
|
package/dist/utils/http.js
CHANGED
|
@@ -1 +1,38 @@
|
|
|
1
|
-
export const HTTP_METHODS =
|
|
1
|
+
export const HTTP_METHODS = {
|
|
2
|
+
"GET": {
|
|
3
|
+
safe: true,
|
|
4
|
+
idempotent: true,
|
|
5
|
+
},
|
|
6
|
+
"POST": {
|
|
7
|
+
safe: false,
|
|
8
|
+
idempotent: false,
|
|
9
|
+
},
|
|
10
|
+
"PUT": {
|
|
11
|
+
safe: false,
|
|
12
|
+
idempotent: true,
|
|
13
|
+
},
|
|
14
|
+
"DELETE": {
|
|
15
|
+
safe: false,
|
|
16
|
+
idempotent: true,
|
|
17
|
+
},
|
|
18
|
+
"PATCH": {
|
|
19
|
+
safe: false,
|
|
20
|
+
idempotent: false,
|
|
21
|
+
},
|
|
22
|
+
"OPTIONS": {
|
|
23
|
+
safe: true,
|
|
24
|
+
idempotent: true,
|
|
25
|
+
},
|
|
26
|
+
"HEAD": {
|
|
27
|
+
safe: true,
|
|
28
|
+
idempotent: true,
|
|
29
|
+
},
|
|
30
|
+
"TRACE": {
|
|
31
|
+
safe: true,
|
|
32
|
+
idempotent: true,
|
|
33
|
+
},
|
|
34
|
+
"CONNECT": {
|
|
35
|
+
safe: false,
|
|
36
|
+
idempotent: false,
|
|
37
|
+
},
|
|
38
|
+
};
|
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.32",
|
|
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.32"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@simplewebauthn/types": "^11.0.0",
|