@stackframe/stack-shared 2.6.2 → 2.6.3

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.6.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Bugfixes
8
+ - @stackframe/stack-sc@2.6.3
9
+
3
10
  ## 2.6.2
4
11
 
5
12
  ### Patch Changes
@@ -12,6 +12,7 @@ export declare const projectsCrudAdminReadSchema: import("yup").ObjectSchema<{
12
12
  sign_up_enabled: NonNullable<boolean | undefined>;
13
13
  credential_enabled: NonNullable<boolean | undefined>;
14
14
  magic_link_enabled: NonNullable<boolean | undefined>;
15
+ legacy_global_jwt_signing: NonNullable<boolean | undefined>;
15
16
  client_team_creation_enabled: NonNullable<boolean | undefined>;
16
17
  client_user_deletion_enabled: NonNullable<boolean | undefined>;
17
18
  oauth_providers: {
@@ -60,6 +61,7 @@ export declare const projectsCrudAdminReadSchema: import("yup").ObjectSchema<{
60
61
  sign_up_enabled: undefined;
61
62
  credential_enabled: undefined;
62
63
  magic_link_enabled: undefined;
64
+ legacy_global_jwt_signing: undefined;
63
65
  client_team_creation_enabled: undefined;
64
66
  client_user_deletion_enabled: undefined;
65
67
  oauth_providers: undefined;
@@ -113,6 +115,7 @@ export declare const projectsCrudAdminUpdateSchema: import("yup").ObjectSchema<{
113
115
  sign_up_enabled?: boolean | undefined;
114
116
  credential_enabled?: boolean | undefined;
115
117
  magic_link_enabled?: boolean | undefined;
118
+ legacy_global_jwt_signing?: false | undefined;
116
119
  client_team_creation_enabled?: boolean | undefined;
117
120
  client_user_deletion_enabled?: boolean | undefined;
118
121
  oauth_providers?: {
@@ -160,6 +163,7 @@ export declare const projectsCrudAdminCreateSchema: import("yup").ObjectSchema<{
160
163
  sign_up_enabled?: boolean | undefined;
161
164
  credential_enabled?: boolean | undefined;
162
165
  magic_link_enabled?: boolean | undefined;
166
+ legacy_global_jwt_signing?: false | undefined;
163
167
  client_team_creation_enabled?: boolean | undefined;
164
168
  client_user_deletion_enabled?: boolean | undefined;
165
169
  oauth_providers?: {
@@ -240,6 +244,7 @@ export declare const projectsCrud: import("../../crud").CrudSchemaFromOptions<{
240
244
  sign_up_enabled: NonNullable<boolean | undefined>;
241
245
  credential_enabled: NonNullable<boolean | undefined>;
242
246
  magic_link_enabled: NonNullable<boolean | undefined>;
247
+ legacy_global_jwt_signing: NonNullable<boolean | undefined>;
243
248
  client_team_creation_enabled: NonNullable<boolean | undefined>;
244
249
  client_user_deletion_enabled: NonNullable<boolean | undefined>;
245
250
  oauth_providers: {
@@ -288,6 +293,7 @@ export declare const projectsCrud: import("../../crud").CrudSchemaFromOptions<{
288
293
  sign_up_enabled: undefined;
289
294
  credential_enabled: undefined;
290
295
  magic_link_enabled: undefined;
296
+ legacy_global_jwt_signing: undefined;
291
297
  client_team_creation_enabled: undefined;
292
298
  client_user_deletion_enabled: undefined;
293
299
  oauth_providers: undefined;
@@ -316,6 +322,7 @@ export declare const projectsCrud: import("../../crud").CrudSchemaFromOptions<{
316
322
  sign_up_enabled?: boolean | undefined;
317
323
  credential_enabled?: boolean | undefined;
318
324
  magic_link_enabled?: boolean | undefined;
325
+ legacy_global_jwt_signing?: false | undefined;
319
326
  client_team_creation_enabled?: boolean | undefined;
320
327
  client_user_deletion_enabled?: boolean | undefined;
321
328
  oauth_providers?: {
@@ -393,6 +400,7 @@ export declare const internalProjectsCrud: import("../../crud").CrudSchemaFromOp
393
400
  sign_up_enabled: NonNullable<boolean | undefined>;
394
401
  credential_enabled: NonNullable<boolean | undefined>;
395
402
  magic_link_enabled: NonNullable<boolean | undefined>;
403
+ legacy_global_jwt_signing: NonNullable<boolean | undefined>;
396
404
  client_team_creation_enabled: NonNullable<boolean | undefined>;
397
405
  client_user_deletion_enabled: NonNullable<boolean | undefined>;
398
406
  oauth_providers: {
@@ -441,6 +449,7 @@ export declare const internalProjectsCrud: import("../../crud").CrudSchemaFromOp
441
449
  sign_up_enabled: undefined;
442
450
  credential_enabled: undefined;
443
451
  magic_link_enabled: undefined;
452
+ legacy_global_jwt_signing: undefined;
444
453
  client_team_creation_enabled: undefined;
445
454
  client_user_deletion_enabled: undefined;
446
455
  oauth_providers: undefined;
@@ -469,6 +478,7 @@ export declare const internalProjectsCrud: import("../../crud").CrudSchemaFromOp
469
478
  sign_up_enabled?: boolean | undefined;
470
479
  credential_enabled?: boolean | undefined;
471
480
  magic_link_enabled?: boolean | undefined;
481
+ legacy_global_jwt_signing?: false | undefined;
472
482
  client_team_creation_enabled?: boolean | undefined;
473
483
  client_user_deletion_enabled?: boolean | undefined;
474
484
  oauth_providers?: {
@@ -43,6 +43,8 @@ export const projectsCrudAdminReadSchema = yupObject({
43
43
  sign_up_enabled: schemaFields.projectSignUpEnabledSchema.required(),
44
44
  credential_enabled: schemaFields.projectCredentialEnabledSchema.required(),
45
45
  magic_link_enabled: schemaFields.projectMagicLinkEnabledSchema.required(),
46
+ // TODO: remove this
47
+ legacy_global_jwt_signing: schemaFields.yupBoolean().required(),
46
48
  client_team_creation_enabled: schemaFields.projectClientTeamCreationEnabledSchema.required(),
47
49
  client_user_deletion_enabled: schemaFields.projectClientUserDeletionEnabledSchema.required(),
48
50
  oauth_providers: yupArray(oauthProviderSchema.required()).required(),
@@ -76,6 +78,7 @@ export const projectsCrudAdminUpdateSchema = yupObject({
76
78
  magic_link_enabled: schemaFields.projectMagicLinkEnabledSchema.optional(),
77
79
  client_team_creation_enabled: schemaFields.projectClientTeamCreationEnabledSchema.optional(),
78
80
  client_user_deletion_enabled: schemaFields.projectClientUserDeletionEnabledSchema.optional(),
81
+ legacy_global_jwt_signing: schemaFields.yupBoolean().isFalse().optional(),
79
82
  allow_localhost: schemaFields.projectAllowLocalhostSchema.optional(),
80
83
  email_config: emailConfigSchema.optional().default(undefined),
81
84
  domains: yupArray(domainSchema.required()).optional().default(undefined),
@@ -1,21 +1,36 @@
1
1
  import * as jose from "jose";
2
- export declare function signJWT(issuer: string, payload: any, expirationTime?: string): Promise<string>;
3
- export declare function verifyJWT(issuer: string, jwt: string): Promise<jose.JWTPayload>;
4
- export declare function getPrivateJwk(): Promise<{
2
+ export declare function legacySignGlobalJWT(issuer: string, payload: any, expirationTime?: string): Promise<string>;
3
+ export declare function legacyVerifyGlobalJWT(issuer: string, jwt: string): Promise<jose.JWTPayload>;
4
+ export declare function signJWT(options: {
5
+ issuer: string;
6
+ audience: string;
7
+ payload: any;
8
+ expirationTime?: string;
9
+ }): Promise<string>;
10
+ export declare function verifyJWT(options: {
11
+ issuer: string;
12
+ jwt: string;
13
+ }): Promise<jose.JWTPayload>;
14
+ export declare function getPrivateJwk(secret: string): Promise<{
5
15
  kty: string;
6
16
  crv: string;
7
17
  d: string;
8
18
  x: string;
9
19
  y: string;
10
20
  }>;
11
- export declare function getPublicJwkSet(): Promise<{
12
- keys: Pick<{
21
+ export declare function getPublicJwkSet(secret: string): Promise<{
22
+ keys: {
23
+ kid: string;
24
+ x: string;
13
25
  kty: string;
14
26
  crv: string;
15
- d: string;
16
- x: string;
17
27
  y: string;
18
- }, "x" | "kty" | "crv" | "y">[];
28
+ }[];
19
29
  }>;
20
- export declare function encryptJWE(payload: any, expirationTime?: string): Promise<string>;
21
- export declare function decryptJWE(jwt: string): Promise<jose.JWTPayload>;
30
+ export declare function getPerAudienceSecret(options: {
31
+ audience: string;
32
+ secret: string;
33
+ }): string;
34
+ export declare function getKid(options: {
35
+ secret: string;
36
+ }): string;
package/dist/utils/jwt.js CHANGED
@@ -4,9 +4,18 @@ import { encodeBase64 } from "./bytes";
4
4
  import { getEnvVariable } from "./env";
5
5
  import { globalVar } from "./globals";
6
6
  import { pick } from "./objects";
7
- const STACK_SERVER_SECRET = jose.base64url.decode(getEnvVariable("STACK_SERVER_SECRET"));
8
- export async function signJWT(issuer, payload, expirationTime = "5m") {
9
- const privateJwk = await jose.importJWK(await getPrivateJwk());
7
+ import crypto from "crypto";
8
+ import { JOSEError } from "jose/errors";
9
+ const STACK_SERVER_SECRET = getEnvVariable("STACK_SERVER_SECRET");
10
+ try {
11
+ jose.base64url.decode(STACK_SERVER_SECRET);
12
+ }
13
+ catch (e) {
14
+ throw new Error("STACK_SERVER_SECRET is not valid. Please use the generateKeys script to generate a new secret.");
15
+ }
16
+ // TODO: remove this after moving everyone to project specific JWTs
17
+ export async function legacySignGlobalJWT(issuer, payload, expirationTime = "5m") {
18
+ const privateJwk = await jose.importJWK(await getPrivateJwk(STACK_SERVER_SECRET));
10
19
  return await new jose.SignJWT(payload)
11
20
  .setProtectedHeader({ alg: "ES256" })
12
21
  .setIssuer(issuer)
@@ -14,15 +23,36 @@ export async function signJWT(issuer, payload, expirationTime = "5m") {
14
23
  .setExpirationTime(expirationTime)
15
24
  .sign(privateJwk);
16
25
  }
17
- export async function verifyJWT(issuer, jwt) {
18
- const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet());
19
- const verified = await jose.jwtVerify(jwt, jwkSet, {
20
- issuer,
21
- });
26
+ // TODO: remove this after moving everyone to project specific JWTs
27
+ export async function legacyVerifyGlobalJWT(issuer, jwt) {
28
+ const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(STACK_SERVER_SECRET));
29
+ const verified = await jose.jwtVerify(jwt, jwkSet, { issuer });
22
30
  return verified.payload;
23
31
  }
24
- export async function getPrivateJwk() {
25
- const secretHash = await globalVar.crypto.subtle.digest("SHA-256", STACK_SERVER_SECRET);
32
+ export async function signJWT(options) {
33
+ const secret = getPerAudienceSecret({ audience: options.audience, secret: STACK_SERVER_SECRET });
34
+ const kid = getKid({ secret });
35
+ const privateJwk = await jose.importJWK(await getPrivateJwk(secret));
36
+ return await new jose.SignJWT(options.payload)
37
+ .setProtectedHeader({ alg: "ES256", kid })
38
+ .setIssuer(options.issuer)
39
+ .setIssuedAt()
40
+ .setAudience(options.audience)
41
+ .setExpirationTime(options.expirationTime || "5m")
42
+ .sign(privateJwk);
43
+ }
44
+ export async function verifyJWT(options) {
45
+ const audience = jose.decodeJwt(options.jwt).aud;
46
+ if (!audience || typeof audience !== "string") {
47
+ throw new JOSEError("Invalid JWT audience");
48
+ }
49
+ const secret = getPerAudienceSecret({ audience, secret: STACK_SERVER_SECRET });
50
+ const jwkSet = jose.createLocalJWKSet(await getPublicJwkSet(secret));
51
+ const verified = await jose.jwtVerify(options.jwt, jwkSet, { issuer: options.issuer });
52
+ return verified.payload;
53
+ }
54
+ export async function getPrivateJwk(secret) {
55
+ const secretHash = await globalVar.crypto.subtle.digest("SHA-256", jose.base64url.decode(secret));
26
56
  const priv = new Uint8Array(secretHash);
27
57
  const ec = new elliptic.ec('p256');
28
58
  const key = ec.keyFromPrivate(priv);
@@ -35,24 +65,23 @@ export async function getPrivateJwk() {
35
65
  y: encodeBase64(publicKey.getY().toBuffer()),
36
66
  };
37
67
  }
38
- export async function getPublicJwkSet() {
39
- const privateJwk = await getPrivateJwk();
68
+ export async function getPublicJwkSet(secret) {
69
+ const privateJwk = await getPrivateJwk(secret);
40
70
  const jwk = pick(privateJwk, ["kty", "crv", "x", "y"]);
41
71
  return {
42
- keys: [jwk]
72
+ keys: [{ ...jwk, kid: getKid({ secret }) }],
43
73
  };
44
74
  }
45
- export async function encryptJWE(payload, expirationTime = "5m") {
46
- return await new jose.EncryptJWT(payload)
47
- .setProtectedHeader({ alg: "dir", enc: "A128CBC-HS256" })
48
- .setIssuer("stack")
49
- .setIssuedAt()
50
- .setExpirationTime(expirationTime)
51
- .encrypt(STACK_SERVER_SECRET);
75
+ export function getPerAudienceSecret(options) {
76
+ return jose.base64url.encode(crypto
77
+ .createHash('sha256')
78
+ .update(JSON.stringify([options.secret, options.audience]))
79
+ .digest());
52
80
  }
53
- export async function decryptJWE(jwt) {
54
- if (!jwt) {
55
- throw new Error("Provided JWT is empty");
56
- }
57
- return (await jose.jwtDecrypt(jwt, STACK_SERVER_SECRET)).payload;
81
+ ;
82
+ export function getKid(options) {
83
+ return jose.base64url.encode(crypto
84
+ .createHash('sha256')
85
+ .update(JSON.stringify([options.secret, "kid"]))
86
+ .digest()).slice(0, 12);
58
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.6.2",
3
+ "version": "2.6.3",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -38,7 +38,7 @@
38
38
  "oauth4webapi": "^2.10.3",
39
39
  "semver": "^7.6.3",
40
40
  "uuid": "^9.0.1",
41
- "@stackframe/stack-sc": "2.6.2"
41
+ "@stackframe/stack-sc": "2.6.3"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/bcrypt": "^5.0.2",