@fluojs/jwt 1.0.0-beta.4 → 1.0.0
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 +2 -2
- package/README.md +2 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/refresh/refresh-token.d.ts +9 -0
- package/dist/refresh/refresh-token.d.ts.map +1 -1
- package/dist/refresh/refresh-token.js +43 -14
- package/dist/service.d.ts +2 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +1 -1
- package/dist/signing/verifier.js +1 -1
- package/package.json +4 -4
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은
|
|
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
|
|
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
|
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,kBAAkB,EAAE,KAAK,WAAW,
|
|
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;
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
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
|
/**
|
package/dist/service.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
35
|
+
return expiresIn;
|
|
36
36
|
}
|
|
37
37
|
const trimmed = expiresIn.trim();
|
|
38
38
|
const match = trimmed.match(/^(\d+)([smhd])$/i);
|
package/dist/signing/verifier.js
CHANGED
|
@@ -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
|
|
12
|
+
"version": "1.0.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fluojs/core": "^1.0.0
|
|
40
|
-
"@fluojs/di": "^1.0.0
|
|
41
|
-
"@fluojs/runtime": "^1.0.0
|
|
39
|
+
"@fluojs/core": "^1.0.0",
|
|
40
|
+
"@fluojs/di": "^1.0.0",
|
|
41
|
+
"@fluojs/runtime": "^1.0.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"vitest": "^3.2.4"
|