@jsango/auth 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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/base64url.d.ts +6 -0
- package/dist/internal/base64url.d.ts.map +1 -0
- package/dist/internal/base64url.js +21 -0
- package/dist/internal/base64url.js.map +1 -0
- package/dist/internal/crypto-utils.d.ts +11 -0
- package/dist/internal/crypto-utils.d.ts.map +1 -0
- package/dist/internal/crypto-utils.js +16 -0
- package/dist/internal/crypto-utils.js.map +1 -0
- package/dist/internal/timing.d.ts +5 -0
- package/dist/internal/timing.d.ts.map +1 -0
- package/dist/internal/timing.js +15 -0
- package/dist/internal/timing.js.map +1 -0
- package/dist/public/authentication/api-key.d.ts +17 -0
- package/dist/public/authentication/api-key.d.ts.map +1 -0
- package/dist/public/authentication/api-key.js +57 -0
- package/dist/public/authentication/api-key.js.map +1 -0
- package/dist/public/authentication/bearer.d.ts +27 -0
- package/dist/public/authentication/bearer.d.ts.map +1 -0
- package/dist/public/authentication/bearer.js +97 -0
- package/dist/public/authentication/bearer.js.map +1 -0
- package/dist/public/authentication/jwt.d.ts +60 -0
- package/dist/public/authentication/jwt.d.ts.map +1 -0
- package/dist/public/authentication/jwt.js +154 -0
- package/dist/public/authentication/jwt.js.map +1 -0
- package/dist/public/authentication/manager.d.ts +23 -0
- package/dist/public/authentication/manager.d.ts.map +1 -0
- package/dist/public/authentication/manager.js +64 -0
- package/dist/public/authentication/manager.js.map +1 -0
- package/dist/public/authentication/password.d.ts +24 -0
- package/dist/public/authentication/password.d.ts.map +1 -0
- package/dist/public/authentication/password.js +108 -0
- package/dist/public/authentication/password.js.map +1 -0
- package/dist/public/authentication/session.d.ts +31 -0
- package/dist/public/authentication/session.d.ts.map +1 -0
- package/dist/public/authentication/session.js +165 -0
- package/dist/public/authentication/session.js.map +1 -0
- package/dist/public/authentication/totp.d.ts +52 -0
- package/dist/public/authentication/totp.d.ts.map +1 -0
- package/dist/public/authentication/totp.js +138 -0
- package/dist/public/authentication/totp.js.map +1 -0
- package/dist/public/authorization/composite.d.ts +23 -0
- package/dist/public/authorization/composite.d.ts.map +1 -0
- package/dist/public/authorization/composite.js +79 -0
- package/dist/public/authorization/composite.js.map +1 -0
- package/dist/public/authorization/decision.d.ts +11 -0
- package/dist/public/authorization/decision.d.ts.map +1 -0
- package/dist/public/authorization/decision.js +19 -0
- package/dist/public/authorization/decision.js.map +1 -0
- package/dist/public/authorization/manager.d.ts +39 -0
- package/dist/public/authorization/manager.d.ts.map +1 -0
- package/dist/public/authorization/manager.js +138 -0
- package/dist/public/authorization/manager.js.map +1 -0
- package/dist/public/authorization/permission.d.ts +20 -0
- package/dist/public/authorization/permission.d.ts.map +1 -0
- package/dist/public/authorization/permission.js +46 -0
- package/dist/public/authorization/permission.js.map +1 -0
- package/dist/public/authorization/policy.d.ts +30 -0
- package/dist/public/authorization/policy.d.ts.map +1 -0
- package/dist/public/authorization/policy.js +93 -0
- package/dist/public/authorization/policy.js.map +1 -0
- package/dist/public/authorization/role.d.ts +17 -0
- package/dist/public/authorization/role.d.ts.map +1 -0
- package/dist/public/authorization/role.js +43 -0
- package/dist/public/authorization/role.js.map +1 -0
- package/dist/public/context/auth-context.d.ts +8 -0
- package/dist/public/context/auth-context.d.ts.map +1 -0
- package/dist/public/context/auth-context.js +33 -0
- package/dist/public/context/auth-context.js.map +1 -0
- package/dist/public/errors.d.ts +33 -0
- package/dist/public/errors.d.ts.map +1 -0
- package/dist/public/errors.js +89 -0
- package/dist/public/errors.js.map +1 -0
- package/dist/public/identity.d.ts +40 -0
- package/dist/public/identity.d.ts.map +1 -0
- package/dist/public/identity.js +102 -0
- package/dist/public/identity.js.map +1 -0
- package/dist/public/index.d.ts +23 -0
- package/dist/public/index.d.ts.map +1 -0
- package/dist/public/index.js +29 -0
- package/dist/public/index.js.map +1 -0
- package/dist/public/middleware/authenticate.d.ts +10 -0
- package/dist/public/middleware/authenticate.d.ts.map +1 -0
- package/dist/public/middleware/authenticate.js +65 -0
- package/dist/public/middleware/authenticate.js.map +1 -0
- package/dist/public/middleware/authorize.d.ts +11 -0
- package/dist/public/middleware/authorize.d.ts.map +1 -0
- package/dist/public/middleware/authorize.js +42 -0
- package/dist/public/middleware/authorize.js.map +1 -0
- package/dist/public/types.d.ts +75 -0
- package/dist/public/types.d.ts.map +1 -0
- package/dist/public/types.js +2 -0
- package/dist/public/types.js.map +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JSango 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.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @jsango/auth
|
|
2
|
+
|
|
3
|
+
> Authentication and authorization subsystem with Session, JWT, API Key strategies, Scrypt hashing, and object policies.
|
|
4
|
+
|
|
5
|
+
Part of the **[jsango](https://github.com/jsango/jsango)** backend framework for TypeScript.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @jsango/auth
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { AuthService, ScryptHasher, PolicyRegistry, definePolicy } from '@jsango/auth';
|
|
17
|
+
|
|
18
|
+
const hasher = new ScryptHasher();
|
|
19
|
+
const hash = await hasher.hash('secure-password');
|
|
20
|
+
const valid = await hasher.verify('secure-password', hash);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Documentation
|
|
24
|
+
|
|
25
|
+
For full architecture documentation and guides, visit the [jsango documentation](https://github.com/jsango/jsango/tree/main/docs).
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
MIT © jsango contributors
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../../src/internal/base64url.ts"],"names":[],"mappings":"AAAA,qBAAa,SAAS;WACN,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM;WAKtC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;WAQ7B,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAOpD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class Base64Url {
|
|
2
|
+
static encode(input) {
|
|
3
|
+
const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : input;
|
|
4
|
+
return buf.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
5
|
+
}
|
|
6
|
+
static decode(input) {
|
|
7
|
+
let base64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
8
|
+
while (base64.length % 4 !== 0) {
|
|
9
|
+
base64 += '=';
|
|
10
|
+
}
|
|
11
|
+
return Buffer.from(base64, 'base64').toString('utf8');
|
|
12
|
+
}
|
|
13
|
+
static decodeToBuffer(input) {
|
|
14
|
+
let base64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
15
|
+
while (base64.length % 4 !== 0) {
|
|
16
|
+
base64 += '=';
|
|
17
|
+
}
|
|
18
|
+
return Buffer.from(base64, 'base64');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=base64url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.js","sourceRoot":"","sources":["../../src/internal/base64url.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,SAAS;IACb,MAAM,CAAC,MAAM,CAAC,KAAsB;QACzC,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC3E,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC1F,CAAC;IAEM,MAAM,CAAC,MAAM,CAAC,KAAa;QAChC,IAAI,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACzD,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,CAAC;QAChB,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxD,CAAC;IAEM,MAAM,CAAC,cAAc,CAAC,KAAa;QACxC,IAAI,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACzD,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,CAAC;QAChB,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class CryptoUtils {
|
|
2
|
+
/**
|
|
3
|
+
* Generates a cryptographically secure random token string.
|
|
4
|
+
*/
|
|
5
|
+
static generateSecureToken(byteLength?: number): string;
|
|
6
|
+
/**
|
|
7
|
+
* Hashes a value with SHA-256 (used for API key hashing and token identification).
|
|
8
|
+
*/
|
|
9
|
+
static sha256(data: string): string;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=crypto-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-utils.d.ts","sourceRoot":"","sources":["../../src/internal/crypto-utils.ts"],"names":[],"mappings":"AAEA,qBAAa,WAAW;IACtB;;OAEG;WACW,mBAAmB,CAAC,UAAU,SAAK,GAAG,MAAM;IAI1D;;OAEG;WACW,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAG3C"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
export class CryptoUtils {
|
|
3
|
+
/**
|
|
4
|
+
* Generates a cryptographically secure random token string.
|
|
5
|
+
*/
|
|
6
|
+
static generateSecureToken(byteLength = 32) {
|
|
7
|
+
return crypto.randomBytes(byteLength).toString('hex');
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Hashes a value with SHA-256 (used for API key hashing and token identification).
|
|
11
|
+
*/
|
|
12
|
+
static sha256(data) {
|
|
13
|
+
return crypto.createHash('sha256').update(data, 'utf8').digest('hex');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=crypto-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-utils.js","sourceRoot":"","sources":["../../src/internal/crypto-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,MAAM,OAAO,WAAW;IACtB;;OAEG;IACI,MAAM,CAAC,mBAAmB,CAAC,UAAU,GAAG,EAAE;QAC/C,OAAO,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,MAAM,CAAC,IAAY;QAC/B,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../src/internal/timing.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAWnE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Constant-time string comparison to prevent timing attacks.
|
|
4
|
+
*/
|
|
5
|
+
export function timingSafeEqualString(a, b) {
|
|
6
|
+
const bufA = Buffer.from(a, 'utf8');
|
|
7
|
+
const bufB = Buffer.from(b, 'utf8');
|
|
8
|
+
if (bufA.length !== bufB.length) {
|
|
9
|
+
// Perform dummy timing-safe comparison on identical buffer to equalize execution time
|
|
10
|
+
crypto.timingSafeEqual(bufA, bufA);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return crypto.timingSafeEqual(bufA, bufB);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=timing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../../src/internal/timing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAS,EAAE,CAAS;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAEpC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,sFAAsF;QACtF,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HttpRequest, RequestContext } from '@jsango/http';
|
|
2
|
+
import type { AuthenticationResult, IAuthenticationStrategy, Identity } from '../types.js';
|
|
3
|
+
export interface IApiKeyVerifier {
|
|
4
|
+
verifyApiKey(hashedKey: string, rawKey: string): Promise<Identity | undefined>;
|
|
5
|
+
}
|
|
6
|
+
export interface ApiKeyAuthStrategyOptions {
|
|
7
|
+
readonly verifier: IApiKeyVerifier;
|
|
8
|
+
readonly headerName?: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
export declare class ApiKeyAuthenticationStrategy implements IAuthenticationStrategy {
|
|
11
|
+
readonly name = "api_key";
|
|
12
|
+
readonly verifier: IApiKeyVerifier;
|
|
13
|
+
readonly headerName: string;
|
|
14
|
+
constructor(options: ApiKeyAuthStrategyOptions);
|
|
15
|
+
authenticate(request: HttpRequest, _context: RequestContext): Promise<AuthenticationResult>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=api-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.d.ts","sourceRoot":"","sources":["../../../src/public/authentication/api-key.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGhE,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE3F,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED,qBAAa,4BAA6B,YAAW,uBAAuB;IAC1E,SAAgB,IAAI,aAAa;IACjC,SAAgB,QAAQ,EAAE,eAAe,CAAC;IAC1C,SAAgB,UAAU,EAAE,MAAM,CAAC;gBAEhB,OAAO,EAAE,yBAAyB;IAKxC,YAAY,CACvB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,oBAAoB,CAAC;CAiDjC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { CryptoUtils } from '../../internal/crypto-utils.js';
|
|
2
|
+
import { AnonymousIdentity } from '../identity.js';
|
|
3
|
+
export class ApiKeyAuthenticationStrategy {
|
|
4
|
+
name = 'api_key';
|
|
5
|
+
verifier;
|
|
6
|
+
headerName;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.verifier = options.verifier;
|
|
9
|
+
this.headerName = (options.headerName ?? 'x-api-key').toLowerCase();
|
|
10
|
+
}
|
|
11
|
+
async authenticate(request, _context) {
|
|
12
|
+
let rawKey = request.headers.get(this.headerName);
|
|
13
|
+
// Also check Authorization: ApiKey <key>
|
|
14
|
+
if (!rawKey) {
|
|
15
|
+
const authHeader = request.headers.get('authorization');
|
|
16
|
+
if (authHeader) {
|
|
17
|
+
const match = /^ApiKey\s+(.+)$/i.exec(authHeader);
|
|
18
|
+
if (match && match[1]) {
|
|
19
|
+
rawKey = match[1].trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (!rawKey) {
|
|
24
|
+
return {
|
|
25
|
+
status: 'unauthenticated',
|
|
26
|
+
identity: new AnonymousIdentity(),
|
|
27
|
+
strategy: this.name,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const hashedKey = CryptoUtils.sha256(rawKey);
|
|
31
|
+
try {
|
|
32
|
+
const identity = await this.verifier.verifyApiKey(hashedKey, rawKey);
|
|
33
|
+
if (!identity) {
|
|
34
|
+
return {
|
|
35
|
+
status: 'invalid_credentials',
|
|
36
|
+
identity: new AnonymousIdentity(),
|
|
37
|
+
strategy: this.name,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
status: 'authenticated',
|
|
42
|
+
identity,
|
|
43
|
+
strategy: this.name,
|
|
44
|
+
metadata: { hashedKeyPrefix: hashedKey.slice(0, 8) },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
return {
|
|
49
|
+
status: 'invalid_credentials',
|
|
50
|
+
identity: new AnonymousIdentity(),
|
|
51
|
+
strategy: this.name,
|
|
52
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=api-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.js","sourceRoot":"","sources":["../../../src/public/authentication/api-key.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAYnD,MAAM,OAAO,4BAA4B;IACvB,IAAI,GAAG,SAAS,CAAC;IACjB,QAAQ,CAAkB;IAC1B,UAAU,CAAS;IAEnC,YAAmB,OAAkC;QACnD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;IACtE,CAAC;IAEM,KAAK,CAAC,YAAY,CACvB,OAAoB,EACpB,QAAwB;QAExB,IAAI,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAElD,yCAAyC;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YACxD,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBAClD,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;oBACtB,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO;gBACL,MAAM,EAAE,iBAAiB;gBACzB,QAAQ,EAAE,IAAI,iBAAiB,EAAE;gBACjC,QAAQ,EAAE,IAAI,CAAC,IAAI;aACpB,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAE7C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YACrE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO;oBACL,MAAM,EAAE,qBAAqB;oBAC7B,QAAQ,EAAE,IAAI,iBAAiB,EAAE;oBACjC,QAAQ,EAAE,IAAI,CAAC,IAAI;iBACpB,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,MAAM,EAAE,eAAe;gBACvB,QAAQ;gBACR,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,QAAQ,EAAE,EAAE,eAAe,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;aACrD,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,MAAM,EAAE,qBAAqB;gBAC7B,QAAQ,EAAE,IAAI,iBAAiB,EAAE;gBACjC,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;aAC3D,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HttpRequest, RequestContext } from '@jsango/http';
|
|
2
|
+
import type { AuthenticationResult, IAuthenticationStrategy, Identity } from '../types.js';
|
|
3
|
+
import type { JwtService, JwtPayload } from './jwt.js';
|
|
4
|
+
export interface ITokenVerifier {
|
|
5
|
+
verifyToken(token: string): Promise<Identity | undefined>;
|
|
6
|
+
}
|
|
7
|
+
export declare class JwtTokenVerifier implements ITokenVerifier {
|
|
8
|
+
private readonly jwt;
|
|
9
|
+
private readonly identityResolver?;
|
|
10
|
+
constructor(options: {
|
|
11
|
+
jwt: JwtService;
|
|
12
|
+
identityResolver?: (payload: JwtPayload) => Promise<Identity> | Identity;
|
|
13
|
+
});
|
|
14
|
+
verifyToken(token: string): Promise<Identity | undefined>;
|
|
15
|
+
}
|
|
16
|
+
export interface BearerAuthStrategyOptions {
|
|
17
|
+
readonly verifier: ITokenVerifier;
|
|
18
|
+
readonly realm?: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
export declare class BearerTokenAuthenticationStrategy implements IAuthenticationStrategy {
|
|
21
|
+
readonly name = "bearer";
|
|
22
|
+
readonly verifier: ITokenVerifier;
|
|
23
|
+
readonly realm: string;
|
|
24
|
+
constructor(options: BearerAuthStrategyOptions);
|
|
25
|
+
authenticate(request: HttpRequest, _context: RequestContext): Promise<AuthenticationResult>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=bearer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../../src/public/authentication/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGvD,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;CAC3D;AAED,qBAAa,gBAAiB,YAAW,cAAc;IACrD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CACoC;gBAEnD,OAAO,EAAE;QAC1B,GAAG,EAAE,UAAU,CAAC;QAChB,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;KAC1E;IAKY,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;CAsBvE;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,qBAAa,iCAAkC,YAAW,uBAAuB;IAC/E,SAAgB,IAAI,YAAY;IAChC,SAAgB,QAAQ,EAAE,cAAc,CAAC;IACzC,SAAgB,KAAK,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE,yBAAyB;IAKxC,YAAY,CACvB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,oBAAoB,CAAC;CA+DjC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { AnonymousIdentity, UserIdentity } from '../identity.js';
|
|
2
|
+
import { TokenExpiredError } from '../errors.js';
|
|
3
|
+
export class JwtTokenVerifier {
|
|
4
|
+
jwt;
|
|
5
|
+
identityResolver;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.jwt = options.jwt;
|
|
8
|
+
this.identityResolver = options.identityResolver;
|
|
9
|
+
}
|
|
10
|
+
async verifyToken(token) {
|
|
11
|
+
const payload = await this.jwt.verify(token);
|
|
12
|
+
if (this.identityResolver) {
|
|
13
|
+
return this.identityResolver(payload);
|
|
14
|
+
}
|
|
15
|
+
const sub = payload.sub ?? payload['id'] ?? 'anonymous';
|
|
16
|
+
const roles = Array.isArray(payload['roles']) ? payload['roles'] : [];
|
|
17
|
+
const permissions = Array.isArray(payload['permissions'])
|
|
18
|
+
? payload['permissions']
|
|
19
|
+
: [];
|
|
20
|
+
const tenantId = typeof payload['tenantId'] === 'string' ? payload['tenantId'] : undefined;
|
|
21
|
+
return new UserIdentity({
|
|
22
|
+
id: sub,
|
|
23
|
+
roles,
|
|
24
|
+
permissions,
|
|
25
|
+
tenantId,
|
|
26
|
+
metadata: payload,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class BearerTokenAuthenticationStrategy {
|
|
31
|
+
name = 'bearer';
|
|
32
|
+
verifier;
|
|
33
|
+
realm;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.verifier = options.verifier;
|
|
36
|
+
this.realm = options.realm ?? 'Application';
|
|
37
|
+
}
|
|
38
|
+
async authenticate(request, _context) {
|
|
39
|
+
const authHeader = request.headers.get('authorization');
|
|
40
|
+
if (!authHeader) {
|
|
41
|
+
return {
|
|
42
|
+
status: 'unauthenticated',
|
|
43
|
+
identity: new AnonymousIdentity(),
|
|
44
|
+
strategy: this.name,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const parts = authHeader.trim().split(/\s+/);
|
|
48
|
+
const scheme = parts[0]?.toLowerCase();
|
|
49
|
+
if (scheme !== 'bearer') {
|
|
50
|
+
return {
|
|
51
|
+
status: 'unauthenticated',
|
|
52
|
+
identity: new AnonymousIdentity(),
|
|
53
|
+
strategy: this.name,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (parts.length < 2 || !parts[1] || parts[1].trim().length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
status: 'malformed_credentials',
|
|
59
|
+
identity: new AnonymousIdentity(),
|
|
60
|
+
strategy: this.name,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const token = parts[1].trim();
|
|
64
|
+
try {
|
|
65
|
+
const identity = await this.verifier.verifyToken(token);
|
|
66
|
+
if (!identity) {
|
|
67
|
+
return {
|
|
68
|
+
status: 'invalid_credentials',
|
|
69
|
+
identity: new AnonymousIdentity(),
|
|
70
|
+
strategy: this.name,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
status: 'authenticated',
|
|
75
|
+
identity,
|
|
76
|
+
strategy: this.name,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof TokenExpiredError) {
|
|
81
|
+
return {
|
|
82
|
+
status: 'expired_credentials',
|
|
83
|
+
identity: new AnonymousIdentity(),
|
|
84
|
+
strategy: this.name,
|
|
85
|
+
error: err,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
status: 'invalid_credentials',
|
|
90
|
+
identity: new AnonymousIdentity(),
|
|
91
|
+
strategy: this.name,
|
|
92
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=bearer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bearer.js","sourceRoot":"","sources":["../../../src/public/authentication/bearer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type JwtAlgorithm = 'HS256' | 'HS384' | 'HS512';
|
|
2
|
+
export interface JwtHeader {
|
|
3
|
+
readonly alg: JwtAlgorithm;
|
|
4
|
+
readonly typ: 'JWT';
|
|
5
|
+
readonly kid?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface JwtPayload {
|
|
8
|
+
readonly sub?: string | undefined;
|
|
9
|
+
readonly iss?: string | undefined;
|
|
10
|
+
readonly aud?: string | readonly string[] | undefined;
|
|
11
|
+
readonly exp?: number | undefined;
|
|
12
|
+
readonly nbf?: number | undefined;
|
|
13
|
+
readonly iat?: number | undefined;
|
|
14
|
+
readonly jti?: string | undefined;
|
|
15
|
+
readonly [key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface JwtSignOptions {
|
|
18
|
+
readonly algorithm?: JwtAlgorithm | undefined;
|
|
19
|
+
readonly expiresInSeconds?: number | undefined;
|
|
20
|
+
readonly notBeforeSeconds?: number | undefined;
|
|
21
|
+
readonly issuer?: string | undefined;
|
|
22
|
+
readonly audience?: string | readonly string[] | undefined;
|
|
23
|
+
readonly jwtId?: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
export interface JwtVerifyOptions {
|
|
26
|
+
readonly allowedAlgorithms?: readonly JwtAlgorithm[] | undefined;
|
|
27
|
+
readonly issuer?: string | undefined;
|
|
28
|
+
readonly audience?: string | readonly string[] | undefined;
|
|
29
|
+
readonly clockToleranceSeconds?: number | undefined;
|
|
30
|
+
}
|
|
31
|
+
export interface ITokenRevocationStore {
|
|
32
|
+
isRevoked(tokenId: string): Promise<boolean>;
|
|
33
|
+
revoke(tokenId: string, expiresAt: number): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare class JwtService {
|
|
36
|
+
private readonly secret;
|
|
37
|
+
private readonly defaultAlgorithm;
|
|
38
|
+
private readonly allowedAlgorithms;
|
|
39
|
+
private readonly revocationStore?;
|
|
40
|
+
constructor(secretOrOptions: string | {
|
|
41
|
+
secret: string;
|
|
42
|
+
defaultAlgorithm?: JwtAlgorithm | undefined;
|
|
43
|
+
allowedAlgorithms?: readonly JwtAlgorithm[] | undefined;
|
|
44
|
+
revocationStore?: ITokenRevocationStore | undefined;
|
|
45
|
+
}, additionalOptions?: {
|
|
46
|
+
defaultAlgorithm?: JwtAlgorithm | undefined;
|
|
47
|
+
allowedAlgorithms?: readonly JwtAlgorithm[] | undefined;
|
|
48
|
+
revocationStore?: ITokenRevocationStore | undefined;
|
|
49
|
+
});
|
|
50
|
+
sign(payload: Record<string, unknown>, options?: JwtSignOptions): string;
|
|
51
|
+
verify<T extends JwtPayload = JwtPayload>(token: string, options?: JwtVerifyOptions): Promise<T>;
|
|
52
|
+
private createSignature;
|
|
53
|
+
}
|
|
54
|
+
export declare class MemoryTokenRevocationStore implements ITokenRevocationStore {
|
|
55
|
+
private readonly revoked;
|
|
56
|
+
isRevoked(tokenId: string): Promise<boolean>;
|
|
57
|
+
revoke(tokenId: string, expiresAt: number): Promise<void>;
|
|
58
|
+
clear(): void;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../../src/public/authentication/jwt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import { Base64Url } from '../../internal/base64url.js';
|
|
3
|
+
import { InvalidCredentialsError, TokenExpiredError } from '../errors.js';
|
|
4
|
+
export class JwtService {
|
|
5
|
+
secret;
|
|
6
|
+
defaultAlgorithm;
|
|
7
|
+
allowedAlgorithms;
|
|
8
|
+
revocationStore;
|
|
9
|
+
constructor(secretOrOptions, additionalOptions) {
|
|
10
|
+
const options = typeof secretOrOptions === 'string'
|
|
11
|
+
? { secret: secretOrOptions, ...additionalOptions }
|
|
12
|
+
: secretOrOptions;
|
|
13
|
+
if (!options?.secret || options.secret.length < 16) {
|
|
14
|
+
throw new Error('JWT secret must be at least 16 characters long for cryptographic safety.');
|
|
15
|
+
}
|
|
16
|
+
this.secret = options.secret;
|
|
17
|
+
this.defaultAlgorithm = options.defaultAlgorithm ?? 'HS256';
|
|
18
|
+
this.allowedAlgorithms = Object.freeze([
|
|
19
|
+
...(options.allowedAlgorithms ?? ['HS256', 'HS384', 'HS512']),
|
|
20
|
+
]);
|
|
21
|
+
this.revocationStore = options.revocationStore;
|
|
22
|
+
}
|
|
23
|
+
sign(payload, options = {}) {
|
|
24
|
+
const algorithm = options.algorithm ?? this.defaultAlgorithm;
|
|
25
|
+
if (!this.allowedAlgorithms.includes(algorithm)) {
|
|
26
|
+
throw new Error(`Algorithm "${algorithm}" is not in the allowed algorithms list.`);
|
|
27
|
+
}
|
|
28
|
+
const now = Math.floor(Date.now() / 1000);
|
|
29
|
+
const fullPayload = {
|
|
30
|
+
...payload,
|
|
31
|
+
iat: payload['iat'] ?? now,
|
|
32
|
+
};
|
|
33
|
+
if (options.expiresInSeconds !== undefined) {
|
|
34
|
+
fullPayload['exp'] = now + options.expiresInSeconds;
|
|
35
|
+
}
|
|
36
|
+
if (options.notBeforeSeconds !== undefined) {
|
|
37
|
+
fullPayload['nbf'] = now + options.notBeforeSeconds;
|
|
38
|
+
}
|
|
39
|
+
if (options.issuer !== undefined) {
|
|
40
|
+
fullPayload['iss'] = options.issuer;
|
|
41
|
+
}
|
|
42
|
+
if (options.audience !== undefined) {
|
|
43
|
+
fullPayload['aud'] = options.audience;
|
|
44
|
+
}
|
|
45
|
+
if (options.jwtId !== undefined) {
|
|
46
|
+
fullPayload['jti'] = options.jwtId;
|
|
47
|
+
}
|
|
48
|
+
const header = {
|
|
49
|
+
alg: algorithm,
|
|
50
|
+
typ: 'JWT',
|
|
51
|
+
};
|
|
52
|
+
const encodedHeader = Base64Url.encode(JSON.stringify(header));
|
|
53
|
+
const encodedPayload = Base64Url.encode(JSON.stringify(fullPayload));
|
|
54
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
55
|
+
const signature = this.createSignature(signingInput, algorithm);
|
|
56
|
+
return `${signingInput}.${signature}`;
|
|
57
|
+
}
|
|
58
|
+
async verify(token, options = {}) {
|
|
59
|
+
const parts = token.split('.');
|
|
60
|
+
if (parts.length !== 3) {
|
|
61
|
+
throw new InvalidCredentialsError('Malformed JWT format.');
|
|
62
|
+
}
|
|
63
|
+
const [encodedHeader, encodedPayload, signature] = parts;
|
|
64
|
+
// 1. Decode and validate header
|
|
65
|
+
let header;
|
|
66
|
+
try {
|
|
67
|
+
header = JSON.parse(Base64Url.decode(encodedHeader));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
throw new InvalidCredentialsError('Invalid JWT header encoding.');
|
|
71
|
+
}
|
|
72
|
+
// Strict algorithm confusion defense
|
|
73
|
+
const allowed = options.allowedAlgorithms ?? this.allowedAlgorithms;
|
|
74
|
+
if (!header.alg || !allowed.includes(header.alg) || header.alg === 'none') {
|
|
75
|
+
throw new InvalidCredentialsError(`Disallowed JWT algorithm "${header.alg}".`);
|
|
76
|
+
}
|
|
77
|
+
// 2. Constant-time signature verification
|
|
78
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
79
|
+
const expectedSignature = this.createSignature(signingInput, header.alg);
|
|
80
|
+
const sigBuf = Base64Url.decodeToBuffer(signature);
|
|
81
|
+
const expectedSigBuf = Base64Url.decodeToBuffer(expectedSignature);
|
|
82
|
+
if (sigBuf.length !== expectedSigBuf.length ||
|
|
83
|
+
!crypto.timingSafeEqual(sigBuf, expectedSigBuf)) {
|
|
84
|
+
throw new InvalidCredentialsError('Invalid JWT signature.');
|
|
85
|
+
}
|
|
86
|
+
// 3. Decode and validate payload claims
|
|
87
|
+
let payload;
|
|
88
|
+
try {
|
|
89
|
+
payload = JSON.parse(Base64Url.decode(encodedPayload));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new InvalidCredentialsError('Invalid JWT payload encoding.');
|
|
93
|
+
}
|
|
94
|
+
const now = Math.floor(Date.now() / 1000);
|
|
95
|
+
const tolerance = options.clockToleranceSeconds ?? 0;
|
|
96
|
+
// Check expiration (exp)
|
|
97
|
+
if (typeof payload.exp === 'number' && now - tolerance >= payload.exp) {
|
|
98
|
+
throw new TokenExpiredError();
|
|
99
|
+
}
|
|
100
|
+
// Check not before (nbf)
|
|
101
|
+
if (typeof payload.nbf === 'number' && now + tolerance < payload.nbf) {
|
|
102
|
+
throw new InvalidCredentialsError('JWT token is not active yet.');
|
|
103
|
+
}
|
|
104
|
+
// Check issuer (iss)
|
|
105
|
+
if (options.issuer && payload.iss !== options.issuer) {
|
|
106
|
+
throw new InvalidCredentialsError(`JWT issuer mismatch: expected "${options.issuer}".`);
|
|
107
|
+
}
|
|
108
|
+
// Check audience (aud)
|
|
109
|
+
if (options.audience) {
|
|
110
|
+
const aud = payload.aud;
|
|
111
|
+
const expectedAud = Array.isArray(options.audience) ? options.audience : [options.audience];
|
|
112
|
+
const actualAud = Array.isArray(aud) ? aud : [aud];
|
|
113
|
+
const match = actualAud.some((a) => expectedAud.includes(a));
|
|
114
|
+
if (!match) {
|
|
115
|
+
throw new InvalidCredentialsError('JWT audience mismatch.');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Check revocation (jti)
|
|
119
|
+
if (this.revocationStore && payload.jti) {
|
|
120
|
+
const isRevoked = await this.revocationStore.isRevoked(payload.jti);
|
|
121
|
+
if (isRevoked) {
|
|
122
|
+
throw new InvalidCredentialsError('JWT token has been revoked.');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return payload;
|
|
126
|
+
}
|
|
127
|
+
createSignature(signingInput, algorithm) {
|
|
128
|
+
const hmacAlgorithm = algorithm === 'HS256' ? 'sha256' : algorithm === 'HS384' ? 'sha384' : 'sha512';
|
|
129
|
+
const hmac = crypto.createHmac(hmacAlgorithm, this.secret);
|
|
130
|
+
hmac.update(signingInput);
|
|
131
|
+
return Base64Url.encode(hmac.digest());
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export class MemoryTokenRevocationStore {
|
|
135
|
+
revoked = new Map();
|
|
136
|
+
async isRevoked(tokenId) {
|
|
137
|
+
const expiresAt = this.revoked.get(tokenId);
|
|
138
|
+
if (!expiresAt) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (Date.now() / 1000 > expiresAt) {
|
|
142
|
+
this.revoked.delete(tokenId);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
async revoke(tokenId, expiresAt) {
|
|
148
|
+
this.revoked.set(tokenId, expiresAt);
|
|
149
|
+
}
|
|
150
|
+
clear() {
|
|
151
|
+
this.revoked.clear();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../../src/public/authentication/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AA0C1E,MAAM,OAAO,UAAU;IACJ,MAAM,CAAS;IACf,gBAAgB,CAAe;IAC/B,iBAAiB,CAA0B;IAC3C,eAAe,CAAqC;IAErE,YACE,eAOK,EACL,iBAIC;QAED,MAAM,OAAO,GACX,OAAO,eAAe,KAAK,QAAQ;YACjC,CAAC,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,iBAAiB,EAAE;YACnD,CAAC,CAAC,eAAe,CAAC;QAEtB,IAAI,CAAC,OAAO,EAAE,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;QAC9F,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,OAAO,CAAC;QAC5D,IAAI,CAAC,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC;YACrC,GAAG,CAAC,OAAO,CAAC,iBAAiB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;SAC9D,CAAC,CAAC;QACH,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IACjD,CAAC;IAEM,IAAI,CAAC,OAAgC,EAAE,UAA0B,EAAE;QACxE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,gBAAgB,CAAC;QAC7D,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,cAAc,SAAS,0CAA0C,CAAC,CAAC;QACrF,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,MAAM,WAAW,GAA4B;YAC3C,GAAG,OAAO;YACV,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG;SAC3B,CAAC;QAEF,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;YAC3C,WAAW,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC;QACtD,CAAC;QACD,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;YAC3C,WAAW,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,OAAO,CAAC,gBAAgB,CAAC;QACtD,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjC,WAAW,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;QACtC,CAAC;QACD,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACnC,WAAW,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;QACxC,CAAC;QACD,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,WAAW,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;QACrC,CAAC;QAED,MAAM,MAAM,GAAc;YACxB,GAAG,EAAE,SAAS;YACd,GAAG,EAAE,KAAK;SACX,CAAC;QAEF,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,MAAM,cAAc,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;QACrE,MAAM,YAAY,GAAG,GAAG,aAAa,IAAI,cAAc,EAAE,CAAC;QAE1D,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAChE,OAAO,GAAG,YAAY,IAAI,SAAS,EAAE,CAAC;IACxC,CAAC;IAEM,KAAK,CAAC,MAAM,CACjB,KAAa,EACb,UAA4B,EAAE;QAE9B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,uBAAuB,CAAC,uBAAuB,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,CAAC,aAAa,EAAE,cAAc,EAAE,SAAS,CAAC,GAAG,KAAiC,CAAC;QAErF,gCAAgC;QAChC,IAAI,MAAiB,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,CAAc,CAAC;QACpE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,uBAAuB,CAAC,8BAA8B,CAAC,CAAC;QACpE,CAAC;QAED,qCAAqC;QACrC,MAAM,OAAO,GAAG,OAAO,CAAC,iBAAiB,IAAI,IAAI,CAAC,iBAAiB,CAAC;QACpE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,IAAK,MAAM,CAAC,GAAc,KAAK,MAAM,EAAE,CAAC;YACtF,MAAM,IAAI,uBAAuB,CAAC,6BAA6B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;QACjF,CAAC;QAED,0CAA0C;QAC1C,MAAM,YAAY,GAAG,GAAG,aAAa,IAAI,cAAc,EAAE,CAAC;QAC1D,MAAM,iBAAiB,GAAG,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QAEzE,MAAM,MAAM,GAAG,SAAS,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;QAEnE,IACE,MAAM,CAAC,MAAM,KAAK,cAAc,CAAC,MAAM;YACvC,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,cAAc,CAAC,EAC/C,CAAC;YACD,MAAM,IAAI,uBAAuB,CAAC,wBAAwB,CAAC,CAAC;QAC9D,CAAC;QAED,wCAAwC;QACxC,IAAI,OAAU,CAAC;QACf,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,CAAC,CAAM,CAAC;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,uBAAuB,CAAC,+BAA+B,CAAC,CAAC;QACrE,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,qBAAqB,IAAI,CAAC,CAAC;QAErD,yBAAyB;QACzB,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,GAAG,GAAG,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YACtE,MAAM,IAAI,iBAAiB,EAAE,CAAC;QAChC,CAAC;QAED,yBAAyB;QACzB,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,GAAG,GAAG,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YACrE,MAAM,IAAI,uBAAuB,CAAC,8BAA8B,CAAC,CAAC;QACpE,CAAC;QAED,qBAAqB;QACrB,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;YACrD,MAAM,IAAI,uBAAuB,CAAC,kCAAkC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QAC1F,CAAC;QAED,uBAAuB;QACvB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;YACxB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC5F,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACnD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAW,CAAC,CAAC,CAAC;YACvE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,uBAAuB,CAAC,wBAAwB,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAED,yBAAyB;QACzB,IAAI,IAAI,CAAC,eAAe,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YACxC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACpE,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,IAAI,uBAAuB,CAAC,6BAA6B,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,eAAe,CAAC,YAAoB,EAAE,SAAuB;QACnE,MAAM,aAAa,GACjB,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;QACjF,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC1B,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;CACF;AAED,MAAM,OAAO,0BAA0B;IACpB,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE9C,KAAK,CAAC,SAAS,CAAC,OAAe;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC7B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,SAAiB;QACpD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAEM,KAAK;QACV,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACF"}
|