@maximemrf/adonisjs-jwt 1.0.0-rc.6 → 1.0.0-rc.7

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.
@@ -43,6 +43,7 @@ export function jwtGuard(config) {
43
43
  * the first request, when the app would already be serving traffic.
44
44
  */
45
45
  validateLifetimes(options, PREFIX);
46
+ validateSigningMethod(options, PREFIX, { usesAppKey });
46
47
  let driver;
47
48
  if (usesAppKey) {
48
49
  /**
@@ -53,7 +54,6 @@ export function jwtGuard(config) {
53
54
  assertAppKey(process.env.APP_KEY);
54
55
  }
55
56
  else {
56
- validateSigningMethod(options, PREFIX);
57
57
  driver = resolveDriver(options, PREFIX);
58
58
  }
59
59
  return {
@@ -107,6 +107,7 @@ export class JwtGuard {
107
107
  }
108
108
  const providerUser = await this.#userProvider.createUserForGuard(user);
109
109
  const signPayload = this.#options.content(providerUser);
110
+ assertNoGuardClaims(signPayload, this.#options);
110
111
  const token = this.#signAccessToken(signPayload);
111
112
  let refreshToken;
112
113
  if (this.#refreshTokenUserProvider) {
@@ -407,6 +408,23 @@ export class JwtGuard {
407
408
  }
408
409
  }
409
410
  const REFRESH_TOKENS_NOT_CONFIGURED = 'JWT guard refresh tokens are not configured: set `refreshTokenUserProvider` in the guard config';
411
+ /**
412
+ * The guard sets `exp`, and `iss` / `aud` when `issuer` / `audience` are
413
+ * configured. jsonwebtoken refuses a payload that sets them too, with a
414
+ * message about its own options (`options.expiresIn`) that the app never set.
415
+ */
416
+ function assertNoGuardClaims(payload, options) {
417
+ const conflicts = [['exp', 'tokenExpiresIn']];
418
+ if (options.issuer)
419
+ conflicts.push(['iss', 'issuer']);
420
+ if (options.audience)
421
+ conflicts.push(['aud', 'audience']);
422
+ for (const [claim, option] of conflicts) {
423
+ if (payload[claim] !== undefined) {
424
+ throw new RuntimeException(`JWT guard \`content\` must not set \`${claim}\`: the guard sets it from \`${option}\``);
425
+ }
426
+ }
427
+ }
410
428
  function isUserId(value) {
411
429
  return ((typeof value === 'string' && value !== '') ||
412
430
  (typeof value === 'number' && Number.isFinite(value)) ||
@@ -135,6 +135,13 @@ type JwtSharedOptions = {
135
135
  */
136
136
  verifyPayload?: JwtVerifyPayload;
137
137
  };
138
+ /**
139
+ * Claims set by the guard: `exp` comes from `tokenExpiresIn`, so `content`
140
+ * must not set it
141
+ */
142
+ type GuardClaims = {
143
+ exp?: never;
144
+ };
138
145
  /**
139
146
  * `content` builds the payload of the access tokens. Without `getUserId`,
140
147
  * the guard reads the user id from the `userId` claim, so `content` must
@@ -146,14 +153,14 @@ export type JwtContentOptions<RealUser> = {
146
153
  * Defaults to `payload.userId`. Use e.g. `(payload) => payload.sub` for external providers.
147
154
  */
148
155
  getUserId?: undefined;
149
- content?: (user: JwtGuardUser<RealUser>) => Record<string, any> & BaseJwtContent;
156
+ content?: (user: JwtGuardUser<RealUser>) => Record<string, any> & BaseJwtContent & GuardClaims;
150
157
  } | {
151
158
  /**
152
159
  * Extract the user id from a verified token payload.
153
160
  * Defaults to `payload.userId`. Use e.g. `(payload) => payload.sub` for external providers.
154
161
  */
155
162
  getUserId: JwtGetUserId;
156
- content?: (user: JwtGuardUser<RealUser>) => Record<string, any>;
163
+ content?: (user: JwtGuardUser<RealUser>) => Record<string, any> & GuardClaims;
157
164
  };
158
165
  /**
159
166
  * Options of the `JwtGuard` constructor
@@ -10,7 +10,9 @@ export declare function validateLifetimes(options: Pick<JwtGuardOptions, 'tokenE
10
10
  /**
11
11
  * Exactly one signing method: a secret, an asymmetric key pair, or a JWKS endpoint
12
12
  */
13
- export declare function validateSigningMethod(options: ReleasedSecrets<JwtGuardOptions>, prefix: string): void;
13
+ export declare function validateSigningMethod(options: ReleasedSecrets<JwtGuardOptions>, prefix: string, { usesAppKey }?: {
14
+ usesAppKey?: boolean;
15
+ }): void;
14
16
  /**
15
17
  * Accept `Secret` values (`Env.schema.secret()` in AdonisJS v7) for the
16
18
  * signing keys, and work with their string value
@@ -44,7 +44,7 @@ function assertDuration(value, name, prefix) {
44
44
  /**
45
45
  * Exactly one signing method: a secret, an asymmetric key pair, or a JWKS endpoint
46
46
  */
47
- export function validateSigningMethod(options, prefix) {
47
+ export function validateSigningMethod(options, prefix, { usesAppKey = false } = {}) {
48
48
  const asymmetricPartial = options.privateKey !== undefined ||
49
49
  options.publicKey !== undefined ||
50
50
  options.algorithm !== undefined;
@@ -89,7 +89,11 @@ export function validateSigningMethod(options, prefix) {
89
89
  !(typeof options.jwks.jwksUri === 'string' && options.jwks.jwksUri.trim() !== '')) {
90
90
  throw new Error(`${prefix} \`jwks.jwksUri\` is required to fetch the identity provider keys`);
91
91
  }
92
- if (!options.jwks && !usesAsymmetric && !options.secret) {
92
+ /**
93
+ * Without a signing method, `jwtGuard()` derives the secret from the app
94
+ * key in the resolver
95
+ */
96
+ if (!usesAppKey && !options.jwks && !usesAsymmetric && !options.secret) {
93
97
  throw new Error(`${prefix} requires \`secret\` (symmetric), or \`privateKey\` + \`publicKey\` + \`algorithm\` (asymmetric), or \`jwks\``);
94
98
  }
95
99
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maximemrf/adonisjs-jwt",
3
3
  "description": "AdonisJS package for authentication with JWT tokens",
4
- "version": "1.0.0-rc.6",
4
+ "version": "1.0.0-rc.7",
5
5
  "engines": {
6
6
  "node": ">=24.0.0"
7
7
  },