@fluojs/jwt 1.0.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fluo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md ADDED
@@ -0,0 +1,169 @@
1
+ # @fluojs/jwt
2
+
3
+ <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
+
5
+ HTTP에 독립적인 JWT 토큰 코어로, 액세스 토큰의 서명 및 검증을 담당하며 검증된 결과를 정규화된 `JwtPrincipal` 객체로 변환합니다.
6
+
7
+ ## 목차
8
+
9
+ - [설치](#설치)
10
+ - [사용 시점](#사용-시점)
11
+ - [빠른 시작](#빠른-시작)
12
+ - [일반적인 패턴](#일반적인-패턴)
13
+ - [공개 API 개요](#공개-api-개요)
14
+ - [관련 패키지](#관련-패키지)
15
+ - [예제 소스](#예제-소스)
16
+
17
+ ## 설치
18
+
19
+ ```bash
20
+ npm install @fluojs/jwt
21
+ ```
22
+
23
+ ## 사용 시점
24
+
25
+ - 백엔드 애플리케이션에서 JWT 액세스 토큰을 발행하거나 검증해야 할 때.
26
+ - 토큰의 클레임 형식과 관계없이 `JwtPrincipal` (주체, 역할, 스코프) 형태의 일관된 사용자 정보를 얻고 싶을 때.
27
+ - 재사용 감지 기능이 포함된 리프레시 토큰 로테이션을 구현할 때.
28
+
29
+ ## 빠른 시작
30
+
31
+ ### 모듈 등록
32
+
33
+ 서명 키와 정책을 사용하여 JWT 모듈을 설정합니다.
34
+
35
+ JWT 지원은 `JwtModule.forRoot(...)` 또는 `JwtModule.forRootAsync(...)`를 통해 등록합니다.
36
+
37
+ ```typescript
38
+ import { Module } from '@fluojs/core';
39
+ import { JwtModule } from '@fluojs/jwt';
40
+
41
+ @Module({
42
+ imports: [
43
+ JwtModule.forRoot({
44
+ algorithms: ['HS256'],
45
+ secret: 'your-secure-secret',
46
+ issuer: 'my-api',
47
+ audience: 'my-app',
48
+ accessTokenTtlSeconds: 3600,
49
+ }),
50
+ ],
51
+ })
52
+ export class AuthModule {}
53
+ ```
54
+
55
+ ### 주입된 설정을 사용하는 비동기 등록
56
+
57
+ JWT 설정이 다른 provider에서 와야 한다면, `JwtModule.forRootAsync(...)`를 사용해도 표준 module contract 안에서 안전하게 등록할 수 있습니다.
58
+
59
+ ```typescript
60
+ import { Module, type Token } from '@fluojs/core';
61
+ import { JwtModule } from '@fluojs/jwt';
62
+
63
+ const JWT_SETTINGS = Symbol('jwt-settings');
64
+
65
+ @Module({
66
+ imports: [
67
+ JwtModule.forRootAsync({
68
+ inject: [JWT_SETTINGS],
69
+ useFactory: async (settings) => ({
70
+ accessTokenTtlSeconds: 900,
71
+ algorithms: ['HS256'],
72
+ audience: 'my-app',
73
+ issuer: settings.issuer,
74
+ secret: settings.secret,
75
+ }),
76
+ }),
77
+ ],
78
+ providers: [
79
+ {
80
+ provide: JWT_SETTINGS as Token<{ issuer: string; secret: string }>,
81
+ useValue: {
82
+ issuer: 'my-api',
83
+ secret: 'your-secure-secret',
84
+ },
85
+ },
86
+ ],
87
+ })
88
+ export class AuthModule {}
89
+ ```
90
+
91
+ ### 토큰 서명 및 검증
92
+
93
+ `DefaultJwtSigner`를 주입받아 토큰을 발행하고, `DefaultJwtVerifier`를 통해 검증합니다.
94
+
95
+ ```typescript
96
+ import { DefaultJwtSigner, DefaultJwtVerifier } from '@fluojs/jwt';
97
+
98
+ // 서명 (Sign)
99
+ const token = await signer.signAccessToken({
100
+ sub: 'user-123',
101
+ roles: ['admin'],
102
+ scopes: ['read:profile'],
103
+ });
104
+
105
+ // 검증 (Verify)
106
+ const principal = await verifier.verifyAccessToken(token);
107
+ // principal: { subject: 'user-123', roles: ['admin'], scopes: ['read:profile'], ... }
108
+ ```
109
+
110
+ `JwtService.sign(payload, { expiresIn })`를 사용할 때는 payload 안에 기존 `exp` 값이 있더라도 호출 시점의 `expiresIn` 재정의가 항상 우선합니다. 따라서 토큰 수명은 호출 위치에서 결정적으로 제어됩니다.
111
+
112
+ ## 일반적인 패턴
113
+
114
+ ### 비대칭 서명 (RS256/ES256)
115
+
116
+ 분산 시스템에서 보안을 강화하기 위해 공개키/개인키 쌍을 사용합니다.
117
+
118
+ ```typescript
119
+ const signer = new DefaultJwtSigner({
120
+ algorithms: ['RS256'],
121
+ privateKey: '...PEM...',
122
+ });
123
+
124
+ const verifier = new DefaultJwtVerifier({
125
+ algorithms: ['RS256'],
126
+ publicKey: '...PEM...',
127
+ });
128
+ ```
129
+
130
+ ### 주체 정규화 (Principal Normalization)
131
+
132
+ `@fluojs/jwt`는 `scope` (문자열)와 `scopes` (배열) 클레임을 자동으로 감지하여 `JwtPrincipal`의 단일 `scopes: string[]` 속성으로 통합합니다. 이를 통해 권한 가드에서 일관된 로직을 적용할 수 있습니다.
133
+
134
+ ### 원격 JWKS 검증
135
+
136
+ 검증 키를 원격 JWKS 엔드포인트에서 가져올 때는, 느리거나 멈춘 identity provider 때문에 인증 경로가 무한정 대기하지 않도록 fetch budget을 명시적으로 제한하세요.
137
+
138
+ ```typescript
139
+ const verifier = new DefaultJwtVerifier({
140
+ algorithms: ['RS256'],
141
+ jwksRequestTimeoutMs: 5_000,
142
+ jwksUri: 'https://issuer.example.com/.well-known/jwks.json',
143
+ });
144
+ ```
145
+
146
+ `jwksRequestTimeoutMs`의 기본값은 `5_000`이며, 예산을 넘기면 진행 중인 JWKS fetch를 abort합니다.
147
+
148
+ ## 공개 API 개요
149
+
150
+ ### 주요 클래스
151
+ - `JwtModule`: DI 등록을 위한 기본 진입점입니다.
152
+ - `DefaultJwtSigner`: 클레임 자동 채우기 기능이 포함된 토큰 발행 클래스입니다.
153
+ - `DefaultJwtVerifier`: 토큰 검증 및 정규화를 담당하는 클래스입니다.
154
+ - `JwtService`: 서명과 검증 기능을 결합한 편의용 파사드(facade)입니다.
155
+
156
+ ### 타입
157
+ - `JwtPrincipal`: 정규화된 사용자 식별 객체 (`subject`, `roles`, `scopes`, `claims`).
158
+ - `JwtVerifierOptions`: 알고리즘, 키, 검증 정책 설정을 위한 타입입니다.
159
+
160
+ ## 관련 패키지
161
+
162
+ - `@fluojs/passport`: 이 코어 패키지를 사용하여 가드와 전략을 실행하는 인증 계층입니다.
163
+ - `@fluojs/config`: 환경별로 비밀 키와 JWT 옵션을 관리할 때 권장되는 패키지입니다.
164
+
165
+ ## 예제 소스
166
+
167
+ - `packages/jwt/src/module.test.ts`: 모듈 등록 및 DI 패턴 예제.
168
+ - `packages/jwt/src/signing/signer.test.ts`: 토큰 서명 예제.
169
+ - `examples/auth-jwt-passport/src/auth/auth.service.ts`: 실제 토큰 발행 구현 예제.
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # @fluojs/jwt
2
+
3
+ <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
+
5
+ HTTP-agnostic JWT token core that handles signing access tokens and verifying them into a normalized `JwtPrincipal`.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [When to use](#when-to-use)
11
+ - [Quick Start](#quick-start)
12
+ - [Common Patterns](#common-patterns)
13
+ - [Public API](#public-api)
14
+ - [Related Packages](#related-packages)
15
+ - [Example Sources](#example-sources)
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @fluojs/jwt
21
+ ```
22
+
23
+ ## When to Use
24
+
25
+ - When you need to issue or verify JWT access tokens in a backend application.
26
+ - When you want a stable `JwtPrincipal` shape (subject, roles, scopes) regardless of the underlying token claim variants.
27
+ - When implementing refresh token rotation with replay detection.
28
+
29
+ ## Quick Start
30
+
31
+ ### Register the Module
32
+
33
+ Configure the JWT module with your signing keys and policy.
34
+
35
+ Import JWT support through `JwtModule.forRoot(...)` or `JwtModule.forRootAsync(...)`.
36
+
37
+ ```typescript
38
+ import { Module } from '@fluojs/core';
39
+ import { JwtModule } from '@fluojs/jwt';
40
+
41
+ @Module({
42
+ imports: [
43
+ JwtModule.forRoot({
44
+ algorithms: ['HS256'],
45
+ secret: 'your-secure-secret',
46
+ issuer: 'my-api',
47
+ audience: 'my-app',
48
+ accessTokenTtlSeconds: 3600,
49
+ }),
50
+ ],
51
+ })
52
+ export class AuthModule {}
53
+ ```
54
+
55
+ ### Async Registration with Injected Settings
56
+
57
+ Use `JwtModule.forRootAsync(...)` when your JWT settings must come from another provider and still need to resolve into the standard module contract.
58
+
59
+ ```typescript
60
+ import { Module, type Token } from '@fluojs/core';
61
+ import { JwtModule } from '@fluojs/jwt';
62
+
63
+ const JWT_SETTINGS = Symbol('jwt-settings');
64
+
65
+ @Module({
66
+ imports: [
67
+ JwtModule.forRootAsync({
68
+ inject: [JWT_SETTINGS],
69
+ useFactory: async (settings) => ({
70
+ accessTokenTtlSeconds: 900,
71
+ algorithms: ['HS256'],
72
+ audience: 'my-app',
73
+ issuer: settings.issuer,
74
+ secret: settings.secret,
75
+ }),
76
+ }),
77
+ ],
78
+ providers: [
79
+ {
80
+ provide: JWT_SETTINGS as Token<{ issuer: string; secret: string }>,
81
+ useValue: {
82
+ issuer: 'my-api',
83
+ secret: 'your-secure-secret',
84
+ },
85
+ },
86
+ ],
87
+ })
88
+ export class AuthModule {}
89
+ ```
90
+
91
+ ### Sign and Verify Tokens
92
+
93
+ Inject `DefaultJwtSigner` to issue tokens and `DefaultJwtVerifier` to validate them.
94
+
95
+ ```typescript
96
+ import { DefaultJwtSigner, DefaultJwtVerifier } from '@fluojs/jwt';
97
+
98
+ // Sign
99
+ const token = await signer.signAccessToken({
100
+ sub: 'user-123',
101
+ roles: ['admin'],
102
+ scopes: ['read:profile'],
103
+ });
104
+
105
+ // Verify
106
+ const principal = await verifier.verifyAccessToken(token);
107
+ // principal: { subject: 'user-123', roles: ['admin'], scopes: ['read:profile'], ... }
108
+ ```
109
+
110
+ 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.
111
+
112
+ ## Common Patterns
113
+
114
+ ### Asymmetric Signing (RS256/ES256)
115
+
116
+ Use public/private key pairs for enhanced security across distributed systems.
117
+
118
+ ```typescript
119
+ const signer = new DefaultJwtSigner({
120
+ algorithms: ['RS256'],
121
+ privateKey: '...PEM...',
122
+ });
123
+
124
+ const verifier = new DefaultJwtVerifier({
125
+ algorithms: ['RS256'],
126
+ publicKey: '...PEM...',
127
+ });
128
+ ```
129
+
130
+ ### Principal Normalization
131
+
132
+ `@fluojs/jwt` automatically unifies `scope` (string) and `scopes` (array) claims into a single `scopes: string[]` property in the `JwtPrincipal`, ensuring consistent behavior for authorization guards.
133
+
134
+ ### Remote JWKS verification
135
+
136
+ When verification keys come from a remote JWKS endpoint, keep the fetch path bounded so auth traffic cannot hang on a slow or stalled identity provider.
137
+
138
+ ```typescript
139
+ const verifier = new DefaultJwtVerifier({
140
+ algorithms: ['RS256'],
141
+ jwksRequestTimeoutMs: 5_000,
142
+ jwksUri: 'https://issuer.example.com/.well-known/jwks.json',
143
+ });
144
+ ```
145
+
146
+ `jwksRequestTimeoutMs` defaults to `5_000` and aborts the outbound JWKS fetch once that budget is exceeded.
147
+
148
+ ## Public API Overview
149
+
150
+ ### Core Classes
151
+ - `JwtModule`: The main entry point for DI registration.
152
+ - `DefaultJwtSigner`: Handles token issuance with default claim filling.
153
+ - `DefaultJwtVerifier`: Handles token validation and normalization.
154
+ - `JwtService`: A convenience facade combining signing and verification.
155
+
156
+ ### Types
157
+ - `JwtPrincipal`: The normalized identity object (`subject`, `roles`, `scopes`, `claims`).
158
+ - `JwtVerifierOptions`: Configuration for algorithms, keys, and validation policy.
159
+
160
+ ## Related Packages
161
+
162
+ - `@fluojs/passport`: The auth execution layer that uses this core for guards and strategies.
163
+ - `@fluojs/config`: Recommended for managing secrets and JWT options across environments.
164
+
165
+ ## Example Sources
166
+
167
+ - `packages/jwt/src/module.test.ts`: Module registration and DI patterns.
168
+ - `packages/jwt/src/signing/signer.test.ts`: Token signing examples.
169
+ - `examples/auth-jwt-passport/src/auth/auth.service.ts`: Real-world token issuance.
@@ -0,0 +1,29 @@
1
+ import { FluoError } from '@fluojs/core';
2
+ /**
3
+ * Error thrown when JWT verification fails due to signature or structural issues.
4
+ */
5
+ export declare class JwtVerificationError extends FluoError {
6
+ constructor(message: string, options?: {
7
+ cause?: unknown;
8
+ code?: string;
9
+ });
10
+ }
11
+ /**
12
+ * Error thrown when a provided JWT string is malformed or not a valid token.
13
+ */
14
+ export declare class JwtInvalidTokenError extends JwtVerificationError {
15
+ constructor(message?: string);
16
+ }
17
+ /**
18
+ * Error thrown when a JWT is valid but has exceeded its expiration time.
19
+ */
20
+ export declare class JwtExpiredTokenError extends JwtVerificationError {
21
+ constructor(message?: string);
22
+ }
23
+ /**
24
+ * Error thrown when the JWT module is misconfigured (e.g. missing keys).
25
+ */
26
+ export declare class JwtConfigurationError extends FluoError {
27
+ constructor(message: string);
28
+ }
29
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAS;gBACrC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO;CAM9E;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,oBAAoB;gBAChD,OAAO,SAAiB;CAGrC;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,oBAAoB;gBAChD,OAAO,SAAqB;CAGzC;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;gBACtC,OAAO,EAAE,MAAM;CAG5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,46 @@
1
+ import { FluoError } from '@fluojs/core';
2
+
3
+ /**
4
+ * Error thrown when JWT verification fails due to signature or structural issues.
5
+ */
6
+ export class JwtVerificationError extends FluoError {
7
+ constructor(message, options = {}) {
8
+ super(message, {
9
+ cause: options.cause,
10
+ code: options.code ?? 'JWT_VERIFICATION_ERROR'
11
+ });
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Error thrown when a provided JWT string is malformed or not a valid token.
17
+ */
18
+ export class JwtInvalidTokenError extends JwtVerificationError {
19
+ constructor(message = 'Invalid JWT.') {
20
+ super(message, {
21
+ code: 'JWT_INVALID_TOKEN'
22
+ });
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Error thrown when a JWT is valid but has exceeded its expiration time.
28
+ */
29
+ export class JwtExpiredTokenError extends JwtVerificationError {
30
+ constructor(message = 'JWT has expired.') {
31
+ super(message, {
32
+ code: 'JWT_EXPIRED'
33
+ });
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Error thrown when the JWT module is misconfigured (e.g. missing keys).
39
+ */
40
+ export class JwtConfigurationError extends FluoError {
41
+ constructor(message) {
42
+ super(message, {
43
+ code: 'JWT_CONFIGURATION_ERROR'
44
+ });
45
+ }
46
+ }
@@ -0,0 +1,10 @@
1
+ export * from './errors.js';
2
+ export * from './signing/jwks.js';
3
+ export * from './module.js';
4
+ export * from './refresh/refresh-token.js';
5
+ export * from './service.js';
6
+ export * from './signing/signer.js';
7
+ export * from './status.js';
8
+ export * from './types.js';
9
+ export * from './signing/verifier.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export * from './errors.js';
2
+ export * from './signing/jwks.js';
3
+ export * from './module.js';
4
+ export * from './refresh/refresh-token.js';
5
+ export * from './service.js';
6
+ export * from './signing/signer.js';
7
+ export * from './status.js';
8
+ export * from './types.js';
9
+ export * from './signing/verifier.js';
@@ -0,0 +1,13 @@
1
+ import { type AsyncModuleOptions, type Constructor } from '@fluojs/core';
2
+ import type { JwtVerifierOptions } from './types.js';
3
+ type ModuleType = Constructor;
4
+ /**
5
+ * Registers JWT services and optional refresh-token support for an application module.
6
+ */
7
+ export declare class JwtModule {
8
+ static forRoot(options: JwtVerifierOptions): ModuleType;
9
+ static forRootAsync(options: AsyncModuleOptions<JwtVerifierOptions>): ModuleType;
10
+ private static createModule;
11
+ }
12
+ export {};
13
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +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;AAQhH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD,KAAK,UAAU,GAAG,WAAW,CAAC;AAmF9B;;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,UAAU;IAShF,OAAO,CAAC,MAAM,CAAC,YAAY;CAgB5B"}
package/dist/module.js ADDED
@@ -0,0 +1,93 @@
1
+ let _initClass;
2
+ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
+ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
+ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
+ import { Inject } from '@fluojs/core';
8
+ import { defineModuleMetadata } from '@fluojs/core/internal';
9
+ import { RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
10
+ import { JwtConfigurationError } from './errors.js';
11
+ import { normalizeRefreshTokenOptions, RefreshTokenService } from './refresh/refresh-token.js';
12
+ import { JwtService } from './service.js';
13
+ import { DefaultJwtSigner } from './signing/signer.js';
14
+ import { DefaultJwtVerifier, JWT_OPTIONS } from './signing/verifier.js';
15
+ function resolveRefreshTokenOptions(value) {
16
+ if (typeof value !== 'object' || value === null || !('refreshToken' in value)) {
17
+ throw new JwtConfigurationError('JWT refresh token options are not configured.');
18
+ }
19
+ return normalizeRefreshTokenOptions(value.refreshToken);
20
+ }
21
+ let _AsyncRefreshTokenSer;
22
+ class AsyncRefreshTokenServiceRegistrar {
23
+ static {
24
+ [_AsyncRefreshTokenSer, _initClass] = _applyDecs(this, [Inject(JWT_OPTIONS, DefaultJwtSigner, DefaultJwtVerifier, RUNTIME_CONTAINER)], []).c;
25
+ }
26
+ registered = false;
27
+ constructor(options, signer, verifier, container) {
28
+ this.options = options;
29
+ this.signer = signer;
30
+ this.verifier = verifier;
31
+ this.container = container;
32
+ }
33
+ onModuleInit() {
34
+ if (!this.options.refreshToken || this.registered) {
35
+ return;
36
+ }
37
+ const refreshTokenOptions = resolveRefreshTokenOptions(this.options);
38
+ this.container.register({
39
+ provide: RefreshTokenService,
40
+ scope: 'transient',
41
+ useFactory: () => new RefreshTokenService(refreshTokenOptions, this.signer, this.verifier)
42
+ });
43
+ this.registered = true;
44
+ }
45
+ static {
46
+ _initClass();
47
+ }
48
+ }
49
+ function createJwtModuleProviders(optionsProvider, includeRefreshTokenService, refreshTokenServiceScope, deferRefreshTokenServiceRegistration = false) {
50
+ const providers = [optionsProvider, DefaultJwtVerifier, DefaultJwtSigner, JwtService];
51
+ if (includeRefreshTokenService) {
52
+ providers.push(deferRefreshTokenServiceRegistration ? _AsyncRefreshTokenSer : {
53
+ inject: [JWT_OPTIONS, DefaultJwtSigner, DefaultJwtVerifier],
54
+ provide: RefreshTokenService,
55
+ scope: refreshTokenServiceScope,
56
+ useFactory: (...deps) => {
57
+ const [options, signer, verifier] = deps;
58
+ const refreshTokenOptions = resolveRefreshTokenOptions(options);
59
+ return new RefreshTokenService(refreshTokenOptions, signer, verifier);
60
+ }
61
+ });
62
+ }
63
+ return providers;
64
+ }
65
+
66
+ /**
67
+ * Registers JWT services and optional refresh-token support for an application module.
68
+ */
69
+ export class JwtModule {
70
+ static forRoot(options) {
71
+ return this.createModule({
72
+ provide: JWT_OPTIONS,
73
+ scope: 'singleton',
74
+ useValue: options
75
+ }, Boolean(options.refreshToken), Boolean(options.refreshToken), 'singleton');
76
+ }
77
+ static forRootAsync(options) {
78
+ return this.createModule({
79
+ inject: options.inject,
80
+ provide: JWT_OPTIONS,
81
+ scope: 'singleton',
82
+ useFactory: options.useFactory
83
+ }, true, false, 'transient', true);
84
+ }
85
+ static createModule(optionsProvider, includeRefreshTokenProvider, includeRefreshTokenExport, refreshTokenServiceScope, deferRefreshTokenServiceRegistration = false) {
86
+ class JwtRuntimeModule {}
87
+ defineModuleMetadata(JwtRuntimeModule, {
88
+ exports: [JwtService, DefaultJwtVerifier, DefaultJwtSigner, ...(includeRefreshTokenExport ? [RefreshTokenService] : [])],
89
+ providers: createJwtModuleProviders(optionsProvider, includeRefreshTokenProvider, refreshTokenServiceScope, deferRefreshTokenServiceRegistration)
90
+ });
91
+ return JwtRuntimeModule;
92
+ }
93
+ }
@@ -0,0 +1,48 @@
1
+ import type { DefaultJwtSigner } from '../signing/signer.js';
2
+ import type { DefaultJwtVerifier } from '../signing/verifier.js';
3
+ export interface RefreshTokenStore {
4
+ save(token: RefreshTokenRecord): Promise<void>;
5
+ find(tokenId: string): Promise<RefreshTokenRecord | undefined>;
6
+ revoke(tokenId: string): Promise<void>;
7
+ revokeBySubject(subject: string): Promise<void>;
8
+ consume?(input: RefreshTokenConsumeInput): Promise<RefreshTokenConsumeResult>;
9
+ }
10
+ export interface RefreshTokenConsumeInput {
11
+ tokenId: string;
12
+ subject: string;
13
+ family: string;
14
+ now: Date;
15
+ }
16
+ export type RefreshTokenConsumeResult = 'consumed' | 'already_used' | 'expired' | 'not_found' | 'mismatch' | 'invalid';
17
+ export interface RefreshTokenRecord {
18
+ id: string;
19
+ subject: string;
20
+ family: string;
21
+ expiresAt: Date;
22
+ used: boolean;
23
+ createdAt: Date;
24
+ }
25
+ export interface RefreshTokenOptions {
26
+ secret: string;
27
+ expiresInSeconds: number;
28
+ verifyMaxAgeSeconds?: number;
29
+ rotation: boolean;
30
+ store: RefreshTokenStore;
31
+ }
32
+ export declare function normalizeRefreshTokenOptions(options: RefreshTokenOptions | undefined): RefreshTokenOptions;
33
+ export declare class RefreshTokenService {
34
+ private readonly signer;
35
+ private readonly verifier;
36
+ private readonly options;
37
+ constructor(options: RefreshTokenOptions, signer: DefaultJwtSigner, verifier: DefaultJwtVerifier);
38
+ issueRefreshToken(subject: string): Promise<string>;
39
+ rotateRefreshToken(currentToken: string): Promise<{
40
+ accessToken: string;
41
+ refreshToken: string;
42
+ }>;
43
+ revokeRefreshToken(tokenId: string): Promise<void>;
44
+ revokeAllForSubject(subject: string): Promise<void>;
45
+ private issueRefreshTokenWithFamily;
46
+ private verifyRefreshClaims;
47
+ }
48
+ //# sourceMappingURL=refresh-token.d.ts.map
@@ -0,0 +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,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,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,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvH,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,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,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,mBAAmB,CA2B1G;AAQD,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"}