@venizia/ignis-docs 0.2.0 → 0.2.1-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.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +22 -11
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +6 -2
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +182 -93
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +107 -322
- package/content/extensions/components/authentication/api.md +454 -603
- package/content/extensions/components/authentication/errors.md +121 -498
- package/content/extensions/components/authentication/index.md +88 -801
- package/content/extensions/components/authentication/usage.md +207 -956
- package/content/extensions/components/authorization/api.md +736 -656
- package/content/extensions/components/authorization/errors.md +168 -206
- package/content/extensions/components/authorization/index.md +82 -797
- package/content/extensions/components/authorization/usage.md +194 -527
- package/content/extensions/components/health-check.md +71 -243
- package/content/extensions/components/mail/api.md +504 -287
- package/content/extensions/components/mail/errors.md +73 -61
- package/content/extensions/components/mail/index.md +96 -467
- package/content/extensions/components/mail/usage.md +130 -172
- package/content/extensions/components/request-tracker.md +66 -173
- package/content/extensions/components/socket-io/api.md +195 -13
- package/content/extensions/components/socket-io/errors.md +3 -3
- package/content/extensions/components/socket-io/index.md +50 -337
- package/content/extensions/components/socket-io/usage.md +143 -26
- package/content/extensions/components/static-asset/api.md +410 -142
- package/content/extensions/components/static-asset/errors.md +110 -53
- package/content/extensions/components/static-asset/index.md +79 -608
- package/content/extensions/components/static-asset/usage.md +180 -300
- package/content/extensions/components/websocket/api.md +275 -399
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +74 -407
- package/content/extensions/components/websocket/usage.md +110 -341
- package/content/extensions/helpers/cron/index.md +51 -160
- package/content/extensions/helpers/crypto/index.md +62 -483
- package/content/extensions/helpers/crypto/reference.md +456 -0
- package/content/extensions/helpers/env/index.md +60 -178
- package/content/extensions/helpers/error/index.md +221 -207
- package/content/extensions/helpers/inversion/index.md +65 -556
- package/content/extensions/helpers/inversion/reference.md +522 -0
- package/content/extensions/helpers/kafka/admin.md +20 -1
- package/content/extensions/helpers/kafka/compile-binary.md +41 -35
- package/content/extensions/helpers/kafka/consumer.md +54 -20
- package/content/extensions/helpers/kafka/examples.md +21 -16
- package/content/extensions/helpers/kafka/index.md +80 -610
- package/content/extensions/helpers/kafka/producer.md +134 -5
- package/content/extensions/helpers/kafka/schema-registry.md +45 -70
- package/content/extensions/helpers/logger/hf-logger.md +193 -0
- package/content/extensions/helpers/logger/index.md +64 -563
- package/content/extensions/helpers/logger/pino.md +85 -0
- package/content/extensions/helpers/logger/reference.md +746 -0
- package/content/extensions/helpers/network/api.md +241 -195
- package/content/extensions/helpers/network/index.md +72 -530
- package/content/extensions/helpers/queue/index.md +72 -900
- package/content/extensions/helpers/queue/reference.md +467 -0
- package/content/extensions/helpers/redis/index.md +73 -645
- package/content/extensions/helpers/redis/reference.md +727 -0
- package/content/extensions/helpers/secrets/index.md +66 -0
- package/content/extensions/helpers/socket-io/api.md +305 -203
- package/content/extensions/helpers/socket-io/index.md +66 -432
- package/content/extensions/helpers/storage/api.md +564 -462
- package/content/extensions/helpers/storage/index.md +77 -573
- package/content/extensions/helpers/types/index.md +66 -499
- package/content/extensions/helpers/types/reference.md +650 -0
- package/content/extensions/helpers/uid/index.md +58 -227
- package/content/extensions/helpers/websocket/api.md +329 -216
- package/content/extensions/helpers/websocket/index.md +65 -503
- package/content/extensions/helpers/worker-thread/index.md +58 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
- package/content/guides/core-concepts/persistent/transactions.md +1 -1
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/migrations/redis-helpers-migration.md +1 -1
- package/content/guides/migrations/unified-connectors-migration.md +2 -2
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/references/base/application.md +1 -1
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/datasources-reference.md +599 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +17 -39
- package/content/references/base/filter-system/application-usage.md +69 -121
- package/content/references/base/filter-system/array-operators.md +12 -0
- package/content/references/base/filter-system/comparison-operators.md +12 -0
- package/content/references/base/filter-system/default-filter.md +136 -348
- package/content/references/base/filter-system/fields-order-pagination.md +38 -16
- package/content/references/base/filter-system/index.md +106 -257
- package/content/references/base/filter-system/json-filtering.md +12 -2
- package/content/references/base/filter-system/list-operators.md +16 -2
- package/content/references/base/filter-system/logical-operators.md +13 -0
- package/content/references/base/filter-system/null-operators.md +13 -0
- package/content/references/base/filter-system/pattern-matching.md +12 -0
- package/content/references/base/filter-system/quick-reference.md +11 -2
- package/content/references/base/filter-system/range-operators.md +12 -0
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +156 -233
- package/content/references/base/middlewares.md +35 -21
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +80 -1452
- package/content/references/base/repositories/advanced.md +156 -192
- package/content/references/base/repositories/index.md +77 -650
- package/content/references/base/repositories/mixins.md +22 -18
- package/content/references/base/repositories/relations.md +123 -171
- package/content/references/base/repositories/soft-deletable.md +58 -56
- package/content/references/base/secrets.md +263 -0
- package/content/references/base/services.md +2 -2
- package/content/references/configuration/environment-variables.md +48 -4
- package/content/references/configuration/index.md +49 -31
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/index.md +1 -1
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +29 -62
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +57 -218
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/package.json +8 -8
|
@@ -1,552 +1,518 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Authentication Reference
|
|
3
|
+
description: Full option tables, binding keys, service class hierarchy, strategy registry, and controller factory for the Authentication component
|
|
4
|
+
difficulty: intermediate
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
# Authentication Reference
|
|
8
|
+
|
|
9
|
+
Every option, binding key, class, and method the Authentication component exposes. See the [Overview](./) for the guided introduction.
|
|
10
|
+
|
|
11
|
+
**Files:**
|
|
12
|
+
|
|
13
|
+
- [`packages/core/src/components/auth/authenticate/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate) - component, services, strategies, controllers
|
|
14
|
+
- [`packages/core/src/components/auth/models/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/models) - entity column helpers + request schemas
|
|
15
|
+
- [`packages/core/src/components/auth/base/abstract-auth-registry.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/base/abstract-auth-registry.ts) - `AbstractAuthRegistry`
|
|
16
|
+
|
|
17
|
+
## Import paths
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import {
|
|
21
|
+
// Component + registry
|
|
22
|
+
AuthenticateComponent,
|
|
23
|
+
AuthenticateBindingKeys,
|
|
24
|
+
Authentication,
|
|
25
|
+
AuthenticationFieldCodecs,
|
|
26
|
+
AuthenticationModes,
|
|
27
|
+
AuthenticationTokenTypes,
|
|
28
|
+
AuthenticationStrategyRegistry,
|
|
29
|
+
|
|
30
|
+
// JOSE standards + constants
|
|
31
|
+
JOSEStandards,
|
|
32
|
+
JWKSModes,
|
|
33
|
+
JWKSKeyDrivers,
|
|
34
|
+
JWKSKeyFormats,
|
|
35
|
+
|
|
36
|
+
// Strategies
|
|
37
|
+
JWSAuthenticationStrategy,
|
|
38
|
+
JWKSIssuerAuthenticationStrategy,
|
|
39
|
+
JWKSVerifierAuthenticationStrategy,
|
|
40
|
+
BasicAuthenticationStrategy,
|
|
41
|
+
|
|
42
|
+
// Services
|
|
43
|
+
AbstractBearerTokenService,
|
|
44
|
+
JWSTokenService,
|
|
45
|
+
JWKSIssuerTokenService,
|
|
46
|
+
JWKSVerifierTokenService,
|
|
47
|
+
BasicTokenService,
|
|
48
|
+
|
|
49
|
+
// Controllers
|
|
50
|
+
defineAuthController,
|
|
51
|
+
JWKSController,
|
|
52
|
+
authenticate,
|
|
53
|
+
|
|
54
|
+
// Entity column helpers
|
|
55
|
+
extraUserColumns,
|
|
56
|
+
extraRoleColumns,
|
|
57
|
+
extraPermissionColumns,
|
|
58
|
+
extraPolicyDefinitionColumns,
|
|
59
|
+
UserStatuses,
|
|
60
|
+
UserTypes,
|
|
61
|
+
RoleStatuses,
|
|
62
|
+
} from '@venizia/ignis';
|
|
63
|
+
|
|
64
|
+
import type {
|
|
65
|
+
TAuthenticationRestOptions,
|
|
66
|
+
TJWTTokenServiceOptions,
|
|
67
|
+
IJWSTokenServiceOptions,
|
|
68
|
+
IJWKSIssuerOptions,
|
|
69
|
+
IJWKSVerifierOptions,
|
|
70
|
+
TJWKSTokenServiceOptions,
|
|
71
|
+
TBasicTokenServiceOptions,
|
|
72
|
+
IAuthenticateOptions,
|
|
73
|
+
IAuthUser,
|
|
74
|
+
IJWTTokenPayload,
|
|
75
|
+
IPayloadFieldCodec,
|
|
76
|
+
IAuthService,
|
|
77
|
+
IAuthenticationStrategy,
|
|
78
|
+
TDefineAuthControllerOpts,
|
|
79
|
+
TAuthStrategy,
|
|
80
|
+
TAuthMode,
|
|
81
|
+
TGetTokenExpiresFn,
|
|
82
|
+
TJWKSAlgorithm,
|
|
83
|
+
TJWKSKeyDriver,
|
|
84
|
+
TJWKSKeyFormat,
|
|
85
|
+
TJOSEStandard,
|
|
86
|
+
TJWKSMode,
|
|
87
|
+
TPermissionOptions,
|
|
88
|
+
TPermissionCommonColumns,
|
|
89
|
+
TPolicyDefinitionOptions,
|
|
90
|
+
TPolicyDefinitionCommonColumns,
|
|
91
|
+
} from '@venizia/ignis';
|
|
92
|
+
```
|
|
4
93
|
|
|
5
94
|
## Architecture
|
|
6
95
|
|
|
7
96
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
│
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
│ 3. defineBasicAuth() → BasicTokenService │
|
|
31
|
-
│ 4. defineControllers() → AuthController (factory-built) │
|
|
32
|
-
│ 5. defineOAuth2() → stub (not yet implemented) │
|
|
33
|
-
└────────────────────────────┬─────────────────────────────────────┘
|
|
34
|
-
│
|
|
35
|
-
┌─────────────────────┼──────────────────────┐
|
|
36
|
-
▼ ▼ ▼
|
|
37
|
-
┌──────────────┐ ┌────────────────┐ ┌──────────────────┐
|
|
38
|
-
│ Bearer Token │ │ BasicToken │ │ AuthController │
|
|
39
|
-
│ Services │ │ Service │ │ (factory-built) │
|
|
40
|
-
│ (see below) │ └───────┬────────┘ └────────┬─────────┘
|
|
41
|
-
└──────┬───────┘ │ │
|
|
42
|
-
│ ▼ ▼
|
|
43
|
-
│ ┌──────────────┐ ┌──────────────────┐
|
|
44
|
-
│ │ Basic │ │ /sign-in │
|
|
45
|
-
│ │ Strategy │ │ /sign-up │
|
|
46
|
-
│ └──────────────┘ │ /change-password │
|
|
47
|
-
│ │ /who-am-i │
|
|
48
|
-
▼ └──────────────────┘
|
|
49
|
-
┌──────────────────────────────────────────────────────────┐
|
|
50
|
-
│ Bearer Token Service Hierarchy │
|
|
51
|
-
│ │
|
|
52
|
-
│ AbstractBearerTokenService (base) │
|
|
53
|
-
│ ├── extractCredentials() │
|
|
54
|
-
│ ├── verify() → doVerify() │
|
|
55
|
-
│ ├── generate() → getSigner() + getSigningKey() │
|
|
56
|
-
│ ├── encryptPayload() / decryptPayload() (optional) │
|
|
57
|
-
│ │ │
|
|
58
|
-
│ ├── JWSTokenService (symmetric HS256) │
|
|
59
|
-
│ │ ├── JWSAuthenticationStrategy │
|
|
60
|
-
│ │ └── sign with shared secret │
|
|
61
|
-
│ │ │
|
|
62
|
-
│ └── AbstractJWKSTokenService (lazy-init + retry) │
|
|
63
|
-
│ ├── ensureInitialized() / initialize() │
|
|
64
|
-
│ │ │
|
|
65
|
-
│ ├── JWKSIssuerTokenService (asymmetric) │
|
|
66
|
-
│ │ ├── JWKSIssuerAuthenticationStrategy │
|
|
67
|
-
│ │ ├── sign with private key │
|
|
68
|
-
│ │ ├── verify with public key │
|
|
69
|
-
│ │ └── getJWKS() / getJWKSAsync() │
|
|
70
|
-
│ │ │
|
|
71
|
-
│ └── JWKSVerifierTokenService (remote verify) │
|
|
72
|
-
│ ├── JWKSVerifierAuthenticationStrategy │
|
|
73
|
-
│ └── verify via createRemoteJWKSet() │
|
|
74
|
-
└──────────────────────────────────────────────────────────┘
|
|
97
|
+
Application.preConfigure()
|
|
98
|
+
├── bind JWT_OPTIONS (TJWTTokenServiceOptions, discriminated on `standard`)
|
|
99
|
+
├── bind BASIC_OPTIONS / REST_OPTIONS
|
|
100
|
+
├── this.component(AuthenticateComponent)
|
|
101
|
+
└── AuthenticationStrategyRegistry.register() -- manual, after the component
|
|
102
|
+
|
|
103
|
+
AuthenticateComponent.binding()
|
|
104
|
+
├── switch on jwtOptions.standard
|
|
105
|
+
│ ├── JWS -> defineJWSAuth() -> registers JWSTokenService
|
|
106
|
+
│ └── JWKS -> defineJWKSAuth() -> switch on mode
|
|
107
|
+
│ ├── issuer -> JWKSIssuerTokenService + JWKSController (/certs)
|
|
108
|
+
│ └── verifier -> JWKSVerifierTokenService
|
|
109
|
+
├── defineBasicAuth() -> registers BasicTokenService (if basicOptions bound)
|
|
110
|
+
├── defineControllers() -> registers AuthController (if useAuthController: true)
|
|
111
|
+
└── defineOAuth2() -> stub, not implemented
|
|
112
|
+
|
|
113
|
+
Bearer token service hierarchy:
|
|
114
|
+
AbstractBearerTokenService (extractCredentials, verify, generate, encryptPayload/decryptPayload)
|
|
115
|
+
├── JWSTokenService (symmetric HS256)
|
|
116
|
+
└── AbstractJWKSTokenService (lazy ensureInitialized() + retry-on-failure)
|
|
117
|
+
├── JWKSIssuerTokenService (sign + verify + getJWKS/getJWKSAsync)
|
|
118
|
+
└── JWKSVerifierTokenService (verify only, via createRemoteJWKSet())
|
|
75
119
|
```
|
|
76
120
|
|
|
77
|
-
|
|
121
|
+
**Tech stack**
|
|
78
122
|
|
|
79
123
|
| Technology | Purpose |
|
|
80
124
|
|------------|---------|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
| **Drizzle ORM** | Database access for user lookup (in your implementation) |
|
|
125
|
+
| `jose` | JWT signing (`SignJWT`), verification (`jwtVerify`), JWKS (`createRemoteJWKSet`, `exportJWK`, `importPKCS8`, `importSPKI`, `importJWK`) |
|
|
126
|
+
| `@venizia/ignis-helpers` | `AES` payload encryption, `BaseHelper`/`BaseService`, `getError`, `HTTP` result codes |
|
|
127
|
+
| Hono middleware | Route-level integration via `createMiddleware` from `hono/factory` |
|
|
128
|
+
| `node:fs/promises` | Async key file reads for JWKS |
|
|
86
129
|
|
|
87
|
-
## Component
|
|
130
|
+
## Component methods
|
|
88
131
|
|
|
89
|
-
|
|
132
|
+
`AuthenticateComponent.binding()` runs four private configuration methods and one public stub:
|
|
90
133
|
|
|
91
134
|
| Method | Purpose |
|
|
92
135
|
|--------|---------|
|
|
93
|
-
| `defineJWSAuth(opts)` | Validates
|
|
94
|
-
| `defineJWKSAuth(opts)` | Switches on `mode
|
|
95
|
-
| `defineBasicAuth(opts)` | Validates `verifyCredentials`
|
|
96
|
-
| `defineControllers(opts)` | Requires `jwtOptions` when `useAuthController: true`. Calls `defineAuthController()`
|
|
97
|
-
| `defineOAuth2()` |
|
|
136
|
+
| `defineJWSAuth(opts)` | Validates `jwtSecret` and `getTokenExpiresFn`, binds `IJWSTokenServiceOptions` to `JWT_OPTIONS`, registers `JWSTokenService` |
|
|
137
|
+
| `defineJWKSAuth(opts)` | Switches on `mode`. Issuer: validates keys/format/kid/getTokenExpiresFn, binds to `JWKS_OPTIONS`, registers `JWKSIssuerTokenService` + `JWKSController`. Verifier: validates `jwksUrl`, binds to `JWKS_OPTIONS`, registers `JWKSVerifierTokenService` |
|
|
138
|
+
| `defineBasicAuth(opts)` | Validates `verifyCredentials` presence, registers `BasicTokenService`. Skips (debug log) if `basicOptions` not bound |
|
|
139
|
+
| `defineControllers(opts)` | Requires `jwtOptions` when `useAuthController: true`. Calls `defineAuthController()` and registers the generated controller |
|
|
140
|
+
| `defineOAuth2()` | Public stub, called during `binding()`, performs no action - not yet implemented |
|
|
98
141
|
|
|
99
142
|
> [!NOTE]
|
|
100
|
-
> The component reads
|
|
143
|
+
> The component reads the discriminated union from `JWT_OPTIONS`, then re-binds just the inner options object (`IJWSTokenServiceOptions` or `IJWKSIssuerOptions`/`IJWKSVerifierOptions`) to `JWKS_OPTIONS` or back to `JWT_OPTIONS`, so the token service can resolve it via a plain `@inject`.
|
|
101
144
|
|
|
102
|
-
##
|
|
145
|
+
## Binding keys
|
|
103
146
|
|
|
104
|
-
|
|
147
|
+
| Constant | Key string | Type | Required | Default |
|
|
148
|
+
|----------|-----------|------|----------|---------|
|
|
149
|
+
| `AuthenticateBindingKeys.REST_OPTIONS` | `@app/authenticate/rest-options` | `TAuthenticationRestOptions` | No | <code v-pre>{ useAuthController: false }</code> |
|
|
150
|
+
| `AuthenticateBindingKeys.JWT_OPTIONS` | `@app/authenticate/jwt-options` | `TJWTTokenServiceOptions` | Conditional | -- |
|
|
151
|
+
| `AuthenticateBindingKeys.JWKS_OPTIONS` | `@app/authenticate/jwks-options` | `IJWKSIssuerOptions \| IJWKSVerifierOptions` | Internal | Bound by the component from `JWT_OPTIONS` |
|
|
152
|
+
| `AuthenticateBindingKeys.BASIC_OPTIONS` | `@app/authenticate/basic-options` | `TBasicTokenServiceOptions` | Conditional | -- |
|
|
105
153
|
|
|
106
|
-
|
|
154
|
+
> [!IMPORTANT]
|
|
155
|
+
> At least one of `JWT_OPTIONS` or `BASIC_OPTIONS` must be bound, or `AuthenticateComponent.binding()` throws.
|
|
107
156
|
|
|
108
|
-
|
|
109
|
-
|--------|-----------|---------|-------------|
|
|
110
|
-
| `getInstance()` | `static` | `AuthenticationStrategyRegistry` | Returns the singleton instance (creates if not exists) |
|
|
111
|
-
| `register` | <code v-pre>(opts: { container: Container; strategies: Array<{ name: string; strategy: TClass<IAuthenticationStrategy> }> }) => this</code> | `this` | Registers strategies as singletons in the container. Returns `this` for chaining. |
|
|
112
|
-
| `resolveStrategy` | `(opts: { name: string }) => IAuthenticationStrategy` | `IAuthenticationStrategy` | Resolves a strategy instance from the container by name |
|
|
157
|
+
## Option interfaces
|
|
113
158
|
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
AuthenticationStrategyRegistry.getInstance().register({
|
|
117
|
-
container: this,
|
|
118
|
-
strategies: [
|
|
119
|
-
{ name: Authentication.STRATEGY_JWT, strategy: JWKSIssuerAuthenticationStrategy },
|
|
120
|
-
{ name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
|
|
121
|
-
],
|
|
122
|
-
});
|
|
123
|
-
```
|
|
159
|
+
### TJWTTokenServiceOptions
|
|
124
160
|
|
|
125
|
-
|
|
126
|
-
> `register()` returns `this`, enabling method chaining if needed.
|
|
127
|
-
|
|
128
|
-
**How it works:**
|
|
129
|
-
- Strategies are stored in an internal map and bound to the DI container as singletons
|
|
130
|
-
- Binding keys follow the pattern `authentication.strategy.{name}` (e.g., `authentication.strategy.jwt`, `authentication.strategy.basic`)
|
|
131
|
-
- The standalone `authenticate()` function creates middleware via `AuthenticationProvider`, which uses the registry to resolve strategies
|
|
132
|
-
|
|
133
|
-
**Middleware creation:**
|
|
134
|
-
|
|
135
|
-
The `authenticate()` function returns a Hono middleware that:
|
|
136
|
-
1. Checks if `Authentication.SKIP_AUTHENTICATION` is set on context - if true, skips entirely (logs debug)
|
|
137
|
-
2. Checks if `Authentication.CURRENT_USER` is already set on context - if true, skips (already authenticated)
|
|
138
|
-
3. Reads `strategies` and `mode` from the provided options
|
|
139
|
-
4. Executes strategies based on mode (`any` or `all`)
|
|
140
|
-
5. On success, sets `Authentication.CURRENT_USER` and `Authentication.AUDIT_USER_ID` on context
|
|
141
|
-
6. On failure, throws 401 with list of tried strategies
|
|
142
|
-
|
|
143
|
-
```mermaid
|
|
144
|
-
sequenceDiagram
|
|
145
|
-
participant C as Client
|
|
146
|
-
participant MW as Auth Middleware
|
|
147
|
-
participant P as AuthenticationProvider
|
|
148
|
-
participant R as StrategyRegistry
|
|
149
|
-
participant S as Strategy
|
|
150
|
-
participant SVC as TokenService
|
|
151
|
-
|
|
152
|
-
C->>MW: Request with Authorization header
|
|
153
|
-
MW->>P: authenticateFn({ strategies, mode })
|
|
154
|
-
P->>P: Check SKIP_AUTHENTICATION
|
|
155
|
-
P->>P: Check CURRENT_USER already set
|
|
156
|
-
P->>R: resolveStrategy({ name })
|
|
157
|
-
R-->>P: strategy instance
|
|
158
|
-
P->>S: authenticate(context)
|
|
159
|
-
S->>SVC: extractCredentials(context)
|
|
160
|
-
SVC-->>S: { type, token }
|
|
161
|
-
S->>SVC: verify({ type, token })
|
|
162
|
-
SVC-->>S: IJWTTokenPayload
|
|
163
|
-
S-->>P: IAuthUser
|
|
164
|
-
P->>MW: Set CURRENT_USER + AUDIT_USER_ID
|
|
165
|
-
MW->>C: Continue to handler
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Standalone `authenticate()` Function
|
|
161
|
+
Discriminated union on `standard`:
|
|
169
162
|
|
|
170
163
|
```typescript
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
type TJWTTokenServiceOptions =
|
|
165
|
+
| { standard: typeof JOSEStandards.JWS; options: IJWSTokenServiceOptions }
|
|
166
|
+
| { standard: typeof JOSEStandards.JWKS; options: TJWKSTokenServiceOptions };
|
|
173
167
|
|
|
174
|
-
|
|
175
|
-
return authenticateFn(opts);
|
|
176
|
-
};
|
|
168
|
+
type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // discriminated on `mode`
|
|
177
169
|
```
|
|
178
170
|
|
|
179
|
-
|
|
171
|
+
### IJWSTokenServiceOptions
|
|
172
|
+
|
|
173
|
+
| Option | Type | Default | Required | Description |
|
|
174
|
+
|--------|------|---------|----------|-------------|
|
|
175
|
+
| `jwtSecret` | `string` | -- | Yes | Secret for signing and verifying the JWT signature |
|
|
176
|
+
| `getTokenExpiresFn` | `TGetTokenExpiresFn` | -- | Yes | Returns token expiration in seconds |
|
|
177
|
+
| `applicationSecret` | `string` | -- | No | Enables AES payload field encryption when set |
|
|
178
|
+
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
|
|
179
|
+
| `headerAlgorithm` | `string` | `'HS256'` | No | JWT signing algorithm |
|
|
180
|
+
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom serialize/deserialize per field name |
|
|
181
|
+
|
|
182
|
+
> [!WARNING]
|
|
183
|
+
> `jwtSecret` is mandatory - the component throws if it is missing or equals the placeholder `'unknown_secret'`. `applicationSecret` is optional: when omitted, the JWT payload is standard plaintext; standard fields (`iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat`) are never encrypted either way.
|
|
184
|
+
|
|
185
|
+
### IJWKSIssuerOptions
|
|
186
|
+
|
|
187
|
+
| Option | Type | Default | Required | Description |
|
|
188
|
+
|--------|------|---------|----------|-------------|
|
|
189
|
+
| `mode` | `typeof JWKSModes.ISSUER` | -- | Yes | Must be `'issuer'` |
|
|
190
|
+
| `algorithm` | `TJWKSAlgorithm` | -- | Yes | `'ES256'`, `'RS256'`, or `'EdDSA'` |
|
|
191
|
+
| `keys.driver` | `TJWKSKeyDriver` | -- | Yes | `'text'` (inline) or `'file'` (path) |
|
|
192
|
+
| `keys.format` | `TJWKSKeyFormat` | -- | Yes | `'pem'` or `'jwk'` |
|
|
193
|
+
| `keys.private` | `string` | -- | Yes | Private key content or file path |
|
|
194
|
+
| `keys.public` | `string` | -- | Yes | Public key content or file path |
|
|
195
|
+
| `kid` | `string` | -- | Yes | Key ID exposed in the JWKS endpoint and JWT header |
|
|
196
|
+
| `getTokenExpiresFn` | `TGetTokenExpiresFn` | -- | Yes | Returns token expiration in seconds |
|
|
197
|
+
| `rest.path` | `string` | `'/certs'` | No | Path of the generated `JWKSController` |
|
|
198
|
+
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
|
|
199
|
+
| `applicationSecret` | `string` | -- | No | Enables AES payload field encryption when set |
|
|
200
|
+
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom serialize/deserialize per field name |
|
|
201
|
+
|
|
202
|
+
### IJWKSVerifierOptions
|
|
203
|
+
|
|
204
|
+
| Option | Type | Default | Required | Description |
|
|
205
|
+
|--------|------|---------|----------|-------------|
|
|
206
|
+
| `mode` | `typeof JWKSModes.VERIFIER` | -- | Yes | Must be `'verifier'` |
|
|
207
|
+
| `jwksUrl` | `string` | -- | Yes | URL of the issuer's JWKS endpoint |
|
|
208
|
+
| `cacheTtlMs` | `number` | `43_200_000` (12h) | No | `createRemoteJWKSet` `cacheMaxAge` |
|
|
209
|
+
| `cooldownMs` | `number` | `30_000` (30s) | No | `createRemoteJWKSet` `cooldownDuration` |
|
|
210
|
+
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload decryption |
|
|
211
|
+
| `applicationSecret` | `string` | -- | No | Must match the issuer's secret to decrypt payloads |
|
|
212
|
+
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Must match the issuer's codecs to decrypt custom fields |
|
|
213
|
+
|
|
214
|
+
> [!IMPORTANT]
|
|
215
|
+
> `JWKSVerifierTokenService` cannot sign tokens - `getSigner()`, `getSigningKey()`, and `getDefaultTokenExpiresFn()` all throw. Only `verify()` and `extractCredentials()` are functional.
|
|
216
|
+
|
|
217
|
+
### TBasicTokenServiceOptions
|
|
218
|
+
|
|
219
|
+
| Option | Type | Description |
|
|
220
|
+
|--------|------|-------------|
|
|
221
|
+
| `verifyCredentials` | <code v-pre>(opts: { credentials: { username: string; password: string }; context: TContext }) => Promise<IAuthUser \| null></code> | Callback that validates Basic credentials and returns the authenticated user, or `null` |
|
|
222
|
+
|
|
223
|
+
### TAuthenticationRestOptions
|
|
224
|
+
|
|
225
|
+
Discriminated union on `useAuthController` - when `true`, `controllerOpts` becomes required:
|
|
180
226
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
```mermaid
|
|
187
|
-
classDiagram
|
|
188
|
-
class AbstractBearerTokenService {
|
|
189
|
-
<<abstract>>
|
|
190
|
-
#aes: AES | null
|
|
191
|
-
#applicationSecret: string | null
|
|
192
|
-
+extractCredentials(context) credentials
|
|
193
|
-
+verify(opts) IJWTTokenPayload
|
|
194
|
-
+generate(opts) string
|
|
195
|
-
+encryptPayload(payload) Record
|
|
196
|
-
+decryptPayload(opts) IJWTTokenPayload
|
|
197
|
-
#doVerify(token)* IJWTTokenPayload
|
|
198
|
-
+getSigner(opts)* SignJWT
|
|
199
|
-
#getSigningKey()* Uint8Array | CryptoKey
|
|
200
|
-
#getDefaultTokenExpiresFn()* TGetTokenExpiresFn
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
class JWSTokenService {
|
|
204
|
-
#jwtSecret: Uint8Array
|
|
205
|
-
#options: IJWSTokenServiceOptions
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
class AbstractJWKSTokenService {
|
|
209
|
-
<<abstract>>
|
|
210
|
-
#initialized: boolean
|
|
211
|
-
#initPromise: Promise | null
|
|
212
|
-
+ensureInitialized()
|
|
213
|
-
#initialize()* void
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
class JWKSIssuerTokenService {
|
|
217
|
-
#privateKey: CryptoKey | null
|
|
218
|
-
#publicKey: CryptoKey | null
|
|
219
|
-
#jwks: object | null
|
|
220
|
-
+getJWKS() keys
|
|
221
|
-
+getJWKSAsync() keys
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
class JWKSVerifierTokenService {
|
|
225
|
-
#jwksVerifier: Function | null
|
|
226
|
-
+getSigner() never
|
|
227
|
-
+getSigningKey() never
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
AbstractBearerTokenService <|-- JWSTokenService
|
|
231
|
-
AbstractBearerTokenService <|-- AbstractJWKSTokenService
|
|
232
|
-
AbstractJWKSTokenService <|-- JWKSIssuerTokenService
|
|
233
|
-
AbstractJWKSTokenService <|-- JWKSVerifierTokenService
|
|
227
|
+
```typescript
|
|
228
|
+
type TAuthenticationRestOptions = {} & (
|
|
229
|
+
| { useAuthController?: false | undefined }
|
|
230
|
+
| { useAuthController: true; controllerOpts: TDefineAuthControllerOpts }
|
|
231
|
+
);
|
|
234
232
|
```
|
|
235
233
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
Base class for all Bearer token services. Extends `BaseService`. Generic on <code v-pre><E extends Env = Env></code>.
|
|
239
|
-
|
|
240
|
-
**File:** `packages/core/src/components/auth/authenticate/services/bearer/abstract.service.ts`
|
|
234
|
+
### TDefineAuthControllerOpts
|
|
241
235
|
|
|
242
|
-
|
|
236
|
+
| Option | Type | Default | Description |
|
|
237
|
+
|--------|------|---------|-------------|
|
|
238
|
+
| `restPath` | `string` | `'/auth'` | Base path for the generated controller |
|
|
239
|
+
| `serviceKey` | `string` | -- | DI key for the `IAuthService` implementation (required) |
|
|
240
|
+
| `requireAuthenticatedSignUp` | `boolean` | `false` | Whether `POST /sign-up` requires a valid JWT |
|
|
241
|
+
| `payload.signIn` | `{ request: { schema }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/sign-in` |
|
|
242
|
+
| `payload.signUp` | `{ request: { schema }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/sign-up` |
|
|
243
|
+
| `payload.changePassword` | `{ request: { schema? }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/change-password` |
|
|
244
|
+
| `payload.refreshToken` | `{ response: { schema } }` | `AnyObjectSchema` | Custom response schema for `/token/refresh` |
|
|
245
|
+
| `payload.getUserInformation` | `{ response: { schema } }` | `AnyObjectSchema` | Custom response schema for `/me` and the `who-am-i` `userInformation` field |
|
|
243
246
|
|
|
244
|
-
|
|
245
|
-
|-------|------|-------------|
|
|
246
|
-
| `JWT_COMMON_FIELDS` | <code v-pre>Set<'iss' \| 'sub' \| 'aud' \| 'jti' \| 'nbf' \| 'exp' \| 'iat'></code> | Standard JWT fields that are never encrypted |
|
|
247
|
+
### Route authenticate config
|
|
247
248
|
|
|
248
|
-
|
|
249
|
+
```typescript
|
|
250
|
+
type TRouteAuthenticateConfig =
|
|
251
|
+
| { skip: true }
|
|
252
|
+
| { skip?: false; strategies?: TAuthStrategy[]; mode?: TAuthMode };
|
|
253
|
+
```
|
|
249
254
|
|
|
250
255
|
| Field | Type | Default | Description |
|
|
251
256
|
|-------|------|---------|-------------|
|
|
252
|
-
| `
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
|
|
256
|
-
### Methods
|
|
257
|
+
| `authenticate.strategies` | `TAuthStrategy[]` | -- | Strategy names to try, e.g. `['jwt']`, `['jwt', 'basic']` |
|
|
258
|
+
| `authenticate.mode` | `'any' \| 'all'` | `'any'` | `'any'`: first success wins. `'all'`: every strategy must pass |
|
|
259
|
+
| `authenticate.skip` | `true` | -- | Skips authentication for this route entirely |
|
|
257
260
|
|
|
258
|
-
|
|
259
|
-
|--------|-----------|-------------|
|
|
260
|
-
| `configurePayloadEncryption` | <code v-pre>(opts: { aesAlgorithm?: AESAlgorithmType; applicationSecret?: string; fieldCodecs?: IPayloadFieldCodec[] }) => void</code> | Configures optional AES encryption and field codecs. Codecs are converted to a Map keyed by `codec.key` for O(1) lookup. |
|
|
261
|
-
| `extractCredentials` | <code v-pre>(context: TContext<E, string>) => { type: string; token: string }</code> | Extracts Bearer token from Authorization header |
|
|
262
|
-
| `verify` | <code v-pre>(opts: { type: string; token: string }) => Promise<IJWTTokenPayload></code> | Template method - calls `doVerify()` |
|
|
263
|
-
| `generate` | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn?: TGetTokenExpiresFn }) => Promise<string></code> | Template method - calls `getSigner()` + `getSigningKey()` |
|
|
264
|
-
| `serializeField` | <code v-pre>(opts: { key: string; value: any }) => string</code> | Serializes a single field: codec → `JSON.stringify` fallback |
|
|
265
|
-
| `deserializeField` | <code v-pre>(opts: { key: string; value: string }) => any</code> | Deserializes a single field: codec → `JSON.parse` fallback |
|
|
266
|
-
| `encryptPayload` | <code v-pre>(payload: IJWTTokenPayload) => Record<string, any></code> | AES-encrypts non-standard JWT fields using `serializeField`. Returns payload unchanged if AES not configured. |
|
|
267
|
-
| `decryptPayload` | <code v-pre>(opts: { result: JWTVerifyResult<IJWTTokenPayload> }) => IJWTTokenPayload</code> | Decrypts AES-encrypted fields using `deserializeField`. Returns payload unchanged if AES not configured. |
|
|
261
|
+
### IAuthUser / IJWTTokenPayload
|
|
268
262
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
| `getSigner` | **`public`** | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn: TGetTokenExpiresFn }) => Promise<SignJWT></code> | Create a `jose.SignJWT` instance with the payload |
|
|
275
|
-
| `getSigningKey` | `protected` | `() => ValueOrPromise<Uint8Array \| CryptoKey>` | Return the signing key |
|
|
276
|
-
| `getDefaultTokenExpiresFn` | `protected` | `() => TGetTokenExpiresFn` | Return the default token expiry function |
|
|
277
|
-
|
|
278
|
-
## JWSTokenService
|
|
279
|
-
|
|
280
|
-
Symmetric JWT (HS256) token service with optional AES-encrypted payloads. Extends `AbstractBearerTokenService`.
|
|
263
|
+
```typescript
|
|
264
|
+
interface IAuthUser {
|
|
265
|
+
userId: IdType;
|
|
266
|
+
[extra: string | symbol]: any;
|
|
267
|
+
}
|
|
281
268
|
|
|
282
|
-
|
|
269
|
+
interface IJWTTokenPayload extends JWTPayload, IAuthUser {
|
|
270
|
+
userId: IdType;
|
|
271
|
+
roles: { id: IdType; identifier: string; priority: number }[];
|
|
272
|
+
clientId?: string;
|
|
273
|
+
provider?: string;
|
|
274
|
+
email?: string;
|
|
275
|
+
name?: string;
|
|
276
|
+
[extra: string | symbol]: any;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
283
279
|
|
|
284
|
-
|
|
280
|
+
> [!TIP]
|
|
281
|
+
> `IAuthUser` is intentionally minimal. Your `IAuthService` can return extra fields (roles, email, provider) - they pass through JWT generation and are available on `Authentication.CURRENT_USER` after authentication.
|
|
285
282
|
|
|
286
|
-
|
|
283
|
+
### IAuthService
|
|
287
284
|
|
|
288
285
|
```typescript
|
|
289
|
-
|
|
290
|
-
extends
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
})
|
|
303
|
-
private service: JWSTokenService<E>,
|
|
304
|
-
) { ... }
|
|
305
|
-
|
|
306
|
-
authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
307
|
-
const token = this.service.extractCredentials(context);
|
|
308
|
-
return this.service.verify(token);
|
|
309
|
-
}
|
|
286
|
+
interface IAuthService<
|
|
287
|
+
E extends Env = Env,
|
|
288
|
+
SIRQ extends TSignInRequest = TSignInRequest, SIRS = AnyObject,
|
|
289
|
+
SURQ extends TSignUpRequest = TSignUpRequest, SURS = AnyObject,
|
|
290
|
+
CPRQ extends TChangePasswordRequest = TChangePasswordRequest, CPRS = AnyObject,
|
|
291
|
+
UIRQ = AnyObject, UIRS = AnyObject,
|
|
292
|
+
RTRS = AnyObject,
|
|
293
|
+
> {
|
|
294
|
+
signIn(context: TContext<E>, opts: SIRQ): Promise<SIRS>;
|
|
295
|
+
signUp(context: TContext<E>, opts: SURQ): Promise<SURS>;
|
|
296
|
+
changePassword(context: TContext<E>, opts: CPRQ): Promise<CPRS>;
|
|
297
|
+
getUserInformation?(context: TContext<E>, opts: UIRQ): Promise<UIRS>;
|
|
298
|
+
refreshToken?(context: TContext<E>): Promise<RTRS>;
|
|
310
299
|
}
|
|
311
300
|
```
|
|
312
301
|
|
|
313
|
-
|
|
302
|
+
> [!NOTE]
|
|
303
|
+
> `getUserInformation` and `refreshToken` are optional. The generated auth controller returns `501` for `/me`, `/token/refresh`, or `?withUserInformation=true` on `/who-am-i` if the bound service doesn't implement the corresponding method.
|
|
314
304
|
|
|
315
|
-
|
|
316
|
-
|-------|------|-------------|
|
|
317
|
-
| `jwtSecret` | `Uint8Array` | Encoded JWT secret for `jose` signing/verification |
|
|
318
|
-
| `options` | `IJWSTokenServiceOptions` | Injected options |
|
|
305
|
+
### Field codecs
|
|
319
306
|
|
|
320
|
-
|
|
307
|
+
```typescript
|
|
308
|
+
interface IPayloadFieldCodec<T = unknown> {
|
|
309
|
+
key: string;
|
|
310
|
+
serialize(opts: { value: T }): string;
|
|
311
|
+
deserialize(opts: { raw: string }): T;
|
|
312
|
+
}
|
|
313
|
+
```
|
|
321
314
|
|
|
322
|
-
|
|
315
|
+
`AuthenticationFieldCodecs.ROLES_CODEC` is a ready-made codec for the `roles` field (pipe-separated `id|identifier|priority` strings). It is **not applied automatically** - pass it explicitly via `fieldCodecs: [AuthenticationFieldCodecs.ROLES_CODEC]` in the JWS/JWKS options. Without it, `roles` (and every other non-standard field) is serialized with plain `JSON.stringify` before AES encryption.
|
|
323
316
|
|
|
324
317
|
```typescript
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
) {
|
|
329
|
-
// Throws '[JWSTokenService] Invalid jwtSecret' if !jwtSecret
|
|
330
|
-
// Throws '[JWSTokenService] Invalid getTokenExpiresFn' if !getTokenExpiresFn
|
|
331
|
-
// Calls configurePayloadEncryption({ aesAlgorithm, applicationSecret })
|
|
332
|
-
// Encodes jwtSecret to Uint8Array for jose
|
|
318
|
+
class AuthenticationFieldCodecs {
|
|
319
|
+
static readonly ROLES_CODEC: IPayloadFieldCodec<IJWTTokenPayload['roles']>;
|
|
320
|
+
static build<T>(opts: { key: string; serialize; deserialize }): IPayloadFieldCodec<T>;
|
|
333
321
|
}
|
|
334
322
|
```
|
|
335
323
|
|
|
336
|
-
|
|
337
|
-
> `applicationSecret` is no longer validated in the constructor. If not provided, AES encryption is simply not configured, and payloads pass through in plaintext.
|
|
324
|
+
## Context variables
|
|
338
325
|
|
|
339
|
-
|
|
326
|
+
Set on the Hono `Context` during authentication, readable via `context.get()`:
|
|
340
327
|
|
|
341
|
-
|
|
|
342
|
-
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `getDefaultTokenExpiresFn()` | Returns `this.options.getTokenExpiresFn` |
|
|
328
|
+
| Constant | Key string | Type | Description |
|
|
329
|
+
|----------|-----------|------|-------------|
|
|
330
|
+
| `Authentication.CURRENT_USER` | `auth.current.user` | `IAuthUser` | Authenticated user payload |
|
|
331
|
+
| `Authentication.AUDIT_USER_ID` | `audit.user.id` | `IdType` | Authenticated user's ID |
|
|
332
|
+
| `Authentication.SKIP_AUTHENTICATION` | `authentication.skip` | `boolean` | Set `true` in a preceding middleware to bypass auth |
|
|
347
333
|
|
|
348
|
-
##
|
|
334
|
+
## Constants
|
|
349
335
|
|
|
350
|
-
|
|
336
|
+
**Authentication**
|
|
351
337
|
|
|
352
|
-
|
|
338
|
+
| Constant | Value | Description |
|
|
339
|
+
|----------|-------|-------------|
|
|
340
|
+
| `Authentication.STRATEGY_JWT` | `'jwt'` | JWT strategy name |
|
|
341
|
+
| `Authentication.STRATEGY_BASIC` | `'basic'` | Basic strategy name |
|
|
342
|
+
| `Authentication.TYPE_BEARER` | `'Bearer'` | Bearer token type prefix |
|
|
343
|
+
| `Authentication.TYPE_BASIC` | `'Basic'` | Basic token type prefix |
|
|
344
|
+
| `Authentication.AUTHENTICATION_STRATEGY` | `'authentication.strategy'` | Binding key prefix for registered strategies |
|
|
353
345
|
|
|
354
|
-
|
|
346
|
+
**AuthenticationTokenTypes**
|
|
355
347
|
|
|
356
|
-
|
|
348
|
+
| Constant | Value |
|
|
349
|
+
|----------|-------|
|
|
350
|
+
| `TYPE_AUTHORIZATION_CODE` | `'000_AUTHORIZATION_CODE'` |
|
|
351
|
+
| `TYPE_ACCESS_TOKEN` | `'100_ACCESS_TOKEN'` |
|
|
352
|
+
| `TYPE_REFRESH_TOKEN` | `'200_REFRESH_TOKEN'` |
|
|
357
353
|
|
|
358
|
-
|
|
359
|
-
|-------|------|---------|-------------|
|
|
360
|
-
| `initialized` | `boolean` | `false` | Whether the service has been initialized |
|
|
361
|
-
| `initPromise` | `Promise<void> \| null` | `null` | Pending initialization promise (for concurrent callers) |
|
|
354
|
+
**JOSE / JWKS constants** - each class also exposes `SCHEME_SET: Set<string>` and `isValid(input): boolean`
|
|
362
355
|
|
|
363
|
-
|
|
356
|
+
| Class | Members |
|
|
357
|
+
|-------|---------|
|
|
358
|
+
| `JOSEStandards` | `JWS` (`'JWS'`), `JWKS` (`'JWKS'`) |
|
|
359
|
+
| `JWKSModes` | `ISSUER` (`'issuer'`), `VERIFIER` (`'verifier'`) |
|
|
360
|
+
| `JWKSKeyDrivers` | `TEXT` (`'text'`), `FILE` (`'file'`) |
|
|
361
|
+
| `JWKSKeyFormats` | `PEM` (`'pem'`), `JWK` (`'jwk'`) |
|
|
362
|
+
| `AuthenticateStrategy` | `BASIC` (`'basic'`), `JWT` (`'jwt'`) - same values as `Authentication.STRATEGY_*` |
|
|
363
|
+
| `AuthenticationModes` | `ANY` (`'any'`), `ALL` (`'all'`) |
|
|
364
364
|
|
|
365
|
-
|
|
366
|
-
|--------|-----------|-------------|
|
|
367
|
-
| `ensureInitialized` | `() => Promise<void>` | Lazily initializes the service on first call. Concurrent callers share the same promise. On failure, resets `initPromise` so the next call retries. |
|
|
365
|
+
## Strategy registry
|
|
368
366
|
|
|
369
|
-
|
|
367
|
+
`AuthenticationStrategyRegistry` is a singleton extending `AbstractAuthRegistry<IAuthenticationStrategy>`.
|
|
370
368
|
|
|
371
369
|
| Method | Signature | Description |
|
|
372
|
-
|
|
373
|
-
| `
|
|
370
|
+
|--------|-----------|--------------|
|
|
371
|
+
| `getInstance()` | `static (): AuthenticationStrategyRegistry` | Returns (creating if needed) the singleton |
|
|
372
|
+
| `register` | <code v-pre>(opts: { container: Container; strategies: { name: string; strategy: TClass<IAuthenticationStrategy> }[] }) => this</code> | Binds each strategy into the container as a singleton under `authentication.strategy.<name>`. Returns `this` |
|
|
373
|
+
| `resolveStrategy` | `(opts: { name: string }) => IAuthenticationStrategy` | Resolves a registered strategy instance by name |
|
|
374
374
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
375
|
+
```typescript
|
|
376
|
+
AuthenticationStrategyRegistry.getInstance().register({
|
|
377
|
+
container: this,
|
|
378
|
+
strategies: [
|
|
379
|
+
{ name: Authentication.STRATEGY_JWT, strategy: JWKSIssuerAuthenticationStrategy },
|
|
380
|
+
{ name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
|
|
381
|
+
],
|
|
382
|
+
});
|
|
383
|
+
```
|
|
380
384
|
|
|
381
|
-
|
|
385
|
+
**Standalone `authenticate()` function** - the primary export for creating middleware outside the route-config `authenticate` field:
|
|
382
386
|
|
|
383
387
|
```typescript
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
{
|
|
388
|
-
name = Authentication.STRATEGY_JWT;
|
|
389
|
-
standard = JOSEStandards.JWKS;
|
|
390
|
-
|
|
391
|
-
constructor(
|
|
392
|
-
@inject({
|
|
393
|
-
key: BindingKeys.build({
|
|
394
|
-
namespace: BindingNamespaces.SERVICE,
|
|
395
|
-
key: JWKSIssuerTokenService.name,
|
|
396
|
-
}),
|
|
397
|
-
})
|
|
398
|
-
private service: JWKSIssuerTokenService<E>,
|
|
399
|
-
) { ... }
|
|
400
|
-
|
|
401
|
-
authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
402
|
-
const token = this.service.extractCredentials(context);
|
|
403
|
-
return this.service.verify(token);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
388
|
+
const authenticationProvider = new AuthenticationProvider();
|
|
389
|
+
const authenticateFn = authenticationProvider.value();
|
|
390
|
+
|
|
391
|
+
export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) => authenticateFn(opts);
|
|
406
392
|
```
|
|
407
393
|
|
|
408
|
-
|
|
394
|
+
**`AuthenticationProvider` middleware behavior:**
|
|
409
395
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
396
|
+
1. If `Authentication.SKIP_AUTHENTICATION` is set on context, skips entirely (debug log)
|
|
397
|
+
2. If `Authentication.CURRENT_USER` is already set, skips (already authenticated)
|
|
398
|
+
3. Runs strategies per `mode` (`'any'` or `'all'`)
|
|
399
|
+
4. On success, sets `Authentication.CURRENT_USER` and `Authentication.AUDIT_USER_ID`
|
|
400
|
+
5. On failure, throws `401`
|
|
415
401
|
|
|
416
|
-
|
|
402
|
+
> [!NOTE]
|
|
403
|
+
> `'any'` mode discards each failing strategy's error (logs at debug) and only throws once every strategy is exhausted. `'all'` mode uses the **first** strategy's user payload as the identity source; if that payload has no `userId`, it throws `401` even though every strategy technically passed.
|
|
404
|
+
|
|
405
|
+
## Service class hierarchy
|
|
417
406
|
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
407
|
+
```
|
|
408
|
+
AbstractBearerTokenService<E> (extends BaseService)
|
|
409
|
+
#aes, #applicationSecret, #fieldCodecs
|
|
410
|
+
+extractCredentials(context) Extract Bearer token from Authorization header
|
|
411
|
+
+verify(opts) Template method -> doVerify()
|
|
412
|
+
+generate(opts) Template method -> getSigner() + getSigningKey()
|
|
413
|
+
+encryptPayload(payload) / decryptPayload(opts)
|
|
414
|
+
#doVerify(token)* +getSigner(opts)* #getSigningKey()* #getDefaultTokenExpiresFn()*
|
|
415
|
+
|
|
416
|
+
JWSTokenService Symmetric HS256, shared secret
|
|
417
|
+
used by JWSAuthenticationStrategy
|
|
418
|
+
|
|
419
|
+
AbstractJWKSTokenService Lazy ensureInitialized() + retry-on-failure
|
|
420
|
+
#initialized, #initPromise
|
|
421
|
+
+ensureInitialized() #initialize()*
|
|
422
|
+
|
|
423
|
+
JWKSIssuerTokenService Sign with private key, verify with public key
|
|
424
|
+
+getJWKS() (sync, throws if uninitialized) +getJWKSAsync()
|
|
425
|
+
used by JWKSIssuerAuthenticationStrategy
|
|
426
|
+
|
|
427
|
+
JWKSVerifierTokenService Verify only, via remote JWKS
|
|
428
|
+
used by JWKSVerifierAuthenticationStrategy
|
|
426
429
|
```
|
|
427
430
|
|
|
428
|
-
###
|
|
431
|
+
### AbstractBearerTokenService
|
|
429
432
|
|
|
430
|
-
|
|
431
|
-
1. **Resolves key content** - reads from file (`readFile` from `node:fs/promises`) or uses inline text, based on `keys.driver`
|
|
432
|
-
2. **Parses key material** - imports keys using `importPKCS8`/`importSPKI` (PEM format) or `importJWK` (JWK format), based on `keys.format`
|
|
433
|
-
3. **Exports public JWK** - calls `exportJWK()` and adds `kid`, `alg`, `use: 'sig'` metadata
|
|
434
|
-
4. **Caches JWKS** - stores `{ keys: [publicJWK] }` for the `/certs` endpoint
|
|
435
|
-
5. **Sets `initialized = true`**
|
|
433
|
+
**File:** [`packages/core/src/components/auth/authenticate/services/bearer/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/abstract.service.ts)
|
|
436
434
|
|
|
437
|
-
|
|
435
|
+
| Method | Signature | Description |
|
|
436
|
+
|--------|-----------|-------------|
|
|
437
|
+
| `configurePayloadEncryption` | <code v-pre>(opts: { aesAlgorithm?; applicationSecret?; fieldCodecs?: IPayloadFieldCodec[] }) => void</code> | Sets up AES + field codecs. AES only activates if `applicationSecret` is provided |
|
|
438
|
+
| `extractCredentials` | `(context) => { type: string; token: string }` | Parses `Authorization: Bearer <token>` |
|
|
439
|
+
| `verify` | `(opts: { type, token }) => Promise<IJWTTokenPayload>` | Calls `doVerify()`, wraps errors as sanitized `401` |
|
|
440
|
+
| `generate` | `(opts: { payload, getTokenExpiresFn? }) => Promise<string>` | Calls `getSigner()` then signs with `getSigningKey()` |
|
|
441
|
+
| `serializeField` / `deserializeField` | `(opts) => string` / `any` | Per-field codec lookup, `JSON.stringify`/`JSON.parse` fallback |
|
|
442
|
+
| `encryptPayload` | `(payload) => Record<string, any>` | AES-encrypts non-standard fields (keys and values). No-op if AES not configured |
|
|
443
|
+
| `decryptPayload` | `(opts: { result }) => IJWTTokenPayload` | Reverses `encryptPayload`. No-op if AES not configured |
|
|
438
444
|
|
|
439
|
-
|
|
440
|
-
|--------|----------|
|
|
441
|
-
| `doVerify(token)` | Calls `ensureInitialized()`, then `jwtVerify(token, this.publicKey!)`, then `this.decryptPayload()` |
|
|
442
|
-
| `getSigner(opts)` | Calls `ensureInitialized()`, then `this.encryptPayload()`, then creates `SignJWT` with algorithm + kid header |
|
|
443
|
-
| `getSigningKey()` | Returns `this.privateKey` |
|
|
444
|
-
| `getDefaultTokenExpiresFn()` | Returns `this.options.getTokenExpiresFn` |
|
|
445
|
+
Static: `JWT_COMMON_FIELDS: Set<'iss'|'sub'|'aud'|'jti'|'nbf'|'exp'|'iat'>` - never encrypted or touched by field codecs.
|
|
445
446
|
|
|
446
|
-
###
|
|
447
|
+
### JWSTokenService
|
|
447
448
|
|
|
448
|
-
|
|
449
|
-
|--------|-----------|-------------|
|
|
450
|
-
| `getJWKS` | `() => { keys: JWK[] }` | Synchronous - returns cached JWKS. Throws if not yet initialized. |
|
|
451
|
-
| `getJWKSAsync` | `() => Promise<{ keys: JWK[] }>` | Async - calls `ensureInitialized()` first, then returns JWKS. |
|
|
449
|
+
**File:** [`packages/core/src/components/auth/authenticate/services/bearer/jws.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/jws.service.ts)
|
|
452
450
|
|
|
453
|
-
|
|
451
|
+
Constructor validates `jwtSecret` and `getTokenExpiresFn` (throws `500` if missing), encodes the secret to `Uint8Array`, calls `configurePayloadEncryption()`. `doVerify` calls `jose.jwtVerify()` with the shared secret; `getSigner` signs with header `HS256` (or `headerAlgorithm` override).
|
|
454
452
|
|
|
455
|
-
|
|
456
|
-
|--------|-----------|-------------|
|
|
457
|
-
| `resolveKeyContent` | `(opts: { keys }) => Promise<{ priv: string; pub: string }>` | Reads key content from file or returns inline text |
|
|
458
|
-
| `parseKeyMaterial` | `(opts: { raw, algorithm, keys }) => Promise<{ priv, pub }>` | Imports keys using `jose` based on format (PEM or JWK) |
|
|
453
|
+
### AbstractJWKSTokenService
|
|
459
454
|
|
|
460
|
-
|
|
455
|
+
**File:** [`packages/core/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts)
|
|
461
456
|
|
|
462
|
-
|
|
457
|
+
`ensureInitialized()` lazily runs `initialize()` on first call; concurrent callers share the pending promise. If `initialize()` rejects, the promise is reset so the next call retries instead of caching the failure.
|
|
463
458
|
|
|
464
|
-
|
|
459
|
+
### JWKSIssuerTokenService
|
|
465
460
|
|
|
466
|
-
|
|
461
|
+
**File:** [`packages/core/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts)
|
|
467
462
|
|
|
468
|
-
|
|
469
|
-
class JWKSVerifierAuthenticationStrategy<E extends Env = Env>
|
|
470
|
-
extends BaseHelper
|
|
471
|
-
implements IAuthenticationStrategy<E>
|
|
472
|
-
{
|
|
473
|
-
name = Authentication.STRATEGY_JWT;
|
|
474
|
-
standard = JOSEStandards.JWKS;
|
|
475
|
-
|
|
476
|
-
constructor(
|
|
477
|
-
@inject({
|
|
478
|
-
key: BindingKeys.build({
|
|
479
|
-
namespace: BindingNamespaces.SERVICE,
|
|
480
|
-
key: JWKSVerifierTokenService.name,
|
|
481
|
-
}),
|
|
482
|
-
})
|
|
483
|
-
private service: JWKSVerifierTokenService<E>,
|
|
484
|
-
) { ... }
|
|
485
|
-
|
|
486
|
-
authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
487
|
-
const token = this.service.extractCredentials(context);
|
|
488
|
-
return this.service.verify(token);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
```
|
|
463
|
+
`initialize()`: reads key content (file via `readFile` or inline text, per `keys.driver`) → imports it (`importPKCS8`/`importSPKI` for PEM, `importJWK` for JWK) → exports the public JWK with `kid`/`alg`/`use: 'sig'` → caches `{ keys: [publicJWK] }`.
|
|
492
464
|
|
|
493
|
-
|
|
465
|
+
| Method | Signature | Description |
|
|
466
|
+
|--------|-----------|-------------|
|
|
467
|
+
| `getJWKS` | `() => { keys: JWK[] }` | Synchronous, returns the cached JWKS. Throws if called before `initialize()` completes |
|
|
468
|
+
| `getJWKSAsync` | `() => Promise<{ keys: JWK[] }>` | Calls `ensureInitialized()` first |
|
|
494
469
|
|
|
495
|
-
|
|
496
|
-
|-------|------|---------|-------------|
|
|
497
|
-
| `jwksVerifier` | `ReturnType<typeof createRemoteJWKSet> \| null` | `null` | Remote JWKS verifier function |
|
|
470
|
+
### JWKSVerifierTokenService
|
|
498
471
|
|
|
499
|
-
|
|
472
|
+
**File:** [`packages/core/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts)
|
|
500
473
|
|
|
501
|
-
|
|
502
|
-
constructor(
|
|
503
|
-
@inject({ key: AuthenticateBindingKeys.JWKS_OPTIONS })
|
|
504
|
-
protected options: IJWKSVerifierOptions,
|
|
505
|
-
) {
|
|
506
|
-
// Calls configurePayloadEncryption({ aesAlgorithm, applicationSecret })
|
|
507
|
-
// Remote JWKS is NOT fetched here - fetched lazily via ensureInitialized()
|
|
508
|
-
}
|
|
509
|
-
```
|
|
474
|
+
`initialize()` calls `createRemoteJWKSet(jwksUrl, { cacheMaxAge: cacheTtlMs ?? 43_200_000, cooldownDuration: cooldownMs ?? 30_000 })`. `getSigner`/`getSigningKey`/`getDefaultTokenExpiresFn` all throw - this service is verify-only.
|
|
510
475
|
|
|
511
|
-
###
|
|
476
|
+
### BasicTokenService
|
|
512
477
|
|
|
513
|
-
|
|
514
|
-
1. Creates a `createRemoteJWKSet()` from the configured `jwksUrl`
|
|
515
|
-
2. Configures `cacheMaxAge` (default 12h) and `cooldownDuration` (default 30s)
|
|
516
|
-
3. Sets `initialized = true`
|
|
478
|
+
**File:** [`packages/core/src/components/auth/authenticate/services/basic/service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/services/basic/service.ts)
|
|
517
479
|
|
|
518
|
-
|
|
480
|
+
| Method | Signature | Description |
|
|
481
|
+
|--------|-----------|-------------|
|
|
482
|
+
| `extractCredentials` | `(context) => { username: string; password: string }` | Decodes `Authorization: Basic <base64>` |
|
|
483
|
+
| `verify` | `(opts: { credentials, context }) => Promise<IAuthUser>` | Calls the user-provided `verifyCredentials` |
|
|
484
|
+
|
|
485
|
+
Constructor throws `500` if `verifyCredentials` is missing from the injected options.
|
|
486
|
+
|
|
487
|
+
## Strategy classes
|
|
488
|
+
|
|
489
|
+
All four strategies extend `BaseHelper`, implement `IAuthenticationStrategy<E>`, and follow the same shape: a `name` field, a `standard` field (Bearer strategies only), one injected token service, and an `authenticate(context)` method that calls `extractCredentials()` then `verify()`.
|
|
519
490
|
|
|
520
|
-
|
|
|
521
|
-
|
|
522
|
-
| `
|
|
523
|
-
| `
|
|
524
|
-
| `
|
|
525
|
-
| `
|
|
491
|
+
| Strategy | `name` | Injects | File |
|
|
492
|
+
|----------|--------|---------|------|
|
|
493
|
+
| `JWSAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWSTokenService` | [`strategies/jws.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/strategies/jws.strategy.ts) |
|
|
494
|
+
| `JWKSIssuerAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSIssuerTokenService` | [`strategies/jwks.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/strategies/jwks.strategy.ts) |
|
|
495
|
+
| `JWKSVerifierAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSVerifierTokenService` | same file |
|
|
496
|
+
| `BasicAuthenticationStrategy` | `Authentication.STRATEGY_BASIC` | `BasicTokenService` | [`strategies/basic.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/strategies/basic.strategy.ts) |
|
|
497
|
+
|
|
498
|
+
> [!NOTE]
|
|
499
|
+
> Choose the strategy class matching your JOSE standard - `JWKSIssuerAuthenticationStrategy` and `JWKSVerifierAuthenticationStrategy` both register under the same `'jwt'` name, so a JWKS issuer service uses only one of the two.
|
|
526
500
|
|
|
527
501
|
## JWKSController
|
|
528
502
|
|
|
529
|
-
Serves the JWKS endpoint (default path `/certs`).
|
|
503
|
+
Serves the JWKS endpoint (default path `/certs`, configurable via `rest.path`). Intentionally unauthenticated - it serves the public keys external verifiers need.
|
|
530
504
|
|
|
531
|
-
**File:** `packages/core/src/components/auth/authenticate/controllers/jwks/controller.ts`
|
|
505
|
+
**File:** [`packages/core/src/components/auth/authenticate/controllers/jwks/controller.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/controllers/jwks/controller.ts)
|
|
532
506
|
|
|
533
507
|
```typescript
|
|
534
508
|
class JWKSController extends BaseRestController {
|
|
535
|
-
constructor(
|
|
536
|
-
@inject({
|
|
537
|
-
key: BindingKeys.build({
|
|
538
|
-
namespace: BindingNamespaces.SERVICE,
|
|
539
|
-
key: JWKSIssuerTokenService.name,
|
|
540
|
-
}),
|
|
541
|
-
})
|
|
542
|
-
private jwksService: JWKSIssuerTokenService,
|
|
543
|
-
) {
|
|
509
|
+
constructor(@inject(...) private jwksService: JWKSIssuerTokenService) {
|
|
544
510
|
super({ scope: JWKSController.name, path: '/certs', isStrict: true });
|
|
545
511
|
}
|
|
546
512
|
|
|
547
|
-
override binding()
|
|
513
|
+
override binding() {
|
|
548
514
|
this.defineRoute({
|
|
549
|
-
configs: RouteConfigs.GET_JWKS_CERTS,
|
|
515
|
+
configs: RouteConfigs.GET_JWKS_CERTS, // GET '/'
|
|
550
516
|
handler: async context => {
|
|
551
517
|
const jwks = await this.jwksService.getJWKSAsync();
|
|
552
518
|
context.header('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
|
|
@@ -557,107 +523,40 @@ class JWKSController extends BaseRestController {
|
|
|
557
523
|
}
|
|
558
524
|
```
|
|
559
525
|
|
|
560
|
-
|
|
561
|
-
```json
|
|
562
|
-
{
|
|
563
|
-
"keys": [
|
|
564
|
-
{
|
|
565
|
-
"kty": "EC",
|
|
566
|
-
"kid": "my-key-id-1",
|
|
567
|
-
"use": "sig",
|
|
568
|
-
"alg": "ES256",
|
|
569
|
-
"crv": "P-256",
|
|
570
|
-
"x": "...",
|
|
571
|
-
"y": "..."
|
|
572
|
-
}
|
|
573
|
-
]
|
|
574
|
-
}
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
**Cache headers:** `Cache-Control: public, max-age=3600, stale-while-revalidate=86400`
|
|
578
|
-
|
|
579
|
-
> [!NOTE]
|
|
580
|
-
> The `/certs` path is configurable via `rest.path` in `IJWKSIssuerOptions`. The component applies the `@controller` decorator dynamically with the configured path.
|
|
581
|
-
|
|
582
|
-
## BasicTokenService
|
|
583
|
-
|
|
584
|
-
All methods are instance methods on <code v-pre>BasicTokenService<E extends Env = Env></code>, which extends `BaseService`.
|
|
526
|
+
The component applies `@controller({ path })` to `JWKSController` dynamically at binding time (via `Reflect.decorate`), since the path depends on a runtime option.
|
|
585
527
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
### BasicAuthenticationStrategy
|
|
589
|
-
|
|
590
|
-
Extends `BaseHelper` and implements <code v-pre>IAuthenticationStrategy<E></code>. Generic on <code v-pre><E extends Env = Env></code>.
|
|
591
|
-
|
|
592
|
-
```typescript
|
|
593
|
-
class BasicAuthenticationStrategy<E extends Env = Env>
|
|
594
|
-
extends BaseHelper
|
|
595
|
-
implements IAuthenticationStrategy<E>
|
|
596
|
-
{
|
|
597
|
-
name = Authentication.STRATEGY_BASIC;
|
|
598
|
-
|
|
599
|
-
constructor(
|
|
600
|
-
@inject({
|
|
601
|
-
key: BindingKeys.build({
|
|
602
|
-
namespace: BindingNamespaces.SERVICE,
|
|
603
|
-
key: BasicTokenService.name,
|
|
604
|
-
}),
|
|
605
|
-
})
|
|
606
|
-
private service: BasicTokenService<E>,
|
|
607
|
-
) { ... }
|
|
608
|
-
|
|
609
|
-
async authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
610
|
-
const credentials = this.service.extractCredentials(context);
|
|
611
|
-
return this.service.verify({ credentials, context });
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
```
|
|
528
|
+
## Controller factory
|
|
615
529
|
|
|
616
|
-
|
|
530
|
+
`defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController` builds a `BaseRestController` subclass at runtime.
|
|
617
531
|
|
|
618
|
-
|
|
619
|
-
|--------|-----------|-------------|
|
|
620
|
-
| `extractCredentials` | <code v-pre>(context: TContext<E, string>) => { username: string; password: string }</code> | Decodes Base64 <code v-pre>Authorization: Basic <base64></code> header |
|
|
621
|
-
| `verify` | <code v-pre>(opts: { credentials: { username: string; password: string }; context: TContext<E, string> }) => Promise<IAuthUser></code> | Calls user-provided `verifyCredentials` callback |
|
|
532
|
+
**File:** [`packages/core/src/components/auth/authenticate/controllers/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/controllers/factory.ts)
|
|
622
533
|
|
|
623
|
-
|
|
534
|
+
**How it works:**
|
|
624
535
|
|
|
625
|
-
|
|
626
|
-
constructor
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
) {
|
|
630
|
-
// Throws '[BasicTokenService] Invalid verifyCredentials function' if !options?.verifyCredentials
|
|
631
|
-
}
|
|
632
|
-
```
|
|
536
|
+
1. **Class creation.** `class AuthController extends BaseRestController {}` inside the factory closure, decorated with `@controller({ path: restPath })`, `isStrict: true`.
|
|
537
|
+
2. **Service injection.** `inject({ key: serviceKey })(AuthController, undefined, 0)` is applied *after* class definition - the programmatic equivalent of decorating constructor parameter 0. The constructor throws `400` if the resolved service is falsy.
|
|
538
|
+
3. **Routes.** Defined in `binding()` via `this.defineRoute()` - see the endpoint table in [Usage & Examples](./usage#api-endpoints).
|
|
539
|
+
4. **Schemas.** Each endpoint uses `payload.<name>.{request,response}.schema` if provided, else a built-in default (or `AnyObjectSchema` for responses with no built-in schema).
|
|
633
540
|
|
|
634
|
-
|
|
541
|
+
Also exports `JWTTokenPayloadSchema`, the Zod schema backing `/who-am-i`'s response - extended at runtime with an optional `userInformation` field typed from `payload.getUserInformation.response.schema` (or `AnyObjectSchema`).
|
|
635
542
|
|
|
636
|
-
|
|
543
|
+
## Entity column helper types
|
|
637
544
|
|
|
638
|
-
|
|
545
|
+
Exported for extending the auth entity column helpers - see [Usage & Examples](./usage#entity-column-helpers) for the columns themselves.
|
|
639
546
|
|
|
640
547
|
```typescript
|
|
641
|
-
type TPermissionOptions = {
|
|
642
|
-
idType?: 'string' | 'number';
|
|
643
|
-
};
|
|
644
|
-
|
|
548
|
+
type TPermissionOptions = { idType?: 'string' | 'number' };
|
|
645
549
|
type TPermissionCommonColumns = {
|
|
646
550
|
code: NotNull<PgTextBuilderInitial<...>>;
|
|
647
551
|
name: NotNull<PgTextBuilderInitial<...>>;
|
|
648
552
|
subject: NotNull<PgTextBuilderInitial<...>>;
|
|
553
|
+
method: NotNull<PgTextBuilderInitial<...>>;
|
|
649
554
|
action: NotNull<PgTextBuilderInitial<...>>;
|
|
650
555
|
scope: NotNull<PgTextBuilderInitial<...>>;
|
|
651
|
-
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### Policy Definition Types
|
|
655
|
-
|
|
656
|
-
```typescript
|
|
657
|
-
type TPolicyDefinitionOptions = {
|
|
658
|
-
idType?: 'string' | 'number';
|
|
556
|
+
description: PgTextBuilderInitial<...>;
|
|
659
557
|
};
|
|
660
558
|
|
|
559
|
+
type TPolicyDefinitionOptions = { idType?: 'string' | 'number' };
|
|
661
560
|
type TPolicyDefinitionCommonColumns = {
|
|
662
561
|
variant: ReturnType<typeof text>;
|
|
663
562
|
subjectType: ReturnType<typeof text>;
|
|
@@ -668,89 +567,41 @@ type TPolicyDefinitionCommonColumns = {
|
|
|
668
567
|
};
|
|
669
568
|
```
|
|
670
569
|
|
|
671
|
-
##
|
|
672
|
-
|
|
673
|
-
The `defineAuthController()` function dynamically creates a controller class at runtime using decorator composition:
|
|
674
|
-
|
|
675
|
-
**How it works:**
|
|
676
|
-
|
|
677
|
-
1. **Class creation:** A new class is created dynamically with `class AuthController extends BaseRestController {}` inside the factory closure
|
|
678
|
-
2. **Decorator application:** The `@controller({ path: restPath })` decorator is applied to set the base path. The controller is created with `isStrict: true`
|
|
679
|
-
3. **Service injection:** The auth service is injected via `inject({ key: serviceKey })(AuthController, undefined, 0)` after class definition -- this programmatically applies `@inject` to constructor parameter 0
|
|
680
|
-
- Service key is provided via `controllerOpts.serviceKey` (required)
|
|
681
|
-
- Service must implement `IAuthService` interface
|
|
682
|
-
4. **Route definition:** Routes are defined in the controller's `binding()` method using `this.defineRoute()`
|
|
683
|
-
5. **Schema customization:** Custom Zod schemas can be provided per endpoint via the `payload` option. Defaults to built-in schemas when not provided, with `AnyObjectSchema` as the response fallback.
|
|
684
|
-
|
|
685
|
-
**Factory signature:**
|
|
686
|
-
|
|
687
|
-
```typescript
|
|
688
|
-
function defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController;
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
> [!NOTE]
|
|
692
|
-
> The factory also exports `JWTTokenPayloadSchema`, a Zod schema used for the `/who-am-i` response validation. For `/who-am-i`, it is extended with an optional `userInformation` field that is populated when the request sets `?withUserInformation=true`. The same `getUserInformation` service method backs the dedicated `GET /me` route.
|
|
693
|
-
|
|
694
|
-
**Service resolution:**
|
|
695
|
-
|
|
696
|
-
The factory applies `@inject` programmatically to constructor parameter 0:
|
|
697
|
-
|
|
698
|
-
```typescript
|
|
699
|
-
// Inside defineAuthController, after class definition:
|
|
700
|
-
inject({ key: serviceKey })(AuthController, undefined, 0);
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
This is equivalent to:
|
|
704
|
-
```typescript
|
|
705
|
-
constructor(
|
|
706
|
-
@inject({ key: serviceKey })
|
|
707
|
-
authService: IAuthService,
|
|
708
|
-
) { ... }
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
If the service is not bound, the component will throw: `"[AuthController] Failed to init auth controller | Invalid injectable authentication service!"`
|
|
712
|
-
|
|
713
|
-
## File Structure
|
|
570
|
+
## File structure
|
|
714
571
|
|
|
715
572
|
```
|
|
716
|
-
packages/core/src/components/auth/
|
|
717
|
-
├──
|
|
718
|
-
│ ├──
|
|
719
|
-
│ ├──
|
|
720
|
-
│ ├──
|
|
721
|
-
│ ├──
|
|
722
|
-
│
|
|
723
|
-
|
|
724
|
-
│ ├──
|
|
725
|
-
│
|
|
726
|
-
│
|
|
727
|
-
│
|
|
728
|
-
|
|
729
|
-
│
|
|
730
|
-
|
|
731
|
-
│
|
|
732
|
-
├──
|
|
733
|
-
│
|
|
734
|
-
│ │
|
|
735
|
-
│ ├──
|
|
736
|
-
│ │
|
|
737
|
-
│
|
|
738
|
-
│
|
|
739
|
-
|
|
740
|
-
│
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
│ ├── basic.strategy.ts # BasicAuthenticationStrategy
|
|
745
|
-
│ ├── jws.strategy.ts # JWSAuthenticationStrategy
|
|
746
|
-
│ ├── jwks.strategy.ts # JWKSIssuerAuthenticationStrategy + JWKSVerifierAuthenticationStrategy
|
|
747
|
-
│ ├── strategy-registry.ts # AuthenticationStrategyRegistry singleton
|
|
748
|
-
│ └── index.ts # Barrel export
|
|
749
|
-
└── component.ts # AuthenticateComponent
|
|
573
|
+
packages/core/src/components/auth/
|
|
574
|
+
├── authenticate/
|
|
575
|
+
│ ├── common/
|
|
576
|
+
│ │ ├── codecs.ts # AuthenticationFieldCodecs (ROLES_CODEC, build() factory)
|
|
577
|
+
│ │ ├── constants.ts # AuthenticateStrategy, JOSEStandards, JWKSModes, JWKSKeyDrivers, JWKSKeyFormats, Authentication, AuthenticationTokenTypes, AuthenticationModes
|
|
578
|
+
│ │ ├── keys.ts # AuthenticateBindingKeys
|
|
579
|
+
│ │ ├── types.ts # Option interfaces, discriminated unions, IAuthUser, IJWTTokenPayload, IAuthService
|
|
580
|
+
│ │ └── index.ts
|
|
581
|
+
│ ├── controllers/
|
|
582
|
+
│ │ ├── factory.ts # defineAuthController() + JWTTokenPayloadSchema
|
|
583
|
+
│ │ └── jwks/ # JWKSController + route config
|
|
584
|
+
│ ├── middlewares/
|
|
585
|
+
│ │ └── authenticate.middleware.ts # Standalone authenticate() function
|
|
586
|
+
│ ├── providers/
|
|
587
|
+
│ │ └── authentication.provider.ts # AuthenticationProvider
|
|
588
|
+
│ ├── services/
|
|
589
|
+
│ │ ├── basic/service.ts # BasicTokenService
|
|
590
|
+
│ │ └── bearer/
|
|
591
|
+
│ │ ├── abstract.service.ts # AbstractBearerTokenService
|
|
592
|
+
│ │ ├── jws.service.ts # JWSTokenService
|
|
593
|
+
│ │ └── jwks/ # AbstractJWKSTokenService, JWKSIssuerTokenService, JWKSVerifierTokenService
|
|
594
|
+
│ ├── strategies/ # JWSAuthenticationStrategy, JWKS*, BasicAuthenticationStrategy, AuthenticationStrategyRegistry
|
|
595
|
+
│ └── component.ts # AuthenticateComponent
|
|
596
|
+
├── base/
|
|
597
|
+
│ └── abstract-auth-registry.ts # AbstractAuthRegistry (shared by authenticate + authorize)
|
|
598
|
+
└── models/
|
|
599
|
+
├── entities/ # extraUserColumns, extraRoleColumns, extraPermissionColumns, extraPolicyDefinitionColumns
|
|
600
|
+
└── requests/ # SignInRequestSchema, SignUpRequestSchema, ChangePasswordRequestSchema
|
|
750
601
|
```
|
|
751
602
|
|
|
752
|
-
## See
|
|
603
|
+
## See also
|
|
753
604
|
|
|
754
|
-
- [
|
|
755
|
-
- [Usage & Examples](./usage)
|
|
756
|
-
- [Error Reference](./errors)
|
|
605
|
+
- [Overview](./) - guided introduction and common tasks
|
|
606
|
+
- [Usage & Examples](./usage) - securing routes, auth flows, JWKS microservice patterns, API endpoints
|
|
607
|
+
- [Error Reference](./errors) - every error message and how to fix it
|