@stackframe/stack-shared 2.5.16 → 2.5.18
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/clientInterface.d.ts +19 -0
- package/dist/interface/clientInterface.js +89 -26
- package/dist/interface/crud/current-user.d.ts +58 -0
- package/dist/interface/crud/current-user.js +4 -0
- package/dist/interface/crud/email-templates.d.ts +5 -5
- package/dist/interface/crud/email-templates.js +1 -1
- package/dist/interface/crud/projects.d.ts +14 -0
- package/dist/interface/crud/projects.js +3 -0
- package/dist/interface/crud/team-invitation-details.d.ts +25 -0
- package/dist/interface/crud/team-invitation-details.js +17 -0
- package/dist/interface/crud/users.d.ts +116 -0
- package/dist/interface/crud/users.js +30 -2
- package/dist/interface/crud-deprecated/email-templates.d.ts +5 -5
- package/dist/interface/crud-deprecated/email-templates.js +1 -1
- package/dist/interface/serverInterface.d.ts +2 -6
- package/dist/interface/serverInterface.js +10 -5
- package/dist/interface/webhooks.d.ts +54 -0
- package/dist/known-errors.d.ts +15 -0
- package/dist/known-errors.js +33 -0
- package/dist/schema-fields.d.ts +12 -0
- package/dist/schema-fields.js +39 -1
- package/dist/utils/arrays.d.ts +1 -0
- package/dist/utils/arrays.js +3 -0
- package/dist/utils/bytes.d.ts +4 -0
- package/dist/utils/bytes.js +36 -0
- package/dist/utils/crypto.d.ts +1 -0
- package/dist/utils/crypto.js +11 -1
- package/dist/utils/results.d.ts +2 -0
- package/dist/utils/results.js +9 -0
- package/dist/utils/strings.js +14 -2
- package/dist/utils/uuids.js +2 -2
- package/package.json +2 -2
package/dist/schema-fields.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as yup from "yup";
|
|
2
|
+
import { isBase64 } from "./utils/bytes";
|
|
3
|
+
import { StackAssertionError } from "./utils/errors";
|
|
2
4
|
import { allProviders } from "./utils/oauth";
|
|
3
5
|
import { isUuid } from "./utils/uuids";
|
|
4
6
|
const _idDescription = (identify) => `The unique identifier of this ${identify}`;
|
|
@@ -55,6 +57,30 @@ export function yupObject(...args) {
|
|
|
55
57
|
return object.default(undefined);
|
|
56
58
|
}
|
|
57
59
|
/* eslint-enable no-restricted-syntax */
|
|
60
|
+
export function yupUnion(...args) {
|
|
61
|
+
if (args.length === 0)
|
|
62
|
+
throw new Error('yupUnion must have at least one schema');
|
|
63
|
+
const [first] = args;
|
|
64
|
+
const firstDesc = first.describe();
|
|
65
|
+
for (const schema of args) {
|
|
66
|
+
const desc = schema.describe();
|
|
67
|
+
if (desc.type !== firstDesc.type)
|
|
68
|
+
throw new StackAssertionError(`yupUnion must have schemas of the same type (got: ${firstDesc.type} and ${desc.type})`, { first, schema, firstDesc, desc });
|
|
69
|
+
}
|
|
70
|
+
return yupMixed().required().test('is-one-of', 'Invalid value', async (value, context) => {
|
|
71
|
+
const errors = [];
|
|
72
|
+
for (const schema of args) {
|
|
73
|
+
try {
|
|
74
|
+
await schema.validate(value, context.options);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
errors.push(e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw new AggregateError(errors, 'Invalid value; must be one of the provided schemas');
|
|
82
|
+
});
|
|
83
|
+
}
|
|
58
84
|
// Common
|
|
59
85
|
export const adaptSchema = yupMixed();
|
|
60
86
|
/**
|
|
@@ -99,6 +125,11 @@ export const jsonStringOrEmptySchema = yupString().test("json", "Invalid JSON fo
|
|
|
99
125
|
}
|
|
100
126
|
});
|
|
101
127
|
export const emailSchema = yupString().email();
|
|
128
|
+
export const base64Schema = yupString().test("is-base64", "Invalid base64 format", (value) => {
|
|
129
|
+
if (value == null)
|
|
130
|
+
return true;
|
|
131
|
+
return isBase64(value);
|
|
132
|
+
});
|
|
102
133
|
// Request auth
|
|
103
134
|
export const clientOrHigherAuthTypeSchema = yupString().oneOf(['client', 'server', 'admin']);
|
|
104
135
|
export const serverOrHigherAuthTypeSchema = yupString().oneOf(['server', 'admin']);
|
|
@@ -115,6 +146,7 @@ export const projectConfigIdSchema = yupString().meta({ openapiField: { descript
|
|
|
115
146
|
export const projectAllowLocalhostSchema = yupBoolean().meta({ openapiField: { description: 'Whether localhost is allowed as a domain for this project. Should only be allowed in development mode', exampleValue: true } });
|
|
116
147
|
export const projectCreateTeamOnSignUpSchema = yupBoolean().meta({ openapiField: { description: 'Whether a team should be created for each user that signs up', exampleValue: true } });
|
|
117
148
|
export const projectMagicLinkEnabledSchema = yupBoolean().meta({ openapiField: { description: 'Whether magic link authentication is enabled for this project', exampleValue: true } });
|
|
149
|
+
export const projectSignUpEnabledSchema = yupBoolean().meta({ openapiField: { description: 'Whether users can sign up new accounts, or whether they are only allowed to sign in to existing accounts. Regardless of this option, the server API can always create new users with the `POST /users` endpoint.', exampleValue: true } });
|
|
118
150
|
export const projectCredentialEnabledSchema = yupBoolean().meta({ openapiField: { description: 'Whether email password authentication is enabled for this project', exampleValue: true } });
|
|
119
151
|
// Project OAuth config
|
|
120
152
|
export const oauthIdSchema = yupString().oneOf(allProviders).meta({ openapiField: { description: `OAuth provider ID, one of ${allProviders.map(x => `\`${x}\``).join(', ')}`, exampleValue: 'google' } });
|
|
@@ -155,11 +187,15 @@ export const userIdSchema = yupString().uuid().meta({ openapiField: { descriptio
|
|
|
155
187
|
export const primaryEmailSchema = emailSchema.meta({ openapiField: { description: 'Primary email', exampleValue: 'johndoe@example.com' } });
|
|
156
188
|
export const primaryEmailVerifiedSchema = yupBoolean().meta({ openapiField: { description: 'Whether the primary email has been verified to belong to this user', exampleValue: true } });
|
|
157
189
|
export const userDisplayNameSchema = yupString().nullable().meta({ openapiField: { description: _displayNameDescription('user'), exampleValue: 'John Doe' } });
|
|
158
|
-
export const selectedTeamIdSchema = yupString().meta({ openapiField: { description: 'ID of the team currently selected by the user', exampleValue: 'team-id' } });
|
|
190
|
+
export const selectedTeamIdSchema = yupString().uuid().meta({ openapiField: { description: 'ID of the team currently selected by the user', exampleValue: 'team-id' } });
|
|
159
191
|
export const profileImageUrlSchema = yupString().meta({ openapiField: { description: _profileImageUrlDescription('user'), exampleValue: 'https://example.com/image.jpg' } });
|
|
160
192
|
export const signedUpAtMillisSchema = yupNumber().meta({ openapiField: { description: _signedUpAtMillisDescription, exampleValue: 1630000000000 } });
|
|
161
193
|
export const userClientMetadataSchema = jsonSchema.meta({ openapiField: { description: _clientMetaDataDescription('user'), exampleValue: { key: 'value' } } });
|
|
162
194
|
export const userServerMetadataSchema = jsonSchema.meta({ openapiField: { description: _serverMetaDataDescription('user'), exampleValue: { key: 'value' } } });
|
|
195
|
+
export const userOAuthProviderSchema = yupObject({
|
|
196
|
+
type: yupString().required(),
|
|
197
|
+
provider_user_id: yupString().required(),
|
|
198
|
+
});
|
|
163
199
|
// Auth
|
|
164
200
|
export const signInEmailSchema = emailSchema.meta({ openapiField: { description: 'The email to sign in with.', exampleValue: 'johndoe@example.com' } });
|
|
165
201
|
export const emailOtpSignInCallbackUrlSchema = urlSchema.meta({ openapiField: { description: 'The base callback URL to construct the magic link from. A query argument `code` with the verification code will be appended to it. The page should then make a request to the `/auth/otp/sign-in` endpoint.', exampleValue: 'https://example.com/handler/magic-link-callback' } });
|
|
@@ -203,6 +239,8 @@ export const teamProfileImageUrlSchema = yupString().meta({ openapiField: { desc
|
|
|
203
239
|
export const teamClientMetadataSchema = jsonSchema.meta({ openapiField: { description: _clientMetaDataDescription('team'), exampleValue: { key: 'value' } } });
|
|
204
240
|
export const teamServerMetadataSchema = jsonSchema.meta({ openapiField: { description: _serverMetaDataDescription('team'), exampleValue: { key: 'value' } } });
|
|
205
241
|
export const teamCreatedAtMillisSchema = yupNumber().meta({ openapiField: { description: _createdAtMillisDescription('team'), exampleValue: 1630000000000 } });
|
|
242
|
+
export const teamInvitationEmailSchema = emailSchema.meta({ openapiField: { description: 'The email to sign in with.', exampleValue: 'johndoe@example.com' } });
|
|
243
|
+
export const teamInvitationCallbackUrlSchema = urlSchema.meta({ openapiField: { description: 'The base callback URL to construct a verification link for the verification e-mail. A query argument `code` with the verification code will be appended to it. The page should then make a request to the `/contact-channels/verify` endpoint.', exampleValue: 'https://example.com/handler/email-verification' } });
|
|
206
244
|
// Team member profiles
|
|
207
245
|
export const teamMemberDisplayNameSchema = yupString().meta({ openapiField: { description: _displayNameDescription('team member') + ' Note that this is separate from the display_name of the user.', exampleValue: 'John Doe' } });
|
|
208
246
|
export const teamMemberProfileImageUrlSchema = yupString().meta({ openapiField: { description: _profileImageUrlDescription('team member'), exampleValue: 'https://example.com/image.jpg' } });
|
package/dist/utils/arrays.d.ts
CHANGED
|
@@ -13,3 +13,4 @@ export declare function rotateLeft(arr: readonly any[], n: number): any[];
|
|
|
13
13
|
export declare function rotateRight(arr: readonly any[], n: number): any[];
|
|
14
14
|
export declare function shuffle<T>(arr: readonly T[]): T[];
|
|
15
15
|
export declare function outerProduct<T, U>(arr1: readonly T[], arr2: readonly U[]): [T, U][];
|
|
16
|
+
export declare function unique<T>(arr: readonly T[]): T[];
|
package/dist/utils/arrays.js
CHANGED
package/dist/utils/bytes.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export declare function encodeBase32(input: Uint8Array): string;
|
|
2
2
|
export declare function decodeBase32(input: string): Uint8Array;
|
|
3
|
+
export declare function encodeBase64(input: Uint8Array): string;
|
|
4
|
+
export declare function decodeBase64(input: string): Uint8Array;
|
|
5
|
+
export declare function isBase32(input: string): boolean;
|
|
6
|
+
export declare function isBase64(input: string): boolean;
|
package/dist/utils/bytes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StackAssertionError } from "./errors";
|
|
1
2
|
const crockfordAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
2
3
|
const crockfordReplacements = new Map([
|
|
3
4
|
["o", "0"],
|
|
@@ -19,9 +20,16 @@ export function encodeBase32(input) {
|
|
|
19
20
|
if (bits > 0) {
|
|
20
21
|
output += crockfordAlphabet[(value << (5 - bits)) & 31];
|
|
21
22
|
}
|
|
23
|
+
// sanity check
|
|
24
|
+
if (!isBase32(output)) {
|
|
25
|
+
throw new StackAssertionError("Invalid base32 output; this should never happen");
|
|
26
|
+
}
|
|
22
27
|
return output;
|
|
23
28
|
}
|
|
24
29
|
export function decodeBase32(input) {
|
|
30
|
+
if (!isBase32(input)) {
|
|
31
|
+
throw new StackAssertionError("Invalid base32 string");
|
|
32
|
+
}
|
|
25
33
|
const output = new Uint8Array((input.length * 5 / 8) | 0);
|
|
26
34
|
let bits = 0;
|
|
27
35
|
let value = 0;
|
|
@@ -46,3 +54,31 @@ export function decodeBase32(input) {
|
|
|
46
54
|
}
|
|
47
55
|
return output;
|
|
48
56
|
}
|
|
57
|
+
export function encodeBase64(input) {
|
|
58
|
+
const res = btoa(String.fromCharCode(...input));
|
|
59
|
+
// sanity check
|
|
60
|
+
if (!isBase64(res)) {
|
|
61
|
+
throw new StackAssertionError("Invalid base64 output; this should never happen");
|
|
62
|
+
}
|
|
63
|
+
return res;
|
|
64
|
+
}
|
|
65
|
+
export function decodeBase64(input) {
|
|
66
|
+
if (!isBase64(input)) {
|
|
67
|
+
throw new StackAssertionError("Invalid base64 string");
|
|
68
|
+
}
|
|
69
|
+
return new Uint8Array(atob(input).split("").map((char) => char.charCodeAt(0)));
|
|
70
|
+
}
|
|
71
|
+
export function isBase32(input) {
|
|
72
|
+
for (const char of input) {
|
|
73
|
+
if (char === " ")
|
|
74
|
+
continue;
|
|
75
|
+
if (!crockfordAlphabet.includes(char)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
export function isBase64(input) {
|
|
82
|
+
const regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
|
|
83
|
+
return regex.test(input);
|
|
84
|
+
}
|
package/dist/utils/crypto.d.ts
CHANGED
package/dist/utils/crypto.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { encodeBase32 } from "./bytes";
|
|
2
|
+
import { StackAssertionError } from "./errors";
|
|
2
3
|
import { globalVar } from "./globals";
|
|
4
|
+
export function generateRandomValues(array) {
|
|
5
|
+
if (!globalVar.crypto) {
|
|
6
|
+
throw new StackAssertionError("Crypto API is not available in this environment. Are you using an old browser?");
|
|
7
|
+
}
|
|
8
|
+
if (!globalVar.crypto.getRandomValues) {
|
|
9
|
+
throw new StackAssertionError("crypto.getRandomValues is not available in this environment. Are you using an old browser?");
|
|
10
|
+
}
|
|
11
|
+
return globalVar.crypto.getRandomValues(array);
|
|
12
|
+
}
|
|
3
13
|
/**
|
|
4
14
|
* Generates a secure alphanumeric string using the system's cryptographically secure
|
|
5
15
|
* random number generator.
|
|
@@ -7,7 +17,7 @@ import { globalVar } from "./globals";
|
|
|
7
17
|
export function generateSecureRandomString(minBitsOfEntropy = 224) {
|
|
8
18
|
const base32CharactersCount = Math.ceil(minBitsOfEntropy / 5);
|
|
9
19
|
const bytesCount = Math.ceil(base32CharactersCount * 5 / 8);
|
|
10
|
-
const randomBytes =
|
|
20
|
+
const randomBytes = generateRandomValues(new Uint8Array(bytesCount));
|
|
11
21
|
const str = encodeBase32(randomBytes);
|
|
12
22
|
return str.slice(str.length - base32CharactersCount).toLowerCase();
|
|
13
23
|
}
|
package/dist/utils/results.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export type AsyncResult<T, E = unknown, P = void> = Result<T, E> | ({
|
|
|
12
12
|
});
|
|
13
13
|
export declare const Result: {
|
|
14
14
|
fromThrowing: typeof fromThrowing;
|
|
15
|
+
fromThrowingAsync: typeof fromThrowingAsync;
|
|
15
16
|
fromPromise: typeof promiseToResult;
|
|
16
17
|
ok<T>(data: T): {
|
|
17
18
|
status: "ok";
|
|
@@ -60,6 +61,7 @@ declare function pending<P>(progress: P): AsyncResult<never, never, P> & {
|
|
|
60
61
|
};
|
|
61
62
|
declare function promiseToResult<T>(promise: Promise<T>): Promise<Result<T>>;
|
|
62
63
|
declare function fromThrowing<T>(fn: () => T): Result<T, unknown>;
|
|
64
|
+
declare function fromThrowingAsync<T>(fn: () => Promise<T>): Promise<Result<T, unknown>>;
|
|
63
65
|
declare function mapResult<T, U, E = unknown, P = unknown>(result: Result<T, E>, fn: (data: T) => U): Result<U, E>;
|
|
64
66
|
declare function mapResult<T, U, E = unknown, P = unknown>(result: AsyncResult<T, E, P>, fn: (data: T) => U): AsyncResult<U, E, P>;
|
|
65
67
|
declare class RetryError extends AggregateError {
|
package/dist/utils/results.js
CHANGED
|
@@ -2,6 +2,7 @@ import { wait } from "./promises";
|
|
|
2
2
|
import { deindent } from "./strings";
|
|
3
3
|
export const Result = {
|
|
4
4
|
fromThrowing,
|
|
5
|
+
fromThrowingAsync,
|
|
5
6
|
fromPromise: promiseToResult,
|
|
6
7
|
ok(data) {
|
|
7
8
|
return {
|
|
@@ -71,6 +72,14 @@ function fromThrowing(fn) {
|
|
|
71
72
|
return Result.error(error);
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
async function fromThrowingAsync(fn) {
|
|
76
|
+
try {
|
|
77
|
+
return Result.ok(await fn());
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return Result.error(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
74
83
|
function mapResult(result, fn) {
|
|
75
84
|
if (result.status === "error")
|
|
76
85
|
return {
|
package/dist/utils/strings.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findLastIndex } from "./arrays";
|
|
1
|
+
import { findLastIndex, unique } from "./arrays";
|
|
2
2
|
import { StackAssertionError } from "./errors";
|
|
3
3
|
import { filterUndefined } from "./objects";
|
|
4
4
|
export function typedToLowercase(s) {
|
|
@@ -207,6 +207,9 @@ export function nicify(value, options = {}) {
|
|
|
207
207
|
return `[${resValues.join(", ")}]`;
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
+
if (value instanceof URL) {
|
|
211
|
+
return `URL(${JSON.stringify(value.toString())})`;
|
|
212
|
+
}
|
|
210
213
|
const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : (nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name);
|
|
211
214
|
const constructorString = constructorName ? `${nicifyPropertyString(constructorName)} ` : "";
|
|
212
215
|
const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));
|
|
@@ -256,7 +259,16 @@ function nicifyPropertyString(str) {
|
|
|
256
259
|
return JSON.stringify(str);
|
|
257
260
|
}
|
|
258
261
|
function getNicifiableKeys(value) {
|
|
259
|
-
|
|
262
|
+
const overridden = ("getNicifiableKeys" in value ? value.getNicifiableKeys?.bind(value) : null)?.();
|
|
263
|
+
if (overridden != null)
|
|
264
|
+
return overridden;
|
|
265
|
+
const keys = Object.keys(value).sort();
|
|
266
|
+
if (value instanceof Error) {
|
|
267
|
+
if (value.cause)
|
|
268
|
+
keys.unshift("cause");
|
|
269
|
+
keys.unshift("message", "stack");
|
|
270
|
+
}
|
|
271
|
+
return unique(keys);
|
|
260
272
|
}
|
|
261
273
|
function getNicifiableEntries(value) {
|
|
262
274
|
const recordLikes = [Headers];
|
package/dist/utils/uuids.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { generateRandomValues } from "./crypto";
|
|
2
2
|
export function generateUuid() {
|
|
3
3
|
// crypto.randomUuid is not supported in all browsers, so this is a polyfill
|
|
4
|
-
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => (+c ^
|
|
4
|
+
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => (+c ^ generateRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16));
|
|
5
5
|
}
|
|
6
6
|
export function isUuid(str) {
|
|
7
7
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(str);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackframe/stack-shared",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.18",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"jose": "^5.2.2",
|
|
37
37
|
"oauth4webapi": "^2.10.3",
|
|
38
38
|
"uuid": "^9.0.1",
|
|
39
|
-
"@stackframe/stack-sc": "2.5.
|
|
39
|
+
"@stackframe/stack-sc": "2.5.18"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"rimraf": "^5.0.5",
|