@fluojs/jwt 1.0.0-beta.4 → 1.0.0-beta.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.
package/README.ko.md CHANGED
@@ -110,7 +110,7 @@ const principal = await verifier.verifyAccessToken(token);
110
110
  // principal: { subject: 'user-123', roles: ['admin'], scopes: ['read:profile'], ... }
111
111
  ```
112
112
 
113
- `JwtService.sign(payload, { expiresIn })`를 사용할 때는 payload 안에 기존 `exp` 값이 있더라도 호출 시점의 `expiresIn` 재정의가 항상 우선합니다. 따라서 토큰 수명은 호출 위치에서 결정적으로 제어됩니다. `expiresIn`은 초 단위의 0 이상 숫자 또는 `60s`, `15m`, `1h`, `7d` 같은 짧은 duration 문자열을 받을 수 있습니다.
113
+ `JwtService.sign(payload, { expiresIn })`를 사용할 때는 payload 안에 기존 `exp` 값이 있더라도 호출 시점의 `expiresIn` 재정의가 항상 우선합니다. 따라서 토큰 수명은 호출 위치에서 결정적으로 제어됩니다. `expiresIn`은 초 단위의 0 이상 숫자 또는 `60s`, `15m`, `1h`, `7d` 같은 짧은 duration 문자열을 받을 수 있습니다. 숫자 초 값은 JWT NumericDate의 소수 정밀도를 보존하며, 문자열 duration은 기존처럼 정수 초 리터럴로 처리됩니다.
114
114
 
115
115
  ## 일반적인 패턴
116
116
 
@@ -154,7 +154,7 @@ const verifier = new DefaultJwtVerifier({
154
154
 
155
155
  ### 리프레시 토큰
156
156
 
157
- `RefreshTokenService`는 전용 HMAC refresh-token 경로를 사용합니다. `refreshToken.secret`은 access-token 서명 키와 별도로 설정하세요. Rotation은 재사용된 토큰을 안정적으로 감지할있도록 atomic `RefreshTokenStore.consume(...)` 구현을 필요로 합니다.
157
+ `RefreshTokenService`는 전용 HMAC refresh-token 경로를 사용합니다. `refreshToken.secret`은 access-token 서명 키와 별도로 설정하세요. Rotation은 `RefreshTokenStore.rotate(...)`를 사용해 현재 토큰을 소비 처리하고 대체 토큰을 같은 durable store 작업 안에서 저장할 있습니다. 따라서 성공한 rotation은 저장된 후속 토큰 없이 기존 토큰만 소비하지 않습니다. 기존 atomic `consume(...)` hook만 구현한 store도 계속 지원하지만, 대체 토큰 저장의 내구성은 store가 소유한 `rotate(...)` 작업에 달려 있습니다.
158
158
 
159
159
  ## 설정 가드레일
160
160
 
package/README.md CHANGED
@@ -110,7 +110,7 @@ const principal = await verifier.verifyAccessToken(token);
110
110
  // principal: { subject: 'user-123', roles: ['admin'], scopes: ['read:profile'], ... }
111
111
  ```
112
112
 
113
- When you use `JwtService.sign(payload, { expiresIn })`, the per-call `expiresIn` override always wins over any pre-existing `payload.exp` value so token lifetime stays deterministic at the call site. `expiresIn` accepts a non-negative number of seconds or short duration strings such as `60s`, `15m`, `1h`, or `7d`.
113
+ When you use `JwtService.sign(payload, { expiresIn })`, the per-call `expiresIn` override always wins over any pre-existing `payload.exp` value so token lifetime stays deterministic at the call site. `expiresIn` accepts a non-negative number of seconds or short duration strings such as `60s`, `15m`, `1h`, or `7d`. Numeric seconds preserve fractional JWT NumericDate precision; string durations remain whole-second literals.
114
114
 
115
115
  ## Common Patterns
116
116
 
@@ -154,7 +154,7 @@ When multiple compatible keys are configured, `kid` disambiguates the verificati
154
154
 
155
155
  ### Refresh tokens
156
156
 
157
- `RefreshTokenService` uses a dedicated HMAC refresh-token path. Configure `refreshToken.secret` separately from access-token signing keys. Rotation requires an atomic `RefreshTokenStore.consume(...)` implementation so replayed tokens can be detected reliably.
157
+ `RefreshTokenService` uses a dedicated HMAC refresh-token path. Configure `refreshToken.secret` separately from access-token signing keys. Rotation can use `RefreshTokenStore.rotate(...)` to atomically mark the current token as consumed and persist the replacement token in the same durable store operation, so a successful rotation never consumes the old token without a stored successor. Stores that only implement the older atomic `consume(...)` hook remain supported, but durable replacement persistence depends on the store-owned `rotate(...)` operation.
158
158
 
159
159
  ## Configuration Guardrails
160
160
 
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAiC,MAAM,cAAc,CAAC;AAOhH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD,KAAK,UAAU,GAAG,WAAW,CAAC;AAyE9B;;GAEG;AACH,qBAAa,SAAS;IACpB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;IAQvD,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,kBAAkB,CAAC,kBAAkB,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,UAAU;IASvG,OAAO,CAAC,MAAM,CAAC,YAAY;CAkB5B"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAsD,MAAM,cAAc,CAAC;AAOrI,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD,KAAK,UAAU,GAAG,WAAW,CAAC;AAyE9B;;GAEG;AACH,qBAAa,SAAS;IACpB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;IAQvD,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,kBAAkB,CAAC,kBAAkB,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,UAAU;IASvG,OAAO,CAAC,MAAM,CAAC,YAAY;CAkB5B"}
@@ -9,6 +9,7 @@ export interface RefreshTokenStore {
9
9
  revoke(tokenId: string): Promise<void>;
10
10
  revokeBySubject(subject: string): Promise<void>;
11
11
  consume?(input: RefreshTokenConsumeInput): Promise<RefreshTokenConsumeResult>;
12
+ rotate?(input: RefreshTokenRotateInput): Promise<RefreshTokenConsumeResult>;
12
13
  }
13
14
  /**
14
15
  * Describes the refresh token consume input contract.
@@ -19,6 +20,12 @@ export interface RefreshTokenConsumeInput {
19
20
  family: string;
20
21
  now: Date;
21
22
  }
23
+ /**
24
+ * Describes the durable refresh token rotation input contract.
25
+ */
26
+ export interface RefreshTokenRotateInput extends RefreshTokenConsumeInput {
27
+ replacement: RefreshTokenRecord;
28
+ }
22
29
  /**
23
30
  * Defines the refresh token consume result type.
24
31
  */
@@ -67,6 +74,8 @@ export declare class RefreshTokenService {
67
74
  revokeRefreshToken(tokenId: string): Promise<void>;
68
75
  revokeAllForSubject(subject: string): Promise<void>;
69
76
  private issueRefreshTokenWithFamily;
77
+ private consumeRefreshToken;
78
+ private createRefreshTokenWithFamily;
70
79
  private verifyRefreshClaims;
71
80
  }
72
81
  //# sourceMappingURL=refresh-token.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/refresh/refresh-token.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC/D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC/E;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,IAAI,CAAC;CACX;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,iBAAiB,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,mBAAmB,CA2B1G;AAQD;;GAEG;AACH,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAG5C,OAAO,EAAE,mBAAmB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAKzC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMnD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IA+DhG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAI3C,2BAA2B;YA6B3B,mBAAmB;CA4BlC"}
1
+ {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/refresh/refresh-token.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC/D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC9E,MAAM,CAAC,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC7E;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,IAAI,CAAC;CACX;AAED;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,wBAAwB;IACvE,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,iBAAiB,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,mBAAmB,CA6B1G;AAQD;;GAEG;AACH,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAG5C,OAAO,EAAE,mBAAmB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAKzC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMnD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IA0EhG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAI3C,2BAA2B;YAQ3B,mBAAmB;YAUnB,4BAA4B;YA8B5B,mBAAmB;CA4BlC"}
@@ -9,6 +9,10 @@ import { JwtConfigurationError, JwtExpiredTokenError, JwtInvalidTokenError } fro
9
9
  * Describes the refresh token consume input contract.
10
10
  */
11
11
 
12
+ /**
13
+ * Describes the durable refresh token rotation input contract.
14
+ */
15
+
12
16
  /**
13
17
  * Defines the refresh token consume result type.
14
18
  */
@@ -40,8 +44,8 @@ export function normalizeRefreshTokenOptions(options) {
40
44
  if (options.verifyMaxAgeSeconds !== undefined && (!Number.isFinite(options.verifyMaxAgeSeconds) || options.verifyMaxAgeSeconds < 0)) {
41
45
  throw new JwtConfigurationError('JWT refresh token verifyMaxAgeSeconds must be a non-negative finite number.');
42
46
  }
43
- if (options.rotation && typeof options.store.consume !== 'function') {
44
- throw new JwtConfigurationError('Refresh token rotation requires an atomic store.consume() implementation.');
47
+ if (options.rotation && typeof options.store.rotate !== 'function' && typeof options.store.consume !== 'function') {
48
+ throw new JwtConfigurationError('Refresh token rotation requires an atomic store.rotate() or store.consume() implementation.');
45
49
  }
46
50
  return {
47
51
  ...options
@@ -64,23 +68,32 @@ export class RefreshTokenService {
64
68
  async rotateRefreshToken(currentToken) {
65
69
  const claims = await this.verifyRefreshClaims(currentToken);
66
70
  if (this.options.rotation) {
67
- if (!this.options.store.consume) {
68
- throw new JwtConfigurationError('Refresh token rotation requires an atomic store.consume() implementation.');
71
+ if (!this.options.store.rotate && !this.options.store.consume) {
72
+ throw new JwtConfigurationError('Refresh token rotation requires an atomic store.rotate() or store.consume() implementation.');
69
73
  }
70
- const consumeResult = await this.options.store.consume({
74
+ const next = await this.createRefreshTokenWithFamily(claims.sub, claims.family);
75
+ const accessToken = await this.signer.signAccessToken({
76
+ sub: claims.sub
77
+ });
78
+ const consumeResult = this.options.store.rotate ? await this.options.store.rotate({
79
+ family: claims.family,
80
+ now: new Date(),
81
+ replacement: next.record,
82
+ subject: claims.sub,
83
+ tokenId: claims.jti
84
+ }) : await this.consumeRefreshToken({
71
85
  family: claims.family,
72
86
  now: new Date(),
73
87
  subject: claims.sub,
74
88
  tokenId: claims.jti
75
89
  });
76
90
  if (consumeResult === 'consumed') {
77
- const refreshToken = await this.issueRefreshTokenWithFamily(claims.sub, claims.family);
78
- const accessToken = await this.signer.signAccessToken({
79
- sub: claims.sub
80
- });
91
+ if (!this.options.store.rotate) {
92
+ await this.options.store.save(next.record);
93
+ }
81
94
  return {
82
95
  accessToken,
83
- refreshToken
96
+ refreshToken: next.token
84
97
  };
85
98
  }
86
99
  if (consumeResult === 'already_used') {
@@ -124,10 +137,24 @@ export class RefreshTokenService {
124
137
  await this.options.store.revokeBySubject(subject);
125
138
  }
126
139
  async issueRefreshTokenWithFamily(subject, family) {
140
+ const {
141
+ record,
142
+ token
143
+ } = await this.createRefreshTokenWithFamily(subject, family);
144
+ await this.options.store.save(record);
145
+ return token;
146
+ }
147
+ async consumeRefreshToken(input) {
148
+ if (!this.options.store.consume) {
149
+ throw new JwtConfigurationError('Refresh token rotation requires an atomic store.rotate() or store.consume() implementation.');
150
+ }
151
+ return this.options.store.consume(input);
152
+ }
153
+ async createRefreshTokenWithFamily(subject, family) {
127
154
  const now = Math.floor(Date.now() / 1000);
128
155
  const tokenId = randomUUID();
129
156
  const expiresAt = new Date((now + this.options.expiresInSeconds) * 1000);
130
- const tokenRecord = {
157
+ const record = {
131
158
  createdAt: new Date(now * 1000),
132
159
  expiresAt,
133
160
  family,
@@ -143,9 +170,11 @@ export class RefreshTokenService {
143
170
  sub: subject,
144
171
  type: 'refresh'
145
172
  };
146
- const refreshToken = await this.signer.signRefreshToken(claims);
147
- await this.options.store.save(tokenRecord);
148
- return refreshToken;
173
+ const token = await this.signer.signRefreshToken(claims);
174
+ return {
175
+ record,
176
+ token
177
+ };
149
178
  }
150
179
  async verifyRefreshClaims(token) {
151
180
  const principal = await this.verifier.verifyRefreshToken(token);
package/dist/service.d.ts CHANGED
@@ -20,7 +20,8 @@ export interface SignOptions {
20
20
  * Sets the token lifetime relative to the current clock.
21
21
  *
22
22
  * Accepts seconds or a short duration literal such as `"60s"`, `"15m"`,
23
- * `"1h"`, or `"7d"`.
23
+ * `"1h"`, or `"7d"`. Numeric seconds preserve fractional JWT
24
+ * NumericDate precision.
24
25
  */
25
26
  expiresIn?: number | `${number}${DurationUnit}`;
26
27
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAa,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAe,MAAM,uBAAuB,CAAC;AAExE,KAAK,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAoD1C;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,MAAM,GAAG,YAAY,EAAE,CAAC;IAChD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,UAAU,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,qBACa,UAAU;IAGnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAFzB,QAAQ,EAAE,kBAAkB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAG/C;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBnE;;;;;;;;;;;;;;;;;;OAkBG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC;IAQ7E;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAmB/B"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAa,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAe,MAAM,uBAAuB,CAAC;AAExE,KAAK,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAoD1C;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,MAAM,GAAG,YAAY,EAAE,CAAC;IAChD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,UAAU,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,qBACa,UAAU;IAGnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAFzB,QAAQ,EAAE,kBAAkB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAG/C;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBnE;;;;;;;;;;;;;;;;;;OAkBG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC;IAQ7E;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAmB/B"}
package/dist/service.js CHANGED
@@ -32,7 +32,7 @@ function parseExpiresInSeconds(expiresIn) {
32
32
  if (!Number.isFinite(expiresIn) || expiresIn < 0) {
33
33
  throw new Error('JwtService.sign() options.expiresIn must be a non-negative finite number.');
34
34
  }
35
- return Math.floor(expiresIn);
35
+ return expiresIn;
36
36
  }
37
37
  const trimmed = expiresIn.trim();
38
38
  const match = trimmed.match(/^(\d+)([smhd])$/i);
@@ -177,7 +177,7 @@ function normalizePrincipal(claims) {
177
177
  if (typeof claims.sub !== 'string' || claims.sub.length === 0) {
178
178
  throw new JwtInvalidTokenError('JWT is missing a valid subject claim.');
179
179
  }
180
- const scopes = Array.isArray(claims.scopes) ? claims.scopes.filter(scope => typeof scope === 'string') : typeof claims.scope === 'string' ? claims.scope.split(' ').filter(Boolean) : undefined;
180
+ const scopes = Array.isArray(claims.scopes) ? claims.scopes.filter(scope => typeof scope === 'string' && scope.length > 0) : typeof claims.scope === 'string' ? claims.scope.split(' ').filter(Boolean) : undefined;
181
181
  const roles = Array.isArray(claims.roles) ? claims.roles.filter(role => typeof role === 'string') : undefined;
182
182
  return {
183
183
  audience: claims.aud,
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "signing",
10
10
  "verification"
11
11
  ],
12
- "version": "1.0.0-beta.4",
12
+ "version": "1.0.0-beta.5",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,8 +36,8 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.0-beta.5",
40
- "@fluojs/di": "^1.0.0-beta.7",
39
+ "@fluojs/core": "^1.0.0-beta.6",
40
+ "@fluojs/di": "^1.0.0-beta.8",
41
41
  "@fluojs/runtime": "^1.0.0-beta.12"
42
42
  },
43
43
  "devDependencies": {