@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 +7 -0
- package/dist/interface/crud/projects.d.ts +10 -0
- package/dist/interface/crud/projects.js +3 -0
- package/dist/utils/jwt.d.ts +25 -10
- package/dist/utils/jwt.js +54 -25
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -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),
|
package/dist/utils/jwt.d.ts
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
import * as jose from "jose";
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
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:
|
|
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
|
-
}
|
|
28
|
+
}[];
|
|
19
29
|
}>;
|
|
20
|
-
export declare function
|
|
21
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
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
|
|
25
|
-
const
|
|
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
|
|
46
|
-
return
|
|
47
|
-
.
|
|
48
|
-
.
|
|
49
|
-
.
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
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.
|
|
41
|
+
"@stackframe/stack-sc": "2.6.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/bcrypt": "^5.0.2",
|