@stackframe/stack-shared 2.5.17 → 2.5.19

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/interface/adminInterface.d.ts +1 -0
  3. package/dist/interface/adminInterface.js +5 -0
  4. package/dist/interface/clientInterface.d.ts +33 -0
  5. package/dist/interface/clientInterface.js +96 -4
  6. package/dist/interface/crud/current-user.d.ts +8 -0
  7. package/dist/interface/crud/current-user.js +2 -0
  8. package/dist/interface/crud/email-templates.d.ts +5 -5
  9. package/dist/interface/crud/email-templates.js +1 -1
  10. package/dist/interface/crud/projects.d.ts +42 -7
  11. package/dist/interface/crud/projects.js +22 -9
  12. package/dist/interface/crud/team-invitation-details.d.ts +25 -0
  13. package/dist/interface/crud/team-invitation-details.js +17 -0
  14. package/dist/interface/crud/team-member-profiles.d.ts +166 -0
  15. package/dist/interface/crud/team-member-profiles.js +5 -0
  16. package/dist/interface/crud/users.d.ts +16 -0
  17. package/dist/interface/crud/users.js +7 -2
  18. package/dist/interface/crud-deprecated/email-templates.d.ts +5 -5
  19. package/dist/interface/crud-deprecated/email-templates.js +1 -1
  20. package/dist/interface/serverInterface.d.ts +12 -4
  21. package/dist/interface/serverInterface.js +27 -4
  22. package/dist/interface/webhooks.d.ts +4 -0
  23. package/dist/known-errors.d.ts +12 -0
  24. package/dist/known-errors.js +29 -1
  25. package/dist/schema-fields.d.ts +5 -0
  26. package/dist/schema-fields.js +22 -10
  27. package/dist/utils/bytes.d.ts +4 -0
  28. package/dist/utils/bytes.js +36 -0
  29. package/dist/utils/crypto.d.ts +1 -1
  30. package/dist/utils/errors.d.ts +7 -1
  31. package/dist/utils/errors.js +7 -1
  32. package/package.json +2 -2
@@ -206,7 +206,7 @@ const AdminAccessTokenExpired = createKnownErrorConstructor(InvalidAdminAccessTo
206
206
  ], () => []);
207
207
  const InvalidProjectForAdminAccessToken = createKnownErrorConstructor(InvalidAdminAccessToken, "INVALID_PROJECT_FOR_ADMIN_ACCESS_TOKEN", () => [
208
208
  401,
209
- "Admin access token not valid for this project.",
209
+ "Admin access tokens must be created on the internal project.",
210
210
  ], () => []);
211
211
  const AdminAccessTokenIsNotAdmin = createKnownErrorConstructor(InvalidAdminAccessToken, "ADMIN_ACCESS_TOKEN_IS_NOT_ADMIN", () => [
212
212
  401,
@@ -320,6 +320,10 @@ const ProjectNotFound = createKnownErrorConstructor(KnownError, "PROJECT_NOT_FOU
320
320
  },
321
321
  ];
322
322
  }, (json) => [json.project_id]);
323
+ const SignUpNotEnabled = createKnownErrorConstructor(KnownError, "SIGN_UP_NOT_ENABLED", () => [
324
+ 400,
325
+ "Creation of new accounts is not enabled for this project. Please ask the project owner to enable it.",
326
+ ], () => []);
323
327
  const PasswordAuthenticationNotEnabled = createKnownErrorConstructor(KnownError, "PASSWORD_AUTHENTICATION_NOT_ENABLED", () => [
324
328
  400,
325
329
  "Password authentication is not enabled for this project.",
@@ -471,6 +475,17 @@ const OAuthProviderNotFoundOrNotEnabled = createKnownErrorConstructor(KnownError
471
475
  400,
472
476
  "The OAuth provider is not found or not enabled.",
473
477
  ], () => []);
478
+ const MultiFactorAuthenticationRequired = createKnownErrorConstructor(KnownError, "MULTI_FACTOR_AUTHENTICATION_REQUIRED", (attemptCode) => [
479
+ 400,
480
+ `Multi-factor authentication is required for this user.`,
481
+ {
482
+ attempt_code: attemptCode,
483
+ },
484
+ ], (json) => [json.attempt_code]);
485
+ const InvalidTotpCode = createKnownErrorConstructor(KnownError, "INVALID_TOTP_CODE", () => [
486
+ 400,
487
+ "The TOTP code is invalid. Please try again.",
488
+ ], () => []);
474
489
  const UserAuthenticationRequired = createKnownErrorConstructor(KnownError, "USER_AUTHENTICATION_REQUIRED", () => [
475
490
  401,
476
491
  "User authentication required for this endpoint.",
@@ -488,6 +503,15 @@ const TeamPermissionRequired = createKnownErrorConstructor(KnownError, "TEAM_PER
488
503
  permission_id: permissionId,
489
504
  },
490
505
  ], (json) => [json.team_id, json.user_id, json.permission_id]);
506
+ const TeamPermissionNotFound = createKnownErrorConstructor(KnownError, "TEAM_PERMISSION_NOT_FOUND", (teamId, userId, permissionId) => [
507
+ 401,
508
+ `User ${userId} does not have permission ${permissionId} in team ${teamId}.`,
509
+ {
510
+ team_id: teamId,
511
+ user_id: userId,
512
+ permission_id: permissionId,
513
+ },
514
+ ], (json) => [json.team_id, json.user_id, json.permission_id]);
491
515
  const InvalidSharedOAuthProviderId = createKnownErrorConstructor(KnownError, "INVALID_SHARED_OAUTH_PROVIDER_ID", (providerId) => [
492
516
  400,
493
517
  `The shared OAuth provider with ID ${providerId} is not valid.`,
@@ -552,6 +576,7 @@ export const KnownErrors = {
552
576
  UserNotFound,
553
577
  ApiKeyNotFound,
554
578
  ProjectNotFound,
579
+ SignUpNotEnabled,
555
580
  PasswordAuthenticationNotEnabled,
556
581
  EmailPasswordMismatch,
557
582
  RedirectUrlNotWhitelisted,
@@ -582,12 +607,15 @@ export const KnownErrors = {
582
607
  UserAlreadyConnectedToAnotherOAuthConnection,
583
608
  OuterOAuthTimeout,
584
609
  OAuthProviderNotFoundOrNotEnabled,
610
+ MultiFactorAuthenticationRequired,
611
+ InvalidTotpCode,
585
612
  UserAuthenticationRequired,
586
613
  TeamMembershipAlreadyExists,
587
614
  TeamPermissionRequired,
588
615
  InvalidSharedOAuthProviderId,
589
616
  InvalidStandardOAuthProviderId,
590
617
  InvalidAuthorizationCode,
618
+ TeamPermissionNotFound,
591
619
  };
592
620
  // ensure that all known error codes are unique
593
621
  const knownErrorCodes = new Set();
@@ -22,6 +22,7 @@ export declare const jsonSchema: yup.MixedSchema<{} | null, yup.AnyObject, undef
22
22
  export declare const jsonStringSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
23
23
  export declare const jsonStringOrEmptySchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
24
24
  export declare const emailSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
25
+ export declare const base64Schema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
25
26
  export declare const clientOrHigherAuthTypeSchema: yup.StringSchema<"client" | "server" | "admin" | undefined, yup.AnyObject, undefined, "">;
26
27
  export declare const serverOrHigherAuthTypeSchema: yup.StringSchema<"server" | "admin" | undefined, yup.AnyObject, undefined, "">;
27
28
  export declare const adminAuthTypeSchema: yup.StringSchema<"admin" | undefined, yup.AnyObject, undefined, "">;
@@ -35,6 +36,8 @@ export declare const projectConfigIdSchema: yup.StringSchema<string | undefined,
35
36
  export declare const projectAllowLocalhostSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
36
37
  export declare const projectCreateTeamOnSignUpSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
37
38
  export declare const projectMagicLinkEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
39
+ export declare const projectClientTeamCreationEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
40
+ export declare const projectSignUpEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
38
41
  export declare const projectCredentialEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
39
42
  export declare const oauthIdSchema: yup.StringSchema<"google" | "github" | "facebook" | "microsoft" | "spotify" | undefined, yup.AnyObject, undefined, "">;
40
43
  export declare const oauthEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
@@ -98,6 +101,8 @@ export declare const teamProfileImageUrlSchema: yup.StringSchema<string | undefi
98
101
  export declare const teamClientMetadataSchema: yup.MixedSchema<{} | null, yup.AnyObject, undefined, "">;
99
102
  export declare const teamServerMetadataSchema: yup.MixedSchema<{} | null, yup.AnyObject, undefined, "">;
100
103
  export declare const teamCreatedAtMillisSchema: yup.NumberSchema<number | undefined, yup.AnyObject, undefined, "">;
104
+ export declare const teamInvitationEmailSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
105
+ export declare const teamInvitationCallbackUrlSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
101
106
  export declare const teamMemberDisplayNameSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
102
107
  export declare const teamMemberProfileImageUrlSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
103
108
  export declare function yupRequiredWhen<S extends yup.AnyObject>(schema: S, triggerName: string, isValue: any): S;
@@ -1,4 +1,5 @@
1
1
  import * as yup from "yup";
2
+ import { isBase64 } from "./utils/bytes";
2
3
  import { StackAssertionError } from "./utils/errors";
3
4
  import { allProviders } from "./utils/oauth";
4
5
  import { isUuid } from "./utils/uuids";
@@ -39,15 +40,17 @@ export function yupTuple(...args) {
39
40
  export function yupObject(...args) {
40
41
  const object = yup.object(...args).test('no-unknown-object-properties', ({ path }) => `${path} contains unknown properties`, (value, context) => {
41
42
  if (context.options.context?.noUnknownPathPrefixes?.some((prefix) => context.path.startsWith(prefix))) {
42
- const availableKeys = new Set(Object.keys(context.schema.fields));
43
- const unknownKeys = Object.keys(value ?? {}).filter(key => !availableKeys.has(key));
44
- if (unknownKeys.length > 0) {
45
- // TODO "did you mean XYZ"
46
- return context.createError({
47
- message: `${context.path} contains unknown properties: ${unknownKeys.join(', ')}`,
48
- path: context.path,
49
- params: { unknownKeys },
50
- });
43
+ if (context.schema.spec.noUnknown !== false) {
44
+ const availableKeys = new Set(Object.keys(context.schema.fields));
45
+ const unknownKeys = Object.keys(value ?? {}).filter(key => !availableKeys.has(key));
46
+ if (unknownKeys.length > 0) {
47
+ // TODO "did you mean XYZ"
48
+ return context.createError({
49
+ message: `${context.path} contains unknown properties: ${unknownKeys.join(', ')}`,
50
+ path: context.path,
51
+ params: { unknownKeys },
52
+ });
53
+ }
51
54
  }
52
55
  }
53
56
  return true;
@@ -124,6 +127,11 @@ export const jsonStringOrEmptySchema = yupString().test("json", "Invalid JSON fo
124
127
  }
125
128
  });
126
129
  export const emailSchema = yupString().email();
130
+ export const base64Schema = yupString().test("is-base64", "Invalid base64 format", (value) => {
131
+ if (value == null)
132
+ return true;
133
+ return isBase64(value);
134
+ });
127
135
  // Request auth
128
136
  export const clientOrHigherAuthTypeSchema = yupString().oneOf(['client', 'server', 'admin']);
129
137
  export const serverOrHigherAuthTypeSchema = yupString().oneOf(['server', 'admin']);
@@ -140,6 +148,8 @@ export const projectConfigIdSchema = yupString().meta({ openapiField: { descript
140
148
  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 } });
141
149
  export const projectCreateTeamOnSignUpSchema = yupBoolean().meta({ openapiField: { description: 'Whether a team should be created for each user that signs up', exampleValue: true } });
142
150
  export const projectMagicLinkEnabledSchema = yupBoolean().meta({ openapiField: { description: 'Whether magic link authentication is enabled for this project', exampleValue: true } });
151
+ export const projectClientTeamCreationEnabledSchema = yupBoolean().meta({ openapiField: { description: 'Whether client users can create teams', exampleValue: true } });
152
+ 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 } });
143
153
  export const projectCredentialEnabledSchema = yupBoolean().meta({ openapiField: { description: 'Whether email password authentication is enabled for this project', exampleValue: true } });
144
154
  // Project OAuth config
145
155
  export const oauthIdSchema = yupString().oneOf(allProviders).meta({ openapiField: { description: `OAuth provider ID, one of ${allProviders.map(x => `\`${x}\``).join(', ')}`, exampleValue: 'google' } });
@@ -180,7 +190,7 @@ export const userIdSchema = yupString().uuid().meta({ openapiField: { descriptio
180
190
  export const primaryEmailSchema = emailSchema.meta({ openapiField: { description: 'Primary email', exampleValue: 'johndoe@example.com' } });
181
191
  export const primaryEmailVerifiedSchema = yupBoolean().meta({ openapiField: { description: 'Whether the primary email has been verified to belong to this user', exampleValue: true } });
182
192
  export const userDisplayNameSchema = yupString().nullable().meta({ openapiField: { description: _displayNameDescription('user'), exampleValue: 'John Doe' } });
183
- export const selectedTeamIdSchema = yupString().meta({ openapiField: { description: 'ID of the team currently selected by the user', exampleValue: 'team-id' } });
193
+ export const selectedTeamIdSchema = yupString().uuid().meta({ openapiField: { description: 'ID of the team currently selected by the user', exampleValue: 'team-id' } });
184
194
  export const profileImageUrlSchema = yupString().meta({ openapiField: { description: _profileImageUrlDescription('user'), exampleValue: 'https://example.com/image.jpg' } });
185
195
  export const signedUpAtMillisSchema = yupNumber().meta({ openapiField: { description: _signedUpAtMillisDescription, exampleValue: 1630000000000 } });
186
196
  export const userClientMetadataSchema = jsonSchema.meta({ openapiField: { description: _clientMetaDataDescription('user'), exampleValue: { key: 'value' } } });
@@ -232,6 +242,8 @@ export const teamProfileImageUrlSchema = yupString().meta({ openapiField: { desc
232
242
  export const teamClientMetadataSchema = jsonSchema.meta({ openapiField: { description: _clientMetaDataDescription('team'), exampleValue: { key: 'value' } } });
233
243
  export const teamServerMetadataSchema = jsonSchema.meta({ openapiField: { description: _serverMetaDataDescription('team'), exampleValue: { key: 'value' } } });
234
244
  export const teamCreatedAtMillisSchema = yupNumber().meta({ openapiField: { description: _createdAtMillisDescription('team'), exampleValue: 1630000000000 } });
245
+ export const teamInvitationEmailSchema = emailSchema.meta({ openapiField: { description: 'The email to sign in with.', exampleValue: 'johndoe@example.com' } });
246
+ 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' } });
235
247
  // Team member profiles
236
248
  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' } });
237
249
  export const teamMemberProfileImageUrlSchema = yupString().meta({ openapiField: { description: _profileImageUrlDescription('team member'), exampleValue: 'https://example.com/image.jpg' } });
@@ -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;
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- export declare function generateRandomValues(array: Uint8Array): any;
1
+ export declare function generateRandomValues(array: Uint8Array): typeof array;
2
2
  /**
3
3
  * Generates a secure alphanumeric string using the system's cryptographically secure
4
4
  * random number generator.
@@ -2,10 +2,16 @@ import { Json } from "./json";
2
2
  export declare function throwErr(errorMessage: string, extraData?: any): never;
3
3
  export declare function throwErr(error: Error): never;
4
4
  export declare function throwErr(...args: StatusErrorConstructorParameters): never;
5
- export declare class StackAssertionError extends Error {
5
+ export declare class StackAssertionError extends Error implements ErrorWithCustomCapture {
6
6
  readonly extraData?: Record<string, any> | undefined;
7
7
  constructor(message: string, extraData?: Record<string, any> | undefined, options?: ErrorOptions);
8
+ customCaptureExtraArgs: {
9
+ cause?: {} | undefined;
10
+ }[];
8
11
  }
12
+ export type ErrorWithCustomCapture = {
13
+ customCaptureExtraArgs: any[];
14
+ };
9
15
  export declare function registerErrorSink(sink: (location: string, error: unknown) => void): void;
10
16
  export declare function captureError(location: string, error: unknown): void;
11
17
  type Status = {
@@ -16,6 +16,12 @@ export class StackAssertionError extends Error {
16
16
  const disclaimer = `\n\nThis is likely an error in Stack. Please make sure you are running the newest version and report it.`;
17
17
  super(`${message}${message.endsWith(disclaimer) ? "" : disclaimer}`, options);
18
18
  this.extraData = extraData;
19
+ this.customCaptureExtraArgs = [
20
+ {
21
+ ...this.extraData,
22
+ ...this.cause ? { cause: this.cause } : {},
23
+ },
24
+ ];
19
25
  }
20
26
  }
21
27
  StackAssertionError.prototype.name = "StackAssertionError";
@@ -35,7 +41,7 @@ registerErrorSink((location, error, ...extraArgs) => {
35
41
  });
36
42
  export function captureError(location, error) {
37
43
  for (const sink of errorSinks) {
38
- sink(location, error);
44
+ sink(location, error, ...error && (typeof error === 'object' || typeof error === 'function') && "customCaptureExtraArgs" in error && Array.isArray(error.customCaptureExtraArgs) ? error.customCaptureExtraArgs : []);
39
45
  }
40
46
  }
41
47
  export class StatusError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.5.17",
3
+ "version": "2.5.19",
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.17"
39
+ "@stackframe/stack-sc": "2.5.19"
40
40
  },
41
41
  "devDependencies": {
42
42
  "rimraf": "^5.0.5",