@stackframe/stack-shared 2.8.2 → 2.8.5

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 (40) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/config/format.d.ts +38 -0
  3. package/dist/config/format.js +224 -0
  4. package/dist/config/schema.d.ts +721 -0
  5. package/dist/config/schema.js +185 -0
  6. package/dist/crud.js +1 -1
  7. package/dist/interface/adminInterface.d.ts +7 -7
  8. package/dist/interface/adminInterface.js +7 -7
  9. package/dist/interface/clientInterface.d.ts +46 -4
  10. package/dist/interface/clientInterface.js +66 -2
  11. package/dist/interface/crud/current-user.d.ts +2 -2
  12. package/dist/interface/crud/{api-keys.d.ts → internal-api-keys.d.ts} +7 -7
  13. package/dist/interface/crud/{api-keys.js → internal-api-keys.js} +10 -10
  14. package/dist/interface/crud/project-api-keys.d.ts +188 -0
  15. package/dist/interface/crud/project-api-keys.js +76 -0
  16. package/dist/interface/crud/projects.d.ts +53 -20
  17. package/dist/interface/crud/projects.js +15 -5
  18. package/dist/interface/crud/team-member-profiles.d.ts +4 -4
  19. package/dist/interface/crud/users.d.ts +8 -8
  20. package/dist/interface/serverInterface.d.ts +2 -1
  21. package/dist/interface/serverInterface.js +4 -0
  22. package/dist/interface/webhooks.d.ts +2 -2
  23. package/dist/known-errors.d.ts +35 -1
  24. package/dist/known-errors.js +42 -4
  25. package/dist/schema-fields.d.ts +6 -5
  26. package/dist/schema-fields.js +36 -3
  27. package/dist/utils/api-keys.d.ts +23 -0
  28. package/dist/utils/api-keys.js +76 -0
  29. package/dist/utils/bytes.d.ts +3 -0
  30. package/dist/utils/bytes.js +55 -6
  31. package/dist/utils/caches.d.ts +10 -10
  32. package/dist/utils/hashes.d.ts +1 -1
  33. package/dist/utils/hashes.js +1 -3
  34. package/dist/utils/objects.d.ts +35 -2
  35. package/dist/utils/objects.js +128 -0
  36. package/dist/utils/promises.d.ts +1 -1
  37. package/dist/utils/promises.js +9 -10
  38. package/dist/utils/stores.d.ts +6 -6
  39. package/dist/utils/types.d.ts +17 -0
  40. package/package.json +2 -1
@@ -43,8 +43,8 @@ export declare const usersCrudServerReadSchema: import("yup").ObjectSchema<{
43
43
  client_read_only_metadata?: {} | null | undefined;
44
44
  server_metadata?: {} | null | undefined;
45
45
  id: string;
46
- created_at_millis: number;
47
46
  display_name: string;
47
+ created_at_millis: number;
48
48
  profile_image_url: string | null;
49
49
  } | null;
50
50
  selected_team_id: string | null;
@@ -96,13 +96,13 @@ export declare const usersCrudServerReadSchema: import("yup").ObjectSchema<{
96
96
  requires_totp_mfa: undefined;
97
97
  }, "">;
98
98
  export declare const usersCrudServerCreateSchema: import("yup").ObjectSchema<{
99
- primary_email: string | null | undefined;
100
99
  password: string | null | undefined;
101
100
  display_name: string | null | undefined;
102
101
  profile_image_url: string | null | undefined;
103
102
  client_metadata: {} | null | undefined;
104
103
  client_read_only_metadata: {} | null | undefined;
105
104
  server_metadata: {} | null | undefined;
105
+ primary_email: string | null | undefined;
106
106
  primary_email_verified: boolean | undefined;
107
107
  primary_email_auth_enabled: boolean | undefined;
108
108
  passkey_auth_enabled: boolean | undefined;
@@ -112,9 +112,9 @@ export declare const usersCrudServerCreateSchema: import("yup").ObjectSchema<{
112
112
  is_anonymous: boolean | undefined;
113
113
  } & {
114
114
  oauth_providers: {
115
- email: string | null;
116
115
  id: string;
117
116
  account_id: string;
117
+ email: string | null;
118
118
  }[] | undefined;
119
119
  is_anonymous: boolean | undefined;
120
120
  }, import("yup").AnyObject, {
@@ -148,8 +148,8 @@ export declare const usersCrud: import("../../crud").CrudSchemaFromOptions<{
148
148
  client_read_only_metadata?: {} | null | undefined;
149
149
  server_metadata?: {} | null | undefined;
150
150
  id: string;
151
- created_at_millis: number;
152
151
  display_name: string;
152
+ created_at_millis: number;
153
153
  profile_image_url: string | null;
154
154
  } | null;
155
155
  selected_team_id: string | null;
@@ -234,13 +234,13 @@ export declare const usersCrud: import("../../crud").CrudSchemaFromOptions<{
234
234
  is_anonymous: undefined;
235
235
  }, "">;
236
236
  serverCreateSchema: import("yup").ObjectSchema<{
237
- primary_email: string | null | undefined;
238
237
  password: string | null | undefined;
239
238
  display_name: string | null | undefined;
240
239
  profile_image_url: string | null | undefined;
241
240
  client_metadata: {} | null | undefined;
242
241
  client_read_only_metadata: {} | null | undefined;
243
242
  server_metadata: {} | null | undefined;
243
+ primary_email: string | null | undefined;
244
244
  primary_email_verified: boolean | undefined;
245
245
  primary_email_auth_enabled: boolean | undefined;
246
246
  passkey_auth_enabled: boolean | undefined;
@@ -250,9 +250,9 @@ export declare const usersCrud: import("../../crud").CrudSchemaFromOptions<{
250
250
  is_anonymous: boolean | undefined;
251
251
  } & {
252
252
  oauth_providers: {
253
- email: string | null;
254
253
  id: string;
255
254
  account_id: string;
255
+ email: string | null;
256
256
  }[] | undefined;
257
257
  is_anonymous: boolean | undefined;
258
258
  }, import("yup").AnyObject, {
@@ -316,8 +316,8 @@ export declare const userCreatedWebhookEvent: {
316
316
  client_read_only_metadata?: {} | null | undefined;
317
317
  server_metadata?: {} | null | undefined;
318
318
  id: string;
319
- created_at_millis: number;
320
319
  display_name: string;
320
+ created_at_millis: number;
321
321
  profile_image_url: string | null;
322
322
  } | null;
323
323
  selected_team_id: string | null;
@@ -387,8 +387,8 @@ export declare const userUpdatedWebhookEvent: {
387
387
  client_read_only_metadata?: {} | null | undefined;
388
388
  server_metadata?: {} | null | undefined;
389
389
  id: string;
390
- created_at_millis: number;
391
390
  display_name: string;
391
+ created_at_millis: number;
392
392
  profile_image_url: string | null;
393
393
  } | null;
394
394
  selected_team_id: string | null;
@@ -5,11 +5,11 @@ import { ClientInterfaceOptions, StackClientInterface } from "./clientInterface"
5
5
  import { ContactChannelsCrud } from "./crud/contact-channels";
6
6
  import { CurrentUserCrud } from "./crud/current-user";
7
7
  import { ConnectedAccountAccessTokenCrud } from "./crud/oauth";
8
+ import { ProjectPermissionsCrud } from "./crud/project-permissions";
8
9
  import { SessionsCrud } from "./crud/sessions";
9
10
  import { TeamInvitationCrud } from "./crud/team-invitation";
10
11
  import { TeamMemberProfilesCrud } from "./crud/team-member-profiles";
11
12
  import { TeamMembershipsCrud } from "./crud/team-memberships";
12
- import { ProjectPermissionsCrud } from "./crud/project-permissions";
13
13
  import { TeamPermissionsCrud } from "./crud/team-permissions";
14
14
  import { TeamsCrud } from "./crud/teams";
15
15
  import { UsersCrud } from "./crud/users";
@@ -66,6 +66,7 @@ export declare class StackServerInterface extends StackClientInterface {
66
66
  listServerTeams(options?: {
67
67
  userId?: string;
68
68
  }): Promise<TeamsCrud['Server']['Read'][]>;
69
+ getServerTeam(teamId: string): Promise<TeamsCrud['Server']['Read']>;
69
70
  listServerTeamUsers(teamId: string): Promise<UsersCrud['Server']['Read'][]>;
70
71
  createServerTeam(data: TeamsCrud['Server']['Create']): Promise<TeamsCrud['Server']['Read']>;
71
72
  updateServerTeam(teamId: string, data: TeamsCrud['Server']['Update']): Promise<TeamsCrud['Server']['Read']>;
@@ -123,6 +123,10 @@ export class StackServerInterface extends StackClientInterface {
123
123
  const result = await response.json();
124
124
  return result.items;
125
125
  }
126
+ async getServerTeam(teamId) {
127
+ const response = await this.sendServerRequest(`/teams/${teamId}`, {}, null);
128
+ return await response.json();
129
+ }
126
130
  async listServerTeamUsers(teamId) {
127
131
  const response = await this.sendServerRequest(`/users?team_id=${teamId}`, {}, null);
128
132
  const result = await response.json();
@@ -21,8 +21,8 @@ export declare const webhookEvents: readonly [{
21
21
  client_read_only_metadata?: {} | null | undefined;
22
22
  server_metadata?: {} | null | undefined;
23
23
  id: string;
24
- created_at_millis: number;
25
24
  display_name: string;
25
+ created_at_millis: number;
26
26
  profile_image_url: string | null;
27
27
  } | null;
28
28
  selected_team_id: string | null;
@@ -91,8 +91,8 @@ export declare const webhookEvents: readonly [{
91
91
  client_read_only_metadata?: {} | null | undefined;
92
92
  server_metadata?: {} | null | undefined;
93
93
  id: string;
94
- created_at_millis: number;
95
94
  display_name: string;
95
+ created_at_millis: number;
96
96
  profile_image_url: string | null;
97
97
  } | null;
98
98
  selected_team_id: string | null;
@@ -69,6 +69,15 @@ export declare const KnownErrors: {
69
69
  PermissionIdAlreadyExists: KnownErrorConstructor<KnownError & KnownErrorBrand<"PERMISSION_ID_ALREADY_EXISTS">, [permissionId: string]> & {
70
70
  errorCode: "PERMISSION_ID_ALREADY_EXISTS";
71
71
  };
72
+ CliAuthError: KnownErrorConstructor<KnownError & KnownErrorBrand<"CLI_AUTH_ERROR">, [message: string]> & {
73
+ errorCode: "CLI_AUTH_ERROR";
74
+ };
75
+ CliAuthExpiredError: KnownErrorConstructor<KnownError & KnownErrorBrand<"CLI_AUTH_EXPIRED_ERROR">, [message?: string | undefined]> & {
76
+ errorCode: "CLI_AUTH_EXPIRED_ERROR";
77
+ };
78
+ CliAuthUsedError: KnownErrorConstructor<KnownError & KnownErrorBrand<"CLI_AUTH_USED_ERROR">, [message?: string | undefined]> & {
79
+ errorCode: "CLI_AUTH_USED_ERROR";
80
+ };
72
81
  InvalidProjectAuthentication: KnownErrorConstructor<KnownError & KnownErrorBrand<"PROJECT_AUTHENTICATION_ERROR"> & {
73
82
  constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
74
83
  } & KnownErrorBrand<"INVALID_PROJECT_AUTHENTICATION">, [statusCode: number, humanReadableMessage: string, details?: Json | undefined]> & {
@@ -235,9 +244,16 @@ export declare const KnownErrors: {
235
244
  UserNotFound: KnownErrorConstructor<KnownError & KnownErrorBrand<"USER_NOT_FOUND">, []> & {
236
245
  errorCode: "USER_NOT_FOUND";
237
246
  };
238
- ApiKeyNotFound: KnownErrorConstructor<KnownError & KnownErrorBrand<"API_KEY_NOT_FOUND">, []> & {
247
+ ApiKeyNotFound: KnownErrorConstructor<KnownError & KnownErrorBrand<"API_KEY_NOT_VALID"> & {
248
+ constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
249
+ } & KnownErrorBrand<"API_KEY_NOT_FOUND">, []> & {
239
250
  errorCode: "API_KEY_NOT_FOUND";
240
251
  };
252
+ PublicApiKeyCannotBeRevoked: KnownErrorConstructor<KnownError & KnownErrorBrand<"API_KEY_NOT_VALID"> & {
253
+ constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
254
+ } & KnownErrorBrand<"PUBLIC_API_KEY_CANNOT_BE_REVOKED">, []> & {
255
+ errorCode: "PUBLIC_API_KEY_CANNOT_BE_REVOKED";
256
+ };
241
257
  ProjectNotFound: KnownErrorConstructor<KnownError & KnownErrorBrand<"PROJECT_NOT_FOUND">, [projectId: string]> & {
242
258
  errorCode: "PROJECT_NOT_FOUND";
243
259
  };
@@ -403,5 +419,23 @@ export declare const KnownErrors: {
403
419
  InvalidPollingCodeError: KnownErrorConstructor<KnownError & KnownErrorBrand<"INVALID_POLLING_CODE">, [details?: Json | undefined]> & {
404
420
  errorCode: "INVALID_POLLING_CODE";
405
421
  };
422
+ ApiKeyNotValid: KnownErrorConstructor<KnownError & KnownErrorBrand<"API_KEY_NOT_VALID">, [statusCode: number, humanReadableMessage: string, details?: Json | undefined]> & {
423
+ errorCode: "API_KEY_NOT_VALID";
424
+ };
425
+ ApiKeyExpired: KnownErrorConstructor<KnownError & KnownErrorBrand<"API_KEY_NOT_VALID"> & {
426
+ constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
427
+ } & KnownErrorBrand<"API_KEY_EXPIRED">, []> & {
428
+ errorCode: "API_KEY_EXPIRED";
429
+ };
430
+ ApiKeyRevoked: KnownErrorConstructor<KnownError & KnownErrorBrand<"API_KEY_NOT_VALID"> & {
431
+ constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
432
+ } & KnownErrorBrand<"API_KEY_REVOKED">, []> & {
433
+ errorCode: "API_KEY_REVOKED";
434
+ };
435
+ WrongApiKeyType: KnownErrorConstructor<KnownError & KnownErrorBrand<"API_KEY_NOT_VALID"> & {
436
+ constructorArgs: [statusCode: number, humanReadableMessage: string, details?: Json | undefined];
437
+ } & KnownErrorBrand<"WRONG_API_KEY_TYPE">, [expectedType: string, actualType: string]> & {
438
+ errorCode: "WRONG_API_KEY_TYPE";
439
+ };
406
440
  };
407
441
  export {};
@@ -293,10 +293,6 @@ const UserNotFound = createKnownErrorConstructor(KnownError, "USER_NOT_FOUND", (
293
293
  404,
294
294
  "User not found.",
295
295
  ], () => []);
296
- const ApiKeyNotFound = createKnownErrorConstructor(KnownError, "API_KEY_NOT_FOUND", () => [
297
- 404,
298
- "API key not found.",
299
- ], () => []);
300
296
  const ProjectNotFound = createKnownErrorConstructor(KnownError, "PROJECT_NOT_FOUND", (projectId) => {
301
297
  if (typeof projectId !== "string")
302
298
  throw new StackAssertionError("projectId of KnownErrors.ProjectNotFound must be a string");
@@ -570,6 +566,40 @@ const InvalidPollingCodeError = createKnownErrorConstructor(KnownError, "INVALID
570
566
  "The polling code is invalid or does not exist.",
571
567
  details,
572
568
  ], (json) => [json]);
569
+ const CliAuthError = createKnownErrorConstructor(KnownError, "CLI_AUTH_ERROR", (message) => [
570
+ 400,
571
+ message,
572
+ ], (json) => [json.message]);
573
+ const CliAuthExpiredError = createKnownErrorConstructor(KnownError, "CLI_AUTH_EXPIRED_ERROR", (message = "CLI authentication request expired. Please try again.") => [
574
+ 400,
575
+ message,
576
+ ], (json) => [json.message]);
577
+ const CliAuthUsedError = createKnownErrorConstructor(KnownError, "CLI_AUTH_USED_ERROR", (message = "This authentication token has already been used.") => [
578
+ 400,
579
+ message,
580
+ ], (json) => [json.message]);
581
+ const ApiKeyNotValid = createKnownErrorConstructor(KnownError, "API_KEY_NOT_VALID", "inherit", "inherit");
582
+ const ApiKeyExpired = createKnownErrorConstructor(ApiKeyNotValid, "API_KEY_EXPIRED", () => [
583
+ 401,
584
+ "API key has expired.",
585
+ ], () => []);
586
+ const ApiKeyRevoked = createKnownErrorConstructor(ApiKeyNotValid, "API_KEY_REVOKED", () => [
587
+ 401,
588
+ "API key has been revoked.",
589
+ ], () => []);
590
+ const WrongApiKeyType = createKnownErrorConstructor(ApiKeyNotValid, "WRONG_API_KEY_TYPE", (expectedType, actualType) => [
591
+ 400,
592
+ `This endpoint is for ${expectedType} API keys, but a ${actualType} API key was provided.`,
593
+ { expected_type: expectedType, actual_type: actualType },
594
+ ], (json) => [json.expected_type, json.actual_type]);
595
+ const ApiKeyNotFound = createKnownErrorConstructor(ApiKeyNotValid, "API_KEY_NOT_FOUND", () => [
596
+ 404,
597
+ "API key not found.",
598
+ ], () => []);
599
+ const PublicApiKeyCannotBeRevoked = createKnownErrorConstructor(ApiKeyNotValid, "PUBLIC_API_KEY_CANNOT_BE_REVOKED", () => [
600
+ 400,
601
+ "Public API keys cannot be revoked by the secretscanner endpoint.",
602
+ ], () => []);
573
603
  const PermissionIdAlreadyExists = createKnownErrorConstructor(KnownError, "PERMISSION_ID_ALREADY_EXISTS", (permissionId) => [
574
604
  400,
575
605
  `Permission with ID "${permissionId}" already exists. Choose a different ID.`,
@@ -585,6 +615,9 @@ export const KnownErrors = {
585
615
  AllOverloadsFailed,
586
616
  ProjectAuthenticationError,
587
617
  PermissionIdAlreadyExists,
618
+ CliAuthError,
619
+ CliAuthExpiredError,
620
+ CliAuthUsedError,
588
621
  InvalidProjectAuthentication,
589
622
  ProjectKeyWithoutAccessType,
590
623
  InvalidAccessType,
@@ -622,6 +655,7 @@ export const KnownErrors = {
622
655
  UserIdDoesNotExist,
623
656
  UserNotFound,
624
657
  ApiKeyNotFound,
658
+ PublicApiKeyCannotBeRevoked,
625
659
  ProjectNotFound,
626
660
  SignUpNotEnabled,
627
661
  PasswordAuthenticationNotEnabled,
@@ -673,6 +707,10 @@ export const KnownErrors = {
673
707
  OAuthProviderAccessDenied,
674
708
  ContactChannelAlreadyUsedForAuthBySomeoneElse,
675
709
  InvalidPollingCodeError,
710
+ ApiKeyNotValid,
711
+ ApiKeyExpired,
712
+ ApiKeyRevoked,
713
+ WrongApiKeyType,
676
714
  };
677
715
  // ensure that all known error codes are unique
678
716
  const knownErrorCodes = new Set();
@@ -5,8 +5,8 @@ declare module "yup" {
5
5
  empty(): StringSchema<TType, TContext, TDefault, TFlags>;
6
6
  }
7
7
  interface Schema<TType, TContext, TDefault, TFlags> {
8
- getNested<K extends keyof TType>(path: K): yup.Schema<TType[K], TContext, TDefault, TFlags>;
9
- concat<U extends yup.AnySchema>(schema: U): yup.Schema<Omit<TType, keyof yup.InferType<U>> & yup.InferType<U>, TContext, TDefault, TFlags>;
8
+ getNested<K extends keyof NonNullable<TType>>(path: K): yup.Schema<NonNullable<TType>[K], TContext, TDefault, TFlags>;
9
+ concat<U extends yup.AnySchema>(schema: U): yup.Schema<Omit<NonNullable<TType>, keyof yup.InferType<U>> & yup.InferType<U> | (TType & (null | undefined)), TContext, TDefault, TFlags>;
10
10
  }
11
11
  }
12
12
  export declare function yupValidate<S extends yup.ISchema<any>>(schema: S, obj: unknown, options?: yup.ValidateOptions & {
@@ -27,6 +27,7 @@ export declare function yupTuple<T extends [unknown, ...unknown[]]>(...args: Par
27
27
  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, "">;
28
28
  export declare function yupNever(): yup.MixedSchema<never>;
29
29
  export declare function yupUnion<T extends yup.ISchema<any>[]>(...args: T): yup.MixedSchema<yup.InferType<T[number]>>;
30
+ export declare function yupRecord<K extends yup.StringSchema, T extends yup.AnySchema>(keySchema: K, valueSchema: T): yup.MixedSchema<Record<string, yup.InferType<T>>>;
30
31
  export declare function ensureObjectSchema<T extends yup.AnyObject>(schema: yup.Schema<T>): yup.ObjectSchema<T> & typeof schema;
31
32
  export declare const adaptSchema: yup.MixedSchema<typeof StackAdaptSentinel | undefined, yup.AnyObject, undefined, "">;
32
33
  /**
@@ -65,7 +66,7 @@ export declare const projectClientTeamCreationEnabledSchema: yup.BooleanSchema<b
65
66
  export declare const projectClientUserDeletionEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
66
67
  export declare const projectSignUpEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
67
68
  export declare const projectCredentialEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
68
- export declare const oauthIdSchema: yup.StringSchema<"google" | "github" | "microsoft" | "spotify" | "facebook" | "discord" | "gitlab" | "bitbucket" | "linkedin" | "apple" | "x" | undefined, yup.AnyObject, undefined, "">;
69
+ export declare const oauthIdSchema: yup.StringSchema<"apple" | "x" | "google" | "github" | "microsoft" | "spotify" | "facebook" | "discord" | "gitlab" | "bitbucket" | "linkedin" | undefined, yup.AnyObject, undefined, "">;
69
70
  export declare const oauthEnabledSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
70
71
  export declare const oauthTypeSchema: yup.StringSchema<"shared" | "standard" | undefined, yup.AnyObject, undefined, "">;
71
72
  export declare const oauthClientIdSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
@@ -99,7 +100,7 @@ export declare const userClientReadOnlyMetadataSchema: yup.MixedSchema<{} | null
99
100
  export declare const userServerMetadataSchema: yup.MixedSchema<{} | null, yup.AnyObject, undefined, "">;
100
101
  export declare const userOAuthProviderSchema: yup.ObjectSchema<{
101
102
  id: string;
102
- type: "google" | "github" | "microsoft" | "spotify" | "facebook" | "discord" | "gitlab" | "bitbucket" | "linkedin" | "apple" | "x";
103
+ type: "apple" | "x" | "google" | "github" | "microsoft" | "spotify" | "facebook" | "discord" | "gitlab" | "bitbucket" | "linkedin";
103
104
  provider_user_id: string;
104
105
  }, yup.AnyObject, {
105
106
  id: undefined;
@@ -130,7 +131,7 @@ export declare const signInResponseSchema: yup.ObjectSchema<{
130
131
  is_new_user: undefined;
131
132
  user_id: undefined;
132
133
  }, "">;
133
- export declare const teamSystemPermissions: readonly ["$update_team", "$delete_team", "$read_members", "$remove_members", "$invite_members"];
134
+ export declare const teamSystemPermissions: readonly ["$update_team", "$delete_team", "$read_members", "$remove_members", "$invite_members", "$manage_api_keys"];
134
135
  export declare const permissionDefinitionIdSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
135
136
  export declare const customPermissionDefinitionIdSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
136
137
  export declare const teamPermissionDescriptionSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
@@ -118,9 +118,9 @@ export function yupObject(...args) {
118
118
  if (unknownKeys.length > 0) {
119
119
  // TODO "did you mean XYZ"
120
120
  return context.createError({
121
- message: `${context.path} contains unknown properties: ${unknownKeys.join(', ')}`,
121
+ message: `${context.path || "Object"} contains unknown properties: ${unknownKeys.join(', ')}`,
122
122
  path: context.path,
123
- params: { unknownKeys },
123
+ params: { unknownKeys, availableKeys },
124
124
  });
125
125
  }
126
126
  }
@@ -147,7 +147,7 @@ export function yupUnion(...args) {
147
147
  const errors = [];
148
148
  for (const schema of args) {
149
149
  try {
150
- await schema.validate(value, context.options);
150
+ await yupValidate(schema, value, context.options);
151
151
  return true;
152
152
  }
153
153
  catch (e) {
@@ -160,6 +160,38 @@ export function yupUnion(...args) {
160
160
  });
161
161
  });
162
162
  }
163
+ export function yupRecord(keySchema, valueSchema) {
164
+ return yupObject().unknown(true).test('record', '${path} must be a record of valid values', async function (value, context) {
165
+ if (value == null)
166
+ return true;
167
+ const { path, createError } = this;
168
+ if (typeof value !== 'object') {
169
+ return createError({ message: `${path} must be an object` });
170
+ }
171
+ // Validate each property using the provided valueSchema
172
+ for (const key of Object.keys(value)) {
173
+ // Validate the key
174
+ await yupValidate(keySchema, key, context.options);
175
+ // Validate the value
176
+ try {
177
+ await yupValidate(valueSchema, value[key], {
178
+ ...context.options,
179
+ context: {
180
+ ...context.options.context,
181
+ path: path ? `${path}.${key}` : key,
182
+ },
183
+ });
184
+ }
185
+ catch (e) {
186
+ return createError({
187
+ path: path ? `${path}.${key}` : key,
188
+ message: e.message,
189
+ });
190
+ }
191
+ }
192
+ return true;
193
+ });
194
+ }
163
195
  export function ensureObjectSchema(schema) {
164
196
  if (!(schema instanceof yup.ObjectSchema))
165
197
  throw new StackAssertionError(`assertObjectSchema: schema is not an ObjectSchema: ${schema.describe().type}`);
@@ -326,6 +358,7 @@ export const teamSystemPermissions = [
326
358
  '$read_members',
327
359
  '$remove_members',
328
360
  '$invite_members',
361
+ '$manage_api_keys',
329
362
  ];
330
363
  export const permissionDefinitionIdSchema = yupString()
331
364
  .matches(/^\$?[a-z0-9_:]+$/, 'Only lowercase letters, numbers, ":", "_" and optional "$" at the beginning are allowed')
@@ -0,0 +1,23 @@
1
+ /**
2
+ * An api key has the following format:
3
+ * <prefix_without_underscores>_<secret_part_45_chars><id_part_32_chars><type_user_or_team_4_chars><scanner_and_marker_10_chars><checksum_8_chars>
4
+ *
5
+ * The scanner and marker is a base32 character that is used to determine if the api key is a public or private key
6
+ * and if it is a cloud or self-hosted key.
7
+ *
8
+ * The checksum is a crc32 checksum of the api key encoded in hex.
9
+ *
10
+ */
11
+ type ProjectApiKey = {
12
+ id: string;
13
+ prefix: string;
14
+ isPublic: boolean;
15
+ isCloudVersion: boolean;
16
+ secret: string;
17
+ checksum: string;
18
+ type: "user" | "team";
19
+ };
20
+ export declare function isApiKey(secret: string): boolean;
21
+ export declare function createProjectApiKey(options: Pick<ProjectApiKey, "id" | "isPublic" | "isCloudVersion" | "type">): string;
22
+ export declare function parseProjectApiKey(secret: string): ProjectApiKey;
23
+ export {};
@@ -0,0 +1,76 @@
1
+ import { getBase32CharacterFromIndex } from "@stackframe/stack-shared/dist/utils/bytes";
2
+ import { generateSecureRandomString } from "@stackframe/stack-shared/dist/utils/crypto";
3
+ import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
4
+ import crc32 from 'crc/crc32';
5
+ const STACK_AUTH_MARKER = "574ck4u7h";
6
+ // API key part lengths
7
+ const API_KEY_LENGTHS = {
8
+ SECRET_PART: 45,
9
+ ID_PART: 32,
10
+ TYPE_PART: 4,
11
+ SCANNER: 1,
12
+ MARKER: 9,
13
+ CHECKSUM: 8,
14
+ };
15
+ function createChecksumSync(checksummablePart) {
16
+ const data = new TextEncoder().encode(checksummablePart);
17
+ const calculated_checksum = crc32(data);
18
+ return calculated_checksum.toString(16).padStart(8, "0");
19
+ }
20
+ function createApiKeyParts(options) {
21
+ const { id, isPublic, isCloudVersion, type } = options;
22
+ const prefix = isPublic ? "pk" : "sk";
23
+ const scannerFlag = (isCloudVersion ? 0 : 1) + (isPublic ? 2 : 0) + ( /* version */0);
24
+ const secretPart = generateSecureRandomString();
25
+ const idPart = id.replace(/-/g, "");
26
+ const scannerAndMarker = getBase32CharacterFromIndex(scannerFlag).toLowerCase() + STACK_AUTH_MARKER;
27
+ const checksummablePart = `${prefix}_${secretPart}${idPart}${type}${scannerAndMarker}`;
28
+ return { checksummablePart, idPart, prefix, scannerAndMarker, type };
29
+ }
30
+ function parseApiKeyParts(secret) {
31
+ const regex = new RegExp(`^([a-zA-Z0-9_]+)_` + // prefix
32
+ `([a-zA-Z0-9_]{${API_KEY_LENGTHS.SECRET_PART}})` + // secretPart
33
+ `([a-zA-Z0-9_]{${API_KEY_LENGTHS.ID_PART}})` + // idPart
34
+ `([a-zA-Z0-9_]{${API_KEY_LENGTHS.TYPE_PART}})` + // type
35
+ `([a-zA-Z0-9_]{${API_KEY_LENGTHS.SCANNER}})` + // scanner
36
+ `(${STACK_AUTH_MARKER})` + // marker
37
+ `([a-zA-Z0-9_]{${API_KEY_LENGTHS.CHECKSUM}})$` // checksum
38
+ );
39
+ const match = secret.match(regex);
40
+ if (!match) {
41
+ throw new StackAssertionError("Invalid API key format");
42
+ }
43
+ const [, prefix, secretPart, idPart, type, scannerFlag, marker, checksum] = match;
44
+ const isCloudVersion = parseInt(scannerFlag, 32) % 2 === 0;
45
+ const isPublic = (parseInt(scannerFlag, 32) & 2) !== 0;
46
+ const checksummablePart = `${prefix}_${secretPart}${idPart}${type}${scannerFlag}${marker}`;
47
+ const restored_id = idPart.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
48
+ if (!["user", "team"].includes(type)) {
49
+ throw new StackAssertionError("Invalid type");
50
+ }
51
+ return { checksummablePart, checksum, id: restored_id, isCloudVersion, isPublic, prefix, type: type };
52
+ }
53
+ export function isApiKey(secret) {
54
+ return secret.includes("_") && secret.includes(STACK_AUTH_MARKER);
55
+ }
56
+ export function createProjectApiKey(options) {
57
+ const { checksummablePart } = createApiKeyParts(options);
58
+ const checksum = createChecksumSync(checksummablePart);
59
+ return `${checksummablePart}${checksum}`;
60
+ }
61
+ export function parseProjectApiKey(secret) {
62
+ const { checksummablePart, checksum, id, isCloudVersion, isPublic, prefix, type } = parseApiKeyParts(secret);
63
+ const calculated_checksum = createChecksumSync(checksummablePart);
64
+ if (calculated_checksum !== checksum) {
65
+ throw new StackAssertionError("Checksum mismatch");
66
+ }
67
+ return {
68
+ id,
69
+ prefix,
70
+ isPublic,
71
+ isCloudVersion,
72
+ secret,
73
+ checksum,
74
+ type,
75
+ };
76
+ }
@@ -1,3 +1,6 @@
1
+ export declare function toHexString(input: Uint8Array): string;
2
+ export declare function getBase32CharacterFromIndex(index: number): string;
3
+ export declare function getBase32IndexFromCharacter(character: string): number;
1
4
  export declare function encodeBase32(input: Uint8Array): string;
2
5
  export declare function decodeBase32(input: string): Uint8Array;
3
6
  export declare function encodeBase64(input: Uint8Array): string;
@@ -5,6 +5,43 @@ const crockfordReplacements = new Map([
5
5
  ["i", "1"],
6
6
  ["l", "1"],
7
7
  ]);
8
+ export function toHexString(input) {
9
+ return Array.from(input).map(b => b.toString(16).padStart(2, "0")).join("");
10
+ }
11
+ import.meta.vitest?.test("toHexString", ({ expect }) => {
12
+ expect(toHexString(new Uint8Array([]))).toBe("");
13
+ expect(toHexString(new Uint8Array([0]))).toBe("00");
14
+ expect(toHexString(new Uint8Array([15]))).toBe("0f");
15
+ expect(toHexString(new Uint8Array([16]))).toBe("10");
16
+ expect(toHexString(new Uint8Array([255]))).toBe("ff");
17
+ expect(toHexString(new Uint8Array([1, 2, 3]))).toBe("010203");
18
+ });
19
+ export function getBase32CharacterFromIndex(index) {
20
+ if (index < 0 || index >= crockfordAlphabet.length) {
21
+ throw new StackAssertionError(`Invalid base32 index: ${index}`);
22
+ }
23
+ return crockfordAlphabet[index];
24
+ }
25
+ import.meta.vitest?.test("getBase32CharacterFromIndex", ({ expect }) => {
26
+ expect(getBase32CharacterFromIndex(0)).toBe("0");
27
+ expect(getBase32CharacterFromIndex(15)).toBe("F");
28
+ expect(() => getBase32CharacterFromIndex(32)).toThrow();
29
+ });
30
+ export function getBase32IndexFromCharacter(character) {
31
+ if (character.length !== 1) {
32
+ throw new StackAssertionError(`Invalid base32 character: ${character}`);
33
+ }
34
+ const index = crockfordAlphabet.indexOf(character.toUpperCase());
35
+ if (index === -1) {
36
+ throw new StackAssertionError(`Invalid base32 character: ${character}`);
37
+ }
38
+ return index;
39
+ }
40
+ import.meta.vitest?.test("getBase32IndexFromCharacter", ({ expect }) => {
41
+ expect(getBase32IndexFromCharacter("0")).toBe(0);
42
+ expect(getBase32IndexFromCharacter("F")).toBe(15);
43
+ expect(() => getBase32IndexFromCharacter("_")).toThrow();
44
+ });
8
45
  export function encodeBase32(input) {
9
46
  let bits = 0;
10
47
  let value = 0;
@@ -13,12 +50,12 @@ export function encodeBase32(input) {
13
50
  value = (value << 8) | input[i];
14
51
  bits += 8;
15
52
  while (bits >= 5) {
16
- output += crockfordAlphabet[(value >>> (bits - 5)) & 31];
53
+ output += getBase32CharacterFromIndex((value >>> (bits - 5)) & 31);
17
54
  bits -= 5;
18
55
  }
19
56
  }
20
57
  if (bits > 0) {
21
- output += crockfordAlphabet[(value << (5 - bits)) & 31];
58
+ output += getBase32CharacterFromIndex((value << (5 - bits)) & 31);
22
59
  }
23
60
  // sanity check
24
61
  if (!isBase32(output)) {
@@ -26,6 +63,14 @@ export function encodeBase32(input) {
26
63
  }
27
64
  return output;
28
65
  }
66
+ import.meta.vitest?.test("encodeBase32", ({ expect }) => {
67
+ expect(encodeBase32(new Uint8Array([]))).toBe("");
68
+ expect(encodeBase32(new Uint8Array([1]))).toBe("04");
69
+ expect(encodeBase32(new Uint8Array([15]))).toBe("1W");
70
+ expect(encodeBase32(new Uint8Array([16]))).toBe("20");
71
+ expect(encodeBase32(new Uint8Array([255]))).toBe("ZW");
72
+ expect(encodeBase32(new Uint8Array([255, 255]))).toBe("ZZZG");
73
+ });
29
74
  export function decodeBase32(input) {
30
75
  if (!isBase32(input)) {
31
76
  throw new StackAssertionError("Invalid base32 string");
@@ -41,10 +86,7 @@ export function decodeBase32(input) {
41
86
  if (crockfordReplacements.has(char)) {
42
87
  char = crockfordReplacements.get(char);
43
88
  }
44
- const index = crockfordAlphabet.indexOf(char);
45
- if (index === -1) {
46
- throw new Error(`Invalid character: ${char}`);
47
- }
89
+ const index = getBase32IndexFromCharacter(char);
48
90
  value = (value << 5) | index;
49
91
  bits += 5;
50
92
  if (bits >= 8) {
@@ -54,6 +96,13 @@ export function decodeBase32(input) {
54
96
  }
55
97
  return output;
56
98
  }
99
+ import.meta.vitest?.test("decodeBase32", ({ expect }) => {
100
+ expect(decodeBase32("")).toEqual(new Uint8Array([]));
101
+ expect(decodeBase32("00")).toEqual(new Uint8Array([0]));
102
+ expect(decodeBase32("1W")).toEqual(new Uint8Array([15]));
103
+ expect(decodeBase32("20")).toEqual(new Uint8Array([16]));
104
+ expect(decodeBase32("ZW")).toEqual(new Uint8Array([255]));
105
+ });
57
106
  export function encodeBase64(input) {
58
107
  const res = btoa(String.fromCharCode(...input));
59
108
  // Skip sanity check for test cases
@@ -17,16 +17,16 @@ export declare class AsyncCache<D extends any[], T> {
17
17
  refreshWhere(predicate: (dependencies: D) => boolean): Promise<void>;
18
18
  readonly isCacheAvailable: (key: D) => boolean;
19
19
  readonly getIfCached: (key: D) => ({
20
- status: "error";
21
- error: unknown;
22
- } & {
23
- status: "error";
24
- }) | ({
25
20
  status: "pending";
26
21
  } & {
27
22
  progress: void;
28
23
  } & {
29
24
  status: "pending";
25
+ }) | ({
26
+ status: "error";
27
+ error: unknown;
28
+ } & {
29
+ status: "error";
30
30
  }) | ({
31
31
  status: "ok";
32
32
  data: T;
@@ -57,16 +57,16 @@ declare class AsyncValueCache<T> {
57
57
  });
58
58
  isCacheAvailable(): boolean;
59
59
  getIfCached(): ({
60
- status: "error";
61
- error: unknown;
62
- } & {
63
- status: "error";
64
- }) | ({
65
60
  status: "pending";
66
61
  } & {
67
62
  progress: void;
68
63
  } & {
69
64
  status: "pending";
65
+ }) | ({
66
+ status: "error";
67
+ error: unknown;
68
+ } & {
69
+ status: "error";
70
70
  }) | ({
71
71
  status: "ok";
72
72
  data: T;
@@ -1,4 +1,4 @@
1
- export declare function sha512(input: Uint8Array | string): Promise<string>;
1
+ export declare function sha512(input: Uint8Array | string): Promise<Uint8Array>;
2
2
  export declare function hashPassword(password: string): Promise<string>;
3
3
  export declare function comparePassword(password: string, hash: string): Promise<boolean>;
4
4
  export declare function isPasswordHashValid(hash: string): Promise<boolean>;