@cedarjs/api 7.0.0-canary.3111 → 7.0.0-canary.3114

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/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './types.js';
6
6
  export * from './transforms.js';
7
7
  export * from './cors.js';
8
8
  export * from './event.js';
9
+ export * from './signedTokens/index.js';
9
10
  export declare const prismaVersion: string | undefined;
10
11
  /** @deprecated - use `cedarVersion` instead */
11
12
  export declare const redwoodVersion: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,8BAA8B,CAAA;AAC5C,cAAc,yBAAyB,CAAA;AACvC,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAU1B,eAAO,MAAM,aAAa,EAAE,MAAM,GAAG,SAAqC,CAAA;AAC1E,+CAA+C;AAC/C,eAAO,MAAM,cAAc,QAAwB,CAAA;AACnD,+CAA+C;AAC/C,eAAO,MAAM,cAAc,QAAwB,CAAA;AACnD,eAAO,MAAM,YAAY,QAAwB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,8BAA8B,CAAA;AAC5C,cAAc,yBAAyB,CAAA;AACvC,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,yBAAyB,CAAA;AAUvC,eAAO,MAAM,aAAa,EAAE,MAAM,GAAG,SAAqC,CAAA;AAC1E,+CAA+C;AAC/C,eAAO,MAAM,cAAc,QAAwB,CAAA;AACnD,+CAA+C;AAC/C,eAAO,MAAM,cAAc,QAAwB,CAAA;AACnD,eAAO,MAAM,YAAY,QAAwB,CAAA"}
package/dist/index.js CHANGED
@@ -6,10 +6,11 @@ export * from "./types.js";
6
6
  export * from "./transforms.js";
7
7
  export * from "./cors.js";
8
8
  export * from "./event.js";
9
+ export * from "./signedTokens/index.js";
9
10
  const prismaVersion = "7.10.0";
10
- const redwoodVersion = "7.0.0-canary.3111";
11
- const cedarjsVersion = "7.0.0-canary.3111";
12
- const cedarVersion = "7.0.0-canary.3111";
11
+ const redwoodVersion = "7.0.0-canary.3114";
12
+ const cedarjsVersion = "7.0.0-canary.3114";
13
+ const cedarVersion = "7.0.0-canary.3114";
13
14
  export {
14
15
  cedarVersion,
15
16
  cedarjsVersion,
@@ -0,0 +1,2 @@
1
+ export * from './signedTokens.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signedTokens/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA"}
@@ -0,0 +1 @@
1
+ export * from "./signedTokens.js";
@@ -0,0 +1,99 @@
1
+ import type { SignOptions } from 'jsonwebtoken';
2
+ /**
3
+ * Name of the environment variable that holds the default secret used to
4
+ * sign and verify tokens when no `secret` option is passed.
5
+ */
6
+ export declare const SIGNED_TOKEN_SECRET_ENV_VAR = "SIGNED_TOKEN_SECRET";
7
+ /**
8
+ * Machine-readable reason a `SignedTokenError` was thrown. Use it to tell an
9
+ * expired link apart from a tampered one without matching on the message.
10
+ */
11
+ export type SignedTokenErrorCode = 'MISSING_SECRET' | 'MISSING_PURPOSE' | 'MISSING_TOKEN' | 'SIGN_FAILED' | 'INVALID' | 'EXPIRED' | 'PURPOSE_MISMATCH';
12
+ /**
13
+ * Thrown by `createSignedToken` and `verifySignedToken` whenever a token
14
+ * cannot be created or cannot be trusted. Verification never returns `null`
15
+ * or `false` for a bad token; it always throws so a missing check cannot
16
+ * silently pass.
17
+ */
18
+ export declare class SignedTokenError extends Error {
19
+ code: SignedTokenErrorCode;
20
+ constructor(code: SignedTokenErrorCode, message: string, cause?: unknown);
21
+ }
22
+ /**
23
+ * How long a token stays valid. A number is a count of seconds. A string is
24
+ * a duration such as `'10m'`, `'2h'`, or `'7 days'`.
25
+ */
26
+ export type SignedTokenExpiresIn = NonNullable<SignOptions['expiresIn']>;
27
+ export interface CreateSignedTokenOptions<TPayload extends object> {
28
+ /**
29
+ * The claims to carry inside the token. Serialized as JSON, so only
30
+ * JSON-compatible values survive the round trip (a `Date` comes back as a
31
+ * string).
32
+ */
33
+ payload: TPayload;
34
+ /**
35
+ * What the token is for, such as `'google-oauth-state'` or
36
+ * `'email-confirmation'`. A token only verifies when the same purpose is
37
+ * passed to `verifySignedToken`, so a token minted for one flow cannot be
38
+ * replayed in another.
39
+ */
40
+ purpose: string;
41
+ /** How long the token stays valid. See `SignedTokenExpiresIn`. */
42
+ expiresIn: SignedTokenExpiresIn;
43
+ /**
44
+ * The secret to sign with. Defaults to the `SIGNED_TOKEN_SECRET`
45
+ * environment variable. There is no built-in fallback: when neither is set
46
+ * a `SignedTokenError` with code `MISSING_SECRET` is thrown.
47
+ */
48
+ secret?: string;
49
+ }
50
+ export interface VerifySignedTokenOptions {
51
+ /** The purpose the token must have been created with. */
52
+ purpose: string;
53
+ /**
54
+ * The secret to verify with. Defaults to the `SIGNED_TOKEN_SECRET`
55
+ * environment variable. There is no built-in fallback: when neither is set
56
+ * a `SignedTokenError` with code `MISSING_SECRET` is thrown.
57
+ */
58
+ secret?: string;
59
+ }
60
+ /**
61
+ * Creates a compact, URL-safe token that carries `payload`, is bound to
62
+ * `purpose`, expires after `expiresIn`, and is signed so it can only have
63
+ * come from this application.
64
+ *
65
+ * Use it wherever a value has to leave the server and come back untouched:
66
+ * OAuth `state`, email confirmation and password-set links, unsubscribe
67
+ * links, or short-lived capability tokens.
68
+ *
69
+ * @example
70
+ *
71
+ * const state = createSignedToken({
72
+ * payload: { organizationId, userId },
73
+ * purpose: 'google-oauth-state',
74
+ * expiresIn: '10m',
75
+ * })
76
+ */
77
+ export declare function createSignedToken<TPayload extends object>({ payload, purpose, expiresIn, secret, }: CreateSignedTokenOptions<TPayload>): string;
78
+ /**
79
+ * Verifies a token created by `createSignedToken` and returns its payload.
80
+ *
81
+ * Throws a `SignedTokenError` when the token is missing, was signed with a
82
+ * different secret, has been tampered with, has expired, or was created for
83
+ * a different `purpose`. The error's `code` says which.
84
+ *
85
+ * The token argument accepts `null` and `undefined` on purpose so a value
86
+ * read straight from a query string or a header can be passed in as-is. A
87
+ * missing token is a verification failure, never a skipped check.
88
+ *
89
+ * @example
90
+ *
91
+ * const { organizationId, userId } = verifySignedToken<{
92
+ * organizationId: string
93
+ * userId: string
94
+ * }>(event.queryStringParameters?.state, {
95
+ * purpose: 'google-oauth-state',
96
+ * })
97
+ */
98
+ export declare function verifySignedToken<TPayload extends object = Record<string, unknown>>(token: string | null | undefined, { purpose, secret }: VerifySignedTokenOptions): TPayload;
99
+ //# sourceMappingURL=signedTokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signedTokens.d.ts","sourceRoot":"","sources":["../../src/signedTokens/signedTokens.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C;;;GAGG;AACH,eAAO,MAAM,2BAA2B,wBAAwB,CAAA;AAEhE;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GAChB,iBAAiB,GACjB,eAAe,GACf,aAAa,GACb,SAAS,GACT,SAAS,GACT,kBAAkB,CAAA;AAEtB;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,IAAI,EAAE,oBAAoB,CAAA;gBAEd,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAKzE;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAA;AAExE,MAAM,WAAW,wBAAwB,CAAC,QAAQ,SAAS,MAAM;IAC/D;;;;OAIG;IACH,OAAO,EAAE,QAAQ,CAAA;IACjB;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,SAAS,EAAE,oBAAoB,CAAA;IAC/B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAiED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,SAAS,MAAM,EAAE,EACzD,OAAO,EACP,OAAO,EACP,SAAS,EACT,MAAM,GACP,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAAG,MAAM,CAkB7C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEjD,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,wBAAwB,GAC5C,QAAQ,CA2CV"}
@@ -0,0 +1,100 @@
1
+ import jwt from "jsonwebtoken";
2
+ const SIGNED_TOKEN_SECRET_ENV_VAR = "SIGNED_TOKEN_SECRET";
3
+ class SignedTokenError extends Error {
4
+ code;
5
+ constructor(code, message, cause) {
6
+ super(message, cause === void 0 ? void 0 : { cause });
7
+ this.name = "SignedTokenError";
8
+ this.code = code;
9
+ }
10
+ }
11
+ const ALGORITHM = "HS256";
12
+ function resolveSecret(secret, action) {
13
+ const resolved = secret ?? process.env[SIGNED_TOKEN_SECRET_ENV_VAR];
14
+ if (typeof resolved === "string" && resolved.length > 0) {
15
+ return resolved;
16
+ }
17
+ throw new SignedTokenError(
18
+ "MISSING_SECRET",
19
+ `Cannot ${action} a signed token because no secret is configured. Set the ${SIGNED_TOKEN_SECRET_ENV_VAR} environment variable (generate a value with \`yarn cedar generate secret\`), or pass the \`secret\` option explicitly.`
20
+ );
21
+ }
22
+ function assertPurpose(purpose) {
23
+ if (typeof purpose === "string" && purpose.length > 0) {
24
+ return;
25
+ }
26
+ throw new SignedTokenError(
27
+ "MISSING_PURPOSE",
28
+ "A signed token needs a non-empty `purpose` string, such as 'google-oauth-state' or 'email-confirmation'. The purpose is what stops a token created for one flow from verifying in another."
29
+ );
30
+ }
31
+ function isSignedTokenClaims(claims) {
32
+ if (typeof claims !== "object" || claims === null) {
33
+ return false;
34
+ }
35
+ if (!("payload" in claims) || !("purpose" in claims) || !("exp" in claims)) {
36
+ return false;
37
+ }
38
+ return typeof claims.payload === "object" && claims.payload !== null && typeof claims.purpose === "string" && typeof claims.exp === "number";
39
+ }
40
+ function createSignedToken({
41
+ payload,
42
+ purpose,
43
+ expiresIn,
44
+ secret
45
+ }) {
46
+ assertPurpose(purpose);
47
+ const resolvedSecret = resolveSecret(secret, "create");
48
+ try {
49
+ return jwt.sign({ payload, purpose }, resolvedSecret, {
50
+ algorithm: ALGORITHM,
51
+ expiresIn
52
+ });
53
+ } catch (e) {
54
+ const message = e instanceof Error ? e.message : String(e);
55
+ throw new SignedTokenError(
56
+ "SIGN_FAILED",
57
+ `Could not create signed token: ${message}`,
58
+ e
59
+ );
60
+ }
61
+ }
62
+ function verifySignedToken(token, { purpose, secret }) {
63
+ assertPurpose(purpose);
64
+ const resolvedSecret = resolveSecret(secret, "verify");
65
+ if (typeof token !== "string" || token.length === 0) {
66
+ throw new SignedTokenError("MISSING_TOKEN", "No signed token was provided.");
67
+ }
68
+ let claims;
69
+ try {
70
+ claims = jwt.verify(token, resolvedSecret, { algorithms: [ALGORITHM] });
71
+ } catch (e) {
72
+ if (e instanceof jwt.TokenExpiredError) {
73
+ throw new SignedTokenError("EXPIRED", "The signed token has expired.", e);
74
+ }
75
+ throw new SignedTokenError(
76
+ "INVALID",
77
+ "The signed token is invalid or was not created by this application.",
78
+ e
79
+ );
80
+ }
81
+ if (!isSignedTokenClaims(claims)) {
82
+ throw new SignedTokenError(
83
+ "INVALID",
84
+ "The signed token is invalid or was not created by this application."
85
+ );
86
+ }
87
+ if (claims.purpose !== purpose) {
88
+ throw new SignedTokenError(
89
+ "PURPOSE_MISMATCH",
90
+ `The signed token was created for purpose '${claims.purpose}' but was verified with purpose '${purpose}'.`
91
+ );
92
+ }
93
+ return claims.payload;
94
+ }
95
+ export {
96
+ SIGNED_TOKEN_SECRET_ENV_VAR,
97
+ SignedTokenError,
98
+ createSignedToken,
99
+ verifySignedToken
100
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/api",
3
- "version": "7.0.0-canary.3111",
3
+ "version": "7.0.0-canary.3114",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/cedarjs/cedar.git",
@@ -84,7 +84,7 @@
84
84
  "title-case": "3.0.3"
85
85
  },
86
86
  "devDependencies": {
87
- "@cedarjs/framework-tools": "7.0.0-canary.3111",
87
+ "@cedarjs/framework-tools": "7.0.0-canary.3114",
88
88
  "@types/aws-lambda": "8.10.163",
89
89
  "@types/jsonwebtoken": "9.0.10",
90
90
  "@types/memjs": "1",