@venizia/ignis-docs 0.2.1-0 → 0.2.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/best-practices/architectural-patterns.md +3 -3
- package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/content/best-practices/contribution-workflow.md +2 -2
- package/content/best-practices/error-handling.md +94 -89
- package/content/best-practices/security-guidelines.md +5 -5
- package/content/extensions/components/api-reference.md +22 -21
- package/content/extensions/components/authentication/api.md +64 -28
- package/content/extensions/components/authentication/errors.md +19 -5
- package/content/extensions/components/authentication/index.md +19 -18
- package/content/extensions/components/authentication/usage.md +55 -30
- package/content/extensions/components/authorization/api.md +351 -84
- package/content/extensions/components/authorization/errors.md +51 -17
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +24 -16
- package/content/extensions/components/authorization/usage.md +45 -21
- package/content/extensions/components/health-check.md +15 -9
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +105 -54
- package/content/extensions/components/mail/errors.md +12 -10
- package/content/extensions/components/mail/index.md +32 -13
- package/content/extensions/components/mail/usage.md +26 -18
- package/content/extensions/components/request-tracker.md +18 -14
- package/content/extensions/components/socket-io/api.md +377 -882
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +72 -88
- package/content/extensions/components/socket-io/usage.md +107 -117
- package/content/extensions/components/static-asset/api.md +83 -31
- package/content/extensions/components/static-asset/errors.md +18 -7
- package/content/extensions/components/static-asset/index.md +18 -11
- package/content/extensions/components/static-asset/usage.md +11 -6
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +58 -27
- package/content/extensions/components/websocket/errors.md +3 -3
- package/content/extensions/components/websocket/index.md +8 -7
- package/content/extensions/components/websocket/usage.md +21 -8
- package/content/extensions/helpers/cron/index.md +8 -7
- package/content/extensions/helpers/crypto/index.md +16 -8
- package/content/extensions/helpers/crypto/reference.md +96 -24
- package/content/extensions/helpers/env/index.md +14 -10
- package/content/extensions/helpers/error/index.md +99 -23
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +23 -6
- package/content/extensions/helpers/inversion/reference.md +30 -22
- package/content/extensions/helpers/kafka/admin.md +3 -2
- package/content/extensions/helpers/kafka/compile-binary.md +69 -44
- package/content/extensions/helpers/kafka/consumer.md +24 -21
- package/content/extensions/helpers/kafka/examples.md +22 -234
- package/content/extensions/helpers/kafka/index.md +30 -62
- package/content/extensions/helpers/kafka/producer.md +31 -26
- package/content/extensions/helpers/kafka/schema-registry.md +19 -14
- package/content/extensions/helpers/logger/hf-logger.md +49 -22
- package/content/extensions/helpers/logger/index.md +37 -12
- package/content/extensions/helpers/logger/pino.md +31 -11
- package/content/extensions/helpers/logger/reference.md +243 -52
- package/content/extensions/helpers/network/api.md +65 -30
- package/content/extensions/helpers/network/index.md +32 -11
- package/content/extensions/helpers/queue/index.md +17 -6
- package/content/extensions/helpers/queue/reference.md +52 -25
- package/content/extensions/helpers/redis/index.md +29 -11
- package/content/extensions/helpers/redis/reference.md +85 -28
- package/content/extensions/helpers/secrets/index.md +82 -12
- package/content/extensions/helpers/socket-io/api.md +40 -21
- package/content/extensions/helpers/socket-io/index.md +26 -10
- package/content/extensions/helpers/storage/api.md +42 -24
- package/content/extensions/helpers/storage/index.md +10 -7
- package/content/extensions/helpers/types/index.md +20 -7
- package/content/extensions/helpers/types/reference.md +53 -14
- package/content/extensions/helpers/uid/index.md +166 -10
- package/content/extensions/helpers/websocket/api.md +52 -13
- package/content/extensions/helpers/websocket/index.md +7 -4
- package/content/extensions/helpers/worker-thread/index.md +9 -5
- package/content/extensions/helpers/worker-thread/reference.md +20 -20
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +4 -3
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +7 -6
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +63 -21
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +14 -14
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +32 -31
- package/content/references/base/datasources.md +6 -6
- package/content/references/base/dependency-injection.md +12 -11
- package/content/references/base/filter-system/application-usage.md +54 -30
- package/content/references/base/filter-system/array-operators.md +24 -46
- package/content/references/base/filter-system/comparison-operators.md +47 -67
- package/content/references/base/filter-system/default-filter.md +59 -53
- package/content/references/base/filter-system/fields-order-pagination.md +92 -146
- package/content/references/base/filter-system/index.md +25 -13
- package/content/references/base/filter-system/json-filtering.md +45 -184
- package/content/references/base/filter-system/list-operators.md +23 -53
- package/content/references/base/filter-system/logical-operators.md +63 -121
- package/content/references/base/filter-system/null-operators.md +34 -104
- package/content/references/base/filter-system/pattern-matching.md +40 -55
- package/content/references/base/filter-system/quick-reference.md +86 -198
- package/content/references/base/filter-system/range-operators.md +18 -46
- package/content/references/base/filter-system/tips.md +6 -6
- package/content/references/base/filter-system/use-cases.md +33 -15
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +11 -10
- package/content/references/base/models-reference.md +17 -17
- package/content/references/base/models.md +4 -3
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +224 -326
- package/content/references/base/repositories/index.md +19 -6
- package/content/references/base/repositories/mixins.md +5 -5
- package/content/references/base/repositories/relations.md +160 -293
- package/content/references/base/repositories/soft-deletable.md +16 -6
- package/content/references/base/secrets.md +17 -13
- package/content/references/base/services.md +6 -4
- package/content/references/configuration/environment-variables.md +31 -23
- package/content/references/configuration/index.md +6 -4
- package/content/references/index.md +1 -1
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +5 -1
- package/content/references/utilities/jsx-reference.md +11 -11
- package/content/references/utilities/jsx.md +2 -2
- package/content/references/utilities/module.md +78 -25
- package/content/references/utilities/request.md +2 -1
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +2 -2
- package/content/references/utilities/statuses-reference.md +4 -4
- package/content/references/utilities/statuses.md +5 -5
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +17 -16
|
@@ -10,9 +10,21 @@ Every option, binding key, class, and method the Authentication component expose
|
|
|
10
10
|
|
|
11
11
|
**Files:**
|
|
12
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`
|
|
13
|
+
- [`packages/core-server/src/components/auth/authenticate/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate) - component, services, strategies, controllers
|
|
14
|
+
- [`packages/core-server/src/components/auth/models/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/models) - entity column helpers + request schemas
|
|
15
|
+
- [`packages/core-server/src/components/auth/base/abstract-auth-registry.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/base/abstract-auth-registry.ts) - `AbstractAuthRegistry`
|
|
16
|
+
|
|
17
|
+
## Find what you need
|
|
18
|
+
|
|
19
|
+
| You want to | Go to |
|
|
20
|
+
|---|---|
|
|
21
|
+
| See how the pieces fit together | [Architecture](#architecture) |
|
|
22
|
+
| Look up a binding key or option field | [Binding keys](#binding-keys), [Option interfaces](#option-interfaces) |
|
|
23
|
+
| Check what `IAuthUser` / `IAuthService` require | [IAuthUser / IJWTTokenPayload](#iauthuser-ijwttokenpayload), [IAuthService](#iauthservice) |
|
|
24
|
+
| Understand the JWS/JWKS/Basic class hierarchy | [Service class hierarchy](#service-class-hierarchy) |
|
|
25
|
+
| Register or resolve a strategy programmatically | [Strategy registry](#strategy-registry) |
|
|
26
|
+
| See exactly what the built-in `/auth` controller builds | [Controller factory](#controller-factory) |
|
|
27
|
+
| Find a file on disk | [File structure](#file-structure) |
|
|
16
28
|
|
|
17
29
|
## Import paths
|
|
18
30
|
|
|
@@ -26,6 +38,7 @@ import {
|
|
|
26
38
|
AuthenticationModes,
|
|
27
39
|
AuthenticationTokenTypes,
|
|
28
40
|
AuthenticationStrategyRegistry,
|
|
41
|
+
ServiceAssertion,
|
|
29
42
|
|
|
30
43
|
// JOSE standards + constants
|
|
31
44
|
JOSEStandards,
|
|
@@ -38,6 +51,7 @@ import {
|
|
|
38
51
|
JWKSIssuerAuthenticationStrategy,
|
|
39
52
|
JWKSVerifierAuthenticationStrategy,
|
|
40
53
|
BasicAuthenticationStrategy,
|
|
54
|
+
ServiceAuthenticationStrategy,
|
|
41
55
|
|
|
42
56
|
// Services
|
|
43
57
|
AbstractBearerTokenService,
|
|
@@ -69,6 +83,9 @@ import type {
|
|
|
69
83
|
IJWKSVerifierOptions,
|
|
70
84
|
TJWKSTokenServiceOptions,
|
|
71
85
|
TBasicTokenServiceOptions,
|
|
86
|
+
IServiceAuthOptions,
|
|
87
|
+
IServiceAssertionClaims,
|
|
88
|
+
TServiceCallerEntry,
|
|
72
89
|
IAuthenticateOptions,
|
|
73
90
|
IAuthUser,
|
|
74
91
|
IJWTTokenPayload,
|
|
@@ -134,13 +151,14 @@ Bearer token service hierarchy:
|
|
|
134
151
|
| Method | Purpose |
|
|
135
152
|
|--------|---------|
|
|
136
153
|
| `defineJWSAuth(opts)` | Validates `jwtSecret` and `getTokenExpiresFn`, binds `IJWSTokenServiceOptions` to `JWT_OPTIONS`, registers `JWSTokenService` |
|
|
137
|
-
| `defineJWKSAuth(opts)` | Switches on `mode
|
|
154
|
+
| `defineJWKSAuth(opts)` | Switches on `mode`.<br>**Issuer:** validates keys/format/kid/getTokenExpiresFn, binds to `JWKS_OPTIONS`, registers `JWKSIssuerTokenService` + `JWKSController`.<br>**Verifier:** validates `jwksUrl`, binds to `JWKS_OPTIONS`, registers `JWKSVerifierTokenService` |
|
|
155
|
+
| `defineServiceAuth(opts)` | Validates `name` and `resolvePrincipal`, registers the verifier and the `service` strategy. With `keys` present it also registers the signer and mounts the certs route. Absent options: does nothing |
|
|
138
156
|
| `defineBasicAuth(opts)` | Validates `verifyCredentials` presence, registers `BasicTokenService`. Skips (debug log) if `basicOptions` not bound |
|
|
139
157
|
| `defineControllers(opts)` | Requires `jwtOptions` when `useAuthController: true`. Calls `defineAuthController()` and registers the generated controller |
|
|
140
158
|
| `defineOAuth2()` | Public stub, called during `binding()`, performs no action - not yet implemented |
|
|
141
159
|
|
|
142
160
|
> [!NOTE]
|
|
143
|
-
> The component reads the discriminated union from `JWT_OPTIONS
|
|
161
|
+
> The component reads the discriminated union from `JWT_OPTIONS`. It then re-binds only the inner options object - `IJWSTokenServiceOptions`, or `IJWKSIssuerOptions`/`IJWKSVerifierOptions` - to `JWKS_OPTIONS` or back to `JWT_OPTIONS`. The token service resolves it with a plain `@inject`.
|
|
144
162
|
|
|
145
163
|
## Binding keys
|
|
146
164
|
|
|
@@ -150,9 +168,11 @@ Bearer token service hierarchy:
|
|
|
150
168
|
| `AuthenticateBindingKeys.JWT_OPTIONS` | `@app/authenticate/jwt-options` | `TJWTTokenServiceOptions` | Conditional | -- |
|
|
151
169
|
| `AuthenticateBindingKeys.JWKS_OPTIONS` | `@app/authenticate/jwks-options` | `IJWKSIssuerOptions \| IJWKSVerifierOptions` | Internal | Bound by the component from `JWT_OPTIONS` |
|
|
152
170
|
| `AuthenticateBindingKeys.BASIC_OPTIONS` | `@app/authenticate/basic-options` | `TBasicTokenServiceOptions` | Conditional | -- |
|
|
171
|
+
| `AuthenticateBindingKeys.SERVICE_OPTIONS` | `@app/authenticate/service-options` | `IServiceAuthOptions` | Conditional | -- |
|
|
172
|
+
| `AuthenticateBindingKeys.SERVICE_CERTS_PATH` | `@app/authenticate/service-certs-path` | `string` | Internal | Bound by the component from `serviceOptions.rest.path` |
|
|
153
173
|
|
|
154
174
|
> [!IMPORTANT]
|
|
155
|
-
> At least one of `JWT_OPTIONS` or `
|
|
175
|
+
> At least one of `JWT_OPTIONS`, `BASIC_OPTIONS` or `SERVICE_OPTIONS` must be bound, or `AuthenticateComponent.binding()` throws. A service that only VERIFIES assertions needs neither jwt nor basic.
|
|
156
176
|
|
|
157
177
|
## Option interfaces
|
|
158
178
|
|
|
@@ -178,9 +198,11 @@ type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // di
|
|
|
178
198
|
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
|
|
179
199
|
| `headerAlgorithm` | `string` | `'HS256'` | No | JWT signing algorithm |
|
|
180
200
|
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom serialize/deserialize per field name |
|
|
201
|
+
| `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
|
|
202
|
+
| `sign` | `{ issuer?, audience? }` | -- | No | Claims stamped on every issued token. A configured value WINS over one supplied in the payload |
|
|
181
203
|
|
|
182
204
|
> [!WARNING]
|
|
183
|
-
> `jwtSecret` is mandatory - the component throws if it
|
|
205
|
+
> `jwtSecret` is mandatory - the component throws if it's missing or equals the placeholder `'unknown_secret'`. `applicationSecret` is optional. When you omit it, the JWT payload stays standard plaintext. Standard fields (`iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat`) are never encrypted either way.
|
|
184
206
|
|
|
185
207
|
### IJWKSIssuerOptions
|
|
186
208
|
|
|
@@ -198,6 +220,8 @@ type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // di
|
|
|
198
220
|
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
|
|
199
221
|
| `applicationSecret` | `string` | -- | No | Enables AES payload field encryption when set |
|
|
200
222
|
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Custom serialize/deserialize per field name |
|
|
223
|
+
| `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
|
|
224
|
+
| `sign` | `{ issuer?, audience? }` | -- | No | Claims stamped on every issued token. A configured value WINS over one supplied in the payload |
|
|
201
225
|
|
|
202
226
|
### IJWKSVerifierOptions
|
|
203
227
|
|
|
@@ -210,6 +234,7 @@ type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // di
|
|
|
210
234
|
| `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload decryption |
|
|
211
235
|
| `applicationSecret` | `string` | -- | No | Must match the issuer's secret to decrypt payloads |
|
|
212
236
|
| `fieldCodecs` | `IPayloadFieldCodec[]` | `[]` | No | Must match the issuer's codecs to decrypt custom fields |
|
|
237
|
+
| `verify` | `IJWTVerifyOptions` | -- | No | Claim checks on verify: `audience`, `issuer`, `subject`, `algorithms`, `clockTolerance`, `maxTokenAge`, `typ`, `requiredClaims`. Unset means signature and time claims only |
|
|
213
238
|
|
|
214
239
|
> [!IMPORTANT]
|
|
215
240
|
> `JWKSVerifierTokenService` cannot sign tokens - `getSigner()`, `getSigningKey()`, and `getDefaultTokenExpiresFn()` all throw. Only `verify()` and `extractCredentials()` are functional.
|
|
@@ -254,7 +279,7 @@ type TRouteAuthenticateConfig =
|
|
|
254
279
|
|
|
255
280
|
| Field | Type | Default | Description |
|
|
256
281
|
|-------|------|---------|-------------|
|
|
257
|
-
| `authenticate.strategies` | `TAuthStrategy[]` | -- | Strategy names to try,
|
|
282
|
+
| `authenticate.strategies` | `TAuthStrategy[]` | -- | Strategy names to try - for example, `['jwt']` or `['jwt', 'basic']`. Any name you register works too; `'jwt'` and `'basic'` are the ones the framework ships |
|
|
258
283
|
| `authenticate.mode` | `'any' \| 'all'` | `'any'` | `'any'`: first success wins. `'all'`: every strategy must pass |
|
|
259
284
|
| `authenticate.skip` | `true` | -- | Skips authentication for this route entirely |
|
|
260
285
|
|
|
@@ -400,7 +425,7 @@ export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) =
|
|
|
400
425
|
5. On failure, throws `401`
|
|
401
426
|
|
|
402
427
|
> [!NOTE]
|
|
403
|
-
> `'any'` mode
|
|
428
|
+
> `'any'` mode logs each failing strategy at debug and discards the error. It 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
429
|
|
|
405
430
|
## Service class hierarchy
|
|
406
431
|
|
|
@@ -430,7 +455,7 @@ AbstractBearerTokenService<E> (extends BaseService)
|
|
|
430
455
|
|
|
431
456
|
### AbstractBearerTokenService
|
|
432
457
|
|
|
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)
|
|
458
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/abstract.service.ts)
|
|
434
459
|
|
|
435
460
|
| Method | Signature | Description |
|
|
436
461
|
|--------|-----------|-------------|
|
|
@@ -446,21 +471,26 @@ Static: `JWT_COMMON_FIELDS: Set<'iss'|'sub'|'aud'|'jti'|'nbf'|'exp'|'iat'>` - ne
|
|
|
446
471
|
|
|
447
472
|
### JWSTokenService
|
|
448
473
|
|
|
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)
|
|
474
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jws.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jws.service.ts)
|
|
450
475
|
|
|
451
|
-
|
|
476
|
+
The constructor validates `jwtSecret` and `getTokenExpiresFn`, throwing `500` if either is missing. It encodes the secret to `Uint8Array` and calls `configurePayloadEncryption()`. `doVerify` calls `jose.jwtVerify()` with the shared secret. `getSigner` signs with header `HS256`, or `headerAlgorithm` if you set one.
|
|
452
477
|
|
|
453
478
|
### AbstractJWKSTokenService
|
|
454
479
|
|
|
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)
|
|
480
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts)
|
|
456
481
|
|
|
457
|
-
`ensureInitialized()` lazily runs `initialize()` on first call
|
|
482
|
+
`ensureInitialized()` lazily runs `initialize()` on the first call. Concurrent callers share the pending promise. If `initialize()` rejects, the promise resets - the next call retries instead of caching the failure.
|
|
458
483
|
|
|
459
484
|
### JWKSIssuerTokenService
|
|
460
485
|
|
|
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)
|
|
486
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts)
|
|
462
487
|
|
|
463
|
-
`initialize()
|
|
488
|
+
`initialize()` runs in order:
|
|
489
|
+
|
|
490
|
+
1. Reads the key content - `readFile` for `keys.driver: 'file'`, inline text for `'text'`.
|
|
491
|
+
2. Imports it - `importPKCS8`/`importSPKI` for PEM, `importJWK` for JWK.
|
|
492
|
+
3. Exports the public JWK with `kid`, `alg`, and `use: 'sig'` set.
|
|
493
|
+
4. Caches the result as `{ keys: [publicJWK] }`.
|
|
464
494
|
|
|
465
495
|
| Method | Signature | Description |
|
|
466
496
|
|--------|-----------|-------------|
|
|
@@ -469,13 +499,13 @@ Constructor validates `jwtSecret` and `getTokenExpiresFn` (throws `500` if missi
|
|
|
469
499
|
|
|
470
500
|
### JWKSVerifierTokenService
|
|
471
501
|
|
|
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)
|
|
502
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts)
|
|
473
503
|
|
|
474
504
|
`initialize()` calls `createRemoteJWKSet(jwksUrl, { cacheMaxAge: cacheTtlMs ?? 43_200_000, cooldownDuration: cooldownMs ?? 30_000 })`. `getSigner`/`getSigningKey`/`getDefaultTokenExpiresFn` all throw - this service is verify-only.
|
|
475
505
|
|
|
476
506
|
### BasicTokenService
|
|
477
507
|
|
|
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)
|
|
508
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/services/basic/service.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/services/basic/service.ts)
|
|
479
509
|
|
|
480
510
|
| Method | Signature | Description |
|
|
481
511
|
|--------|-----------|-------------|
|
|
@@ -486,23 +516,28 @@ Constructor throws `500` if `verifyCredentials` is missing from the injected opt
|
|
|
486
516
|
|
|
487
517
|
## Strategy classes
|
|
488
518
|
|
|
489
|
-
All four strategies extend `BaseHelper
|
|
519
|
+
All four strategies extend `BaseHelper` and implement `IAuthenticationStrategy<E>`. Each one carries:
|
|
520
|
+
|
|
521
|
+
- A `name` field.
|
|
522
|
+
- A `standard` field (Bearer strategies only).
|
|
523
|
+
- One injected token service.
|
|
524
|
+
- An `authenticate(context)` method that calls `extractCredentials()`, then `verify()`.
|
|
490
525
|
|
|
491
526
|
| Strategy | `name` | Injects | File |
|
|
492
527
|
|----------|--------|---------|------|
|
|
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) |
|
|
528
|
+
| `JWSAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWSTokenService` | [`strategies/jws.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/jws.strategy.ts) |
|
|
529
|
+
| `JWKSIssuerAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSIssuerTokenService` | [`strategies/jwks.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/jwks.strategy.ts) |
|
|
495
530
|
| `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) |
|
|
531
|
+
| `BasicAuthenticationStrategy` | `Authentication.STRATEGY_BASIC` | `BasicTokenService` | [`strategies/basic.strategy.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/strategies/basic.strategy.ts) |
|
|
497
532
|
|
|
498
533
|
> [!NOTE]
|
|
499
|
-
> Choose the strategy class
|
|
534
|
+
> Choose the strategy class that matches your JOSE standard. `JWKSIssuerAuthenticationStrategy` and `JWKSVerifierAuthenticationStrategy` both register under the same `'jwt'` name - use only one of the two per service.
|
|
500
535
|
|
|
501
536
|
## JWKSController
|
|
502
537
|
|
|
503
538
|
Serves the JWKS endpoint (default path `/certs`, configurable via `rest.path`). Intentionally unauthenticated - it serves the public keys external verifiers need.
|
|
504
539
|
|
|
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)
|
|
540
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/controllers/jwks/controller.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/controllers/jwks/controller.ts)
|
|
506
541
|
|
|
507
542
|
```typescript
|
|
508
543
|
class JWKSController extends BaseRestController {
|
|
@@ -529,16 +564,16 @@ The component applies `@controller({ path })` to `JWKSController` dynamically at
|
|
|
529
564
|
|
|
530
565
|
`defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController` builds a `BaseRestController` subclass at runtime.
|
|
531
566
|
|
|
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)
|
|
567
|
+
**File:** [`packages/core-server/src/components/auth/authenticate/controllers/factory.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/controllers/factory.ts)
|
|
533
568
|
|
|
534
569
|
**How it works:**
|
|
535
570
|
|
|
536
571
|
1. **Class creation.** `class AuthController extends BaseRestController {}` inside the factory closure, decorated with `@controller({ path: restPath })`, `isStrict: true`.
|
|
537
572
|
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
573
|
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
|
|
574
|
+
4. **Schemas.** Each endpoint uses `payload.<name>.{request,response}.schema` if you provide it. Otherwise it falls back to a built-in default, or `AnyObjectSchema` where there's no built-in schema for that response.
|
|
540
575
|
|
|
541
|
-
Also exports `JWTTokenPayloadSchema`, the Zod schema backing `/who-am-i`'s response
|
|
576
|
+
Also exports `JWTTokenPayloadSchema`, the Zod schema backing `/who-am-i`'s response. It's extended at runtime with an optional `userInformation` field, typed from `payload.getUserInformation.response.schema` (or `AnyObjectSchema` if you don't provide one).
|
|
542
577
|
|
|
543
578
|
## Entity column helper types
|
|
544
579
|
|
|
@@ -564,13 +599,14 @@ type TPolicyDefinitionCommonColumns = {
|
|
|
564
599
|
action: ReturnType<typeof text>;
|
|
565
600
|
effect: ReturnType<typeof text>;
|
|
566
601
|
domain: ReturnType<typeof text>;
|
|
602
|
+
metadata: ReturnType<typeof jsonb>;
|
|
567
603
|
};
|
|
568
604
|
```
|
|
569
605
|
|
|
570
606
|
## File structure
|
|
571
607
|
|
|
572
608
|
```
|
|
573
|
-
packages/core/src/components/auth/
|
|
609
|
+
packages/core-server/src/components/auth/
|
|
574
610
|
├── authenticate/
|
|
575
611
|
│ ├── common/
|
|
576
612
|
│ │ ├── codecs.ts # AuthenticationFieldCodecs (ROLES_CODEC, build() factory)
|
|
@@ -8,10 +8,24 @@ difficulty: intermediate
|
|
|
8
8
|
|
|
9
9
|
Every error message the Authentication component and its services throw, with cause and fix. See the [Overview](./) for initial setup.
|
|
10
10
|
|
|
11
|
+
## Find what you need
|
|
12
|
+
|
|
13
|
+
| Error is thrown by | Go to |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Startup, while wiring the component | [AuthenticateComponent (startup)](#authenticatecomponent-startup) |
|
|
16
|
+
| `verify`/`generate`/`extractCredentials`, shared by JWS, JWKS, and Basic | [Bearer token services (runtime)](#bearer-token-services-runtime) |
|
|
17
|
+
| The `JWSTokenService` constructor | [JWSTokenService (constructor)](#jwstokenservice-constructor) |
|
|
18
|
+
| `JWKSIssuerTokenService` init or key parsing | [JWKSIssuerTokenService (init + runtime)](#jwksissuertokenservice-init-runtime) |
|
|
19
|
+
| `JWKSVerifierTokenService` signing calls | [JWKSVerifierTokenService (runtime)](#jwksverifiertokenservice-runtime) |
|
|
20
|
+
| Basic auth credential decoding | [BasicTokenService (runtime)](#basictokenservice-runtime) |
|
|
21
|
+
| "Descriptor not found" or a strategy registration issue | [AuthenticationStrategyRegistry (startup + runtime)](#authenticationstrategyregistry-startup-runtime) |
|
|
22
|
+
| "Authentication failed" at request time | [AuthenticationProvider (runtime)](#authenticationprovider-runtime) |
|
|
23
|
+
| The built-in `/auth` controller | [Auth controller factory](#auth-controller-factory) |
|
|
24
|
+
|
|
11
25
|
- **Startup errors (400 unless noted)** come from `AuthenticateComponent.binding()` and stop the application before it serves traffic.
|
|
12
26
|
- **Runtime errors (401)** come from request-time credential extraction, verification, or strategy exhaustion.
|
|
13
|
-
- **Structural errors (500)** come from calling an unsupported operation
|
|
14
|
-
- All of them go through `getError()
|
|
27
|
+
- **Structural errors (500)** come from calling an unsupported operation - signing on a verify-only service, for example - or accessing uninitialized state.
|
|
28
|
+
- All of them go through `getError()`. It defaults `statusCode` to `400` when you don't set one - so every "startup" error below is a 400 unless the table says otherwise.
|
|
15
29
|
|
|
16
30
|
```mermaid
|
|
17
31
|
flowchart LR
|
|
@@ -38,7 +52,7 @@ Thrown during `binding()` while validating options and wiring services.
|
|
|
38
52
|
|---------|--------|-------|-----|
|
|
39
53
|
| `[AuthenticateComponent] At least one of jwtOptions or basicOptions must be provided` | 400 | Neither `JWT_OPTIONS` nor `BASIC_OPTIONS` bound before `this.component(AuthenticateComponent)` | Bind at least one before registering the component - see [Setup](./#common-tasks) |
|
|
40
54
|
| `[AuthenticateComponent] Unknown JOSE standard: {standard}` | 400 | `jwtOptions.standard` is not `'JWS'` or `'JWKS'` | Use `JOSEStandards.JWS` or `JOSEStandards.JWKS` |
|
|
41
|
-
| `[defineJWSAuth] Invalid jwtSecret \| Provided: {jwtSecret}` | 400 | `jwtSecret` falsy or equals placeholder `'unknown_secret'` | Set a real secret
|
|
55
|
+
| `[defineJWSAuth] Invalid jwtSecret \| Provided: {jwtSecret}` | 400 | `jwtSecret` falsy or equals placeholder `'unknown_secret'` | Set a real secret - `APP_ENV_JWT_SECRET`, for example |
|
|
42
56
|
| `[defineJWSAuth] getTokenExpiresFn is required` | 400 | `getTokenExpiresFn` missing from JWS options | Provide `() => Number(process.env.APP_ENV_JWT_EXPIRES_IN \|\| 86400)` |
|
|
43
57
|
| `[defineJWKSAuth] keys.private and keys.public are required for issuer mode` | 400 | Issuer mode missing one or both keys | Provide `keys.private` and `keys.public` |
|
|
44
58
|
| `[defineJWKSAuth] keys.format is required and must be one of: pem, jwk` | 400 | `keys.format` missing or invalid | Use `JWKSKeyFormats.PEM` or `JWKSKeyFormats.JWK` |
|
|
@@ -50,7 +64,7 @@ Thrown during `binding()` while validating options and wiring services.
|
|
|
50
64
|
| `[defineControllers] Auth controller requires jwtOptions to be configured` | 400 | `useAuthController: true` but no `jwtOptions` bound | Bind `JWT_OPTIONS` before enabling the auth controller |
|
|
51
65
|
|
|
52
66
|
> [!NOTE]
|
|
53
|
-
> `applicationSecret` is optional and not validated.
|
|
67
|
+
> `applicationSecret` is optional and not validated. Every token service treats it as optional - omitting it disables AES payload encryption, nothing more. Its presence is never checked.
|
|
54
68
|
|
|
55
69
|
## Bearer token services (runtime)
|
|
56
70
|
|
|
@@ -145,7 +159,7 @@ The middleware that executes strategies in the configured mode.
|
|
|
145
159
|
| `Failed to identify authenticated user!` | 401 | `executeAllMode` | All strategies passed in `'all'` mode, but the first strategy's `userId` is falsy |
|
|
146
160
|
| `Invalid authentication mode \| mode: {mode}` | 500 | `createAuthenticateMiddleware` | `mode` is not `'any'` or `'all'` |
|
|
147
161
|
|
|
148
|
-
**Fix for "Authentication failed":** verify the client sends the right header
|
|
162
|
+
**Fix for "Authentication failed":** verify the client sends the right header - `Bearer <token>` or `Basic <base64>`. Common causes: an expired token, a token signed with a different key, or the wrong strategy name in the route config.
|
|
149
163
|
|
|
150
164
|
## Auth controller factory
|
|
151
165
|
|
|
@@ -6,7 +6,7 @@ difficulty: intermediate
|
|
|
6
6
|
|
|
7
7
|
# Authentication
|
|
8
8
|
|
|
9
|
-
`AuthenticateComponent` wires up JWT and/or Basic HTTP authentication
|
|
9
|
+
`AuthenticateComponent` wires up JWT and/or Basic HTTP authentication. It configures token services, route-level strategies, and an optional built-in `/auth` controller for sign-in, sign-up, and password change.
|
|
10
10
|
|
|
11
11
|
## In one example
|
|
12
12
|
|
|
@@ -57,19 +57,20 @@ const SECURE_ROUTE = {
|
|
|
57
57
|
|
|
58
58
|
## How it works
|
|
59
59
|
|
|
60
|
-
- **One component, three auth mechanisms.** `AuthenticateComponent.binding()` reads `JWT_OPTIONS` (JWS or JWKS
|
|
61
|
-
-
|
|
62
|
-
- **
|
|
63
|
-
-
|
|
64
|
-
-
|
|
60
|
+
- **One component, three auth mechanisms.** `AuthenticateComponent.binding()` reads `JWT_OPTIONS` (JWS or JWKS) and `BASIC_OPTIONS` from the DI container, and registers the token services their presence implies.
|
|
61
|
+
- Bind at least one of the two - the component throws at startup if neither is set.
|
|
62
|
+
- **Strategies are manual, on purpose.** The component registers *token services* - JWS, JWKS, Basic - never *strategies*. Register strategies yourself after the component, via `AuthenticationStrategyRegistry.getInstance().register(...)`.
|
|
63
|
+
- **The registry is a DI-backed singleton.** `AuthenticationStrategyRegistry` binds each strategy into the container as a singleton, under the key `authentication.strategy.<name>`. A route's `authenticate.strategies` list references strategies by that same name.
|
|
64
|
+
- **`authenticate()` is the middleware entry point.** Route-level `authenticate: { strategies, mode }` config and the standalone `authenticate()` function both run through the same `AuthenticationProvider`, which sets `Authentication.CURRENT_USER` on success. See [Multi-strategy authentication](./usage#multi-strategy-authentication) for what `'any'` and `'all'` mode do.
|
|
65
|
+
- **The auth controller is optional and generated.** Set `REST_OPTIONS.useAuthController: true` to call `defineAuthController()`, which builds a `BaseRestController` subclass at runtime - routes for `/sign-in`, `/sign-up`, `/change-password`, `/token/refresh`, `/who-am-i`, and `/me`. You back it with your own `IAuthService` implementation.
|
|
65
66
|
|
|
66
67
|
**JOSE standards**
|
|
67
68
|
|
|
68
69
|
| Standard | Class | Keying | Use case |
|
|
69
70
|
|----------|-------|--------|----------|
|
|
70
71
|
| JWS | `JWSTokenService` | Shared secret (HS256) | Single service signs and verifies |
|
|
71
|
-
| JWKS Issuer | `JWKSIssuerTokenService` | Private + public key (ES256/RS256/EdDSA) |
|
|
72
|
-
| JWKS Verifier | `JWKSVerifierTokenService` | Remote JWKS URL |
|
|
72
|
+
| JWKS Issuer | `JWKSIssuerTokenService` | Private + public key (ES256/RS256/EdDSA) | Issues tokens, serves `/certs` |
|
|
73
|
+
| JWKS Verifier | `JWKSVerifierTokenService` | Remote JWKS URL | Verifies tokens from another issuer, only |
|
|
73
74
|
|
|
74
75
|
## Common tasks
|
|
75
76
|
|
|
@@ -100,7 +101,7 @@ this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS })
|
|
|
100
101
|
});
|
|
101
102
|
```
|
|
102
103
|
|
|
103
|
-
**Add Basic auth.** Provide a `verifyCredentials` callback
|
|
104
|
+
**Add Basic auth.** Provide a `verifyCredentials` callback. It becomes the `'basic'` strategy's source of truth.
|
|
104
105
|
|
|
105
106
|
```typescript
|
|
106
107
|
this.bind<TBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS }).toValue({
|
|
@@ -128,7 +129,7 @@ this.bind<TAuthenticationRestOptions>({ key: AuthenticateBindingKeys.REST_OPTION
|
|
|
128
129
|
});
|
|
129
130
|
```
|
|
130
131
|
|
|
131
|
-
**Secure a route with multiple strategies.** `mode: 'any'` (default) falls back through strategies
|
|
132
|
+
**Secure a route with multiple strategies.** `mode: 'any'` (default) falls back through strategies. `mode: 'all'` requires every one to pass.
|
|
132
133
|
|
|
133
134
|
```typescript
|
|
134
135
|
const FALLBACK_AUTH_CONFIG = {
|
|
@@ -142,11 +143,11 @@ const FALLBACK_AUTH_CONFIG = {
|
|
|
142
143
|
**Add auth entity columns to a Drizzle table.** Spread helper functions into `pgTable()` for User/Role/Permission/PolicyDefinition columns.
|
|
143
144
|
|
|
144
145
|
```typescript
|
|
145
|
-
import { extraUserColumns } from '@venizia/ignis';
|
|
146
|
+
import { extraUserColumns, generateIdColumnDefs, generateTzColumnDefs } from '@venizia/ignis';
|
|
146
147
|
|
|
147
148
|
export const users = pgTable('users', {
|
|
148
|
-
...
|
|
149
|
-
...
|
|
149
|
+
...generateIdColumnDefs(),
|
|
150
|
+
...generateTzColumnDefs(),
|
|
150
151
|
...extraUserColumns(),
|
|
151
152
|
username: text('username').unique().notNull(),
|
|
152
153
|
});
|
|
@@ -163,8 +164,8 @@ export const users = pgTable('users', {
|
|
|
163
164
|
|
|
164
165
|
**Files:**
|
|
165
166
|
|
|
166
|
-
- [`packages/core/src/components/auth/authenticate/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/component.ts) - `AuthenticateComponent`
|
|
167
|
-
- [`packages/core/src/components/auth/authenticate/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/types.ts) - all option interfaces
|
|
168
|
-
- [`packages/core/src/components/auth/authenticate/common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/keys.ts) - `AuthenticateBindingKeys`
|
|
169
|
-
- [`packages/core/src/components/auth/authenticate/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authenticate/common/constants.ts) - `Authentication`, `JOSEStandards`, `JWKSModes`, `JWKSKeyDrivers`, `JWKSKeyFormats`
|
|
170
|
-
- [`packages/core/src/components/auth/models/entities`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/models/entities) - `extraUserColumns`, `extraRoleColumns`, `extraPermissionColumns`, `extraPolicyDefinitionColumns`
|
|
167
|
+
- [`packages/core-server/src/components/auth/authenticate/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/component.ts) - `AuthenticateComponent`
|
|
168
|
+
- [`packages/core-server/src/components/auth/authenticate/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/common/types.ts) - all option interfaces
|
|
169
|
+
- [`packages/core-server/src/components/auth/authenticate/common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/common/keys.ts) - `AuthenticateBindingKeys`
|
|
170
|
+
- [`packages/core-server/src/components/auth/authenticate/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authenticate/common/constants.ts) - `Authentication`, `JOSEStandards`, `JWKSModes`, `JWKSKeyDrivers`, `JWKSKeyFormats`
|
|
171
|
+
- [`packages/core-server/src/components/auth/models/entities`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/models/entities) - `extraUserColumns`, `extraRoleColumns`, `extraPermissionColumns`, `extraPolicyDefinitionColumns`
|
|
@@ -8,6 +8,20 @@ difficulty: intermediate
|
|
|
8
8
|
|
|
9
9
|
Task-oriented examples for the Authentication component. See the [Overview](./) for initial setup and the [API Reference](./api) for every option and class.
|
|
10
10
|
|
|
11
|
+
## Find what you need
|
|
12
|
+
|
|
13
|
+
| You want to | Go to |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Require auth on a route, or make one public | [Securing routes](#securing-routes) |
|
|
16
|
+
| Implement sign-in, sign-up, or change-password | [Implementing IAuthService](#implementing-iauthservice) |
|
|
17
|
+
| Split issuer and verifier across services | [JWKS microservice patterns](#jwks-microservice-patterns) |
|
|
18
|
+
| See what happens on each request, step by step | [Auth flows](#auth-flows) |
|
|
19
|
+
| Accept both JWT and Basic on one route | [Multi-strategy authentication](#multi-strategy-authentication) |
|
|
20
|
+
| Encrypt JWT payload fields | [Token encryption (optional AES)](#token-encryption-optional-aes) |
|
|
21
|
+
| Read `CURRENT_USER` in a handler, with types | [Hono context extension](#hono-context-extension) |
|
|
22
|
+
| Call the built-in `/auth` endpoints | [API endpoints](#api-endpoints) |
|
|
23
|
+
| Add auth columns to a Drizzle table | [Entity column helpers](#entity-column-helpers) |
|
|
24
|
+
|
|
11
25
|
## Securing routes
|
|
12
26
|
|
|
13
27
|
**Require one strategy.** Add `authenticate` to the route config.
|
|
@@ -21,7 +35,7 @@ const SECURE_ROUTE_CONFIG = {
|
|
|
21
35
|
} as const;
|
|
22
36
|
```
|
|
23
37
|
|
|
24
|
-
**Accept multiple strategies with fallback.** `mode: 'any'` (default) tries each in order
|
|
38
|
+
**Accept multiple strategies with fallback.** `mode: 'any'` (default) tries each strategy in order. The first success wins.
|
|
25
39
|
|
|
26
40
|
```typescript
|
|
27
41
|
const FALLBACK_AUTH_CONFIG = {
|
|
@@ -43,7 +57,7 @@ const PUBLIC_ROUTE_CONFIG = {
|
|
|
43
57
|
} as const;
|
|
44
58
|
```
|
|
45
59
|
|
|
46
|
-
**Use `authenticate()` as raw Hono middleware.**
|
|
60
|
+
**Use `authenticate()` as raw Hono middleware.** Use this outside route configs - for a plain Hono sub-app, for example.
|
|
47
61
|
|
|
48
62
|
```typescript
|
|
49
63
|
import { authenticate, Authentication, AuthenticationModes } from '@venizia/ignis';
|
|
@@ -83,7 +97,7 @@ const conditionalAuthMiddleware = createMiddleware(async (c, next) => {
|
|
|
83
97
|
|
|
84
98
|
## Implementing IAuthService
|
|
85
99
|
|
|
86
|
-
The built-in auth controller (`useAuthController: true`) delegates every route to a service you provide
|
|
100
|
+
The built-in auth controller (`useAuthController: true`) delegates every route to a service you provide. That service implements `IAuthService`.
|
|
87
101
|
|
|
88
102
|
**JWS-backed service.**
|
|
89
103
|
|
|
@@ -130,7 +144,7 @@ constructor(
|
|
|
130
144
|
) { super({ scope: AuthenticationService.name }); }
|
|
131
145
|
```
|
|
132
146
|
|
|
133
|
-
**Implement `refreshToken` (optional).**
|
|
147
|
+
**Implement `refreshToken` (optional).** It re-issues a token from the caller's currently valid one. IGNIS has no separate refresh token.
|
|
134
148
|
|
|
135
149
|
```typescript
|
|
136
150
|
async refreshToken(context: TContext<Env>): Promise<{ token: string }> {
|
|
@@ -257,8 +271,8 @@ sequenceDiagram
|
|
|
257
271
|
|
|
258
272
|
| Mode | Behavior | Use case |
|
|
259
273
|
|------|----------|----------|
|
|
260
|
-
| `'any'` (default) |
|
|
261
|
-
| `'all'` | Every strategy must pass
|
|
274
|
+
| `'any'` (default) | Tried in order.<br>First success wins.<br>Failures are logged at debug, not returned.<br>All fail -> `401` listing the tried strategies. | Fallback auth (JWT primary, Basic for legacy clients) |
|
|
275
|
+
| `'all'` | Every strategy must pass.<br>First failure rejects immediately.<br>The **first** strategy's user payload is the identity source. | Multi-factor authentication |
|
|
262
276
|
|
|
263
277
|
```mermaid
|
|
264
278
|
flowchart TD
|
|
@@ -279,11 +293,16 @@ flowchart TD
|
|
|
279
293
|
|
|
280
294
|
## Token encryption (optional AES)
|
|
281
295
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
296
|
+
AES payload encryption is off by default. It only activates when you set `applicationSecret` on the JWS/JWKS options.
|
|
297
|
+
|
|
298
|
+
| Aspect | Behavior |
|
|
299
|
+
|---|---|
|
|
300
|
+
| Default | Off. Without `applicationSecret`, payloads stay standard plaintext JWT. |
|
|
301
|
+
| Standard fields | `iss`, `sub`, `aud`, `jti`, `nbf`, `exp`, `iat` are never encrypted, on either side. |
|
|
302
|
+
| Other fields | Both the key and the value are AES-encrypted. `null` and `undefined` values are skipped. |
|
|
303
|
+
| Serialization | `JSON.stringify` by default. Opt in to `AuthenticationFieldCodecs.ROLES_CODEC` for `roles` - it serializes as pipe-separated `id\|identifier\|priority` strings instead. |
|
|
304
|
+
| Secret | Must stay constant. Changing `applicationSecret` invalidates every existing token. |
|
|
305
|
+
| Issuer/verifier match | Both sides need the identical secret, and identical `fieldCodecs` if you use them. |
|
|
287
306
|
|
|
288
307
|
```typescript
|
|
289
308
|
this.bind<TJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
@@ -331,7 +350,7 @@ The built-in auth controller exists only when `REST_OPTIONS.useAuthController: t
|
|
|
331
350
|
| `GET` | `/certs` | No | JWKS endpoint (Issuer mode only) |
|
|
332
351
|
|
|
333
352
|
> [!NOTE]
|
|
334
|
-
> `/auth` is configurable via `controllerOpts.restPath
|
|
353
|
+
> `/auth` is configurable via `controllerOpts.restPath`. `/certs` is configurable via `rest.path` in `IJWKSIssuerOptions`, and is intentionally unauthenticated.
|
|
335
354
|
|
|
336
355
|
**`POST /auth/sign-in`** - body defaults to `SignInRequestSchema` (nested `identifier`/`credential`), overridable via `payload.signIn`.
|
|
337
356
|
|
|
@@ -339,22 +358,22 @@ The built-in auth controller exists only when `REST_OPTIONS.useAuthController: t
|
|
|
339
358
|
{ "token": "eyJhbGciOiJFUzI1NiIsImtpZCI6Im15LWtleS1pZC0xIn0..." }
|
|
340
359
|
```
|
|
341
360
|
|
|
342
|
-
**`POST /auth/sign-up`** - public unless `requireAuthenticatedSignUp: true`. Body defaults to a flat `SignUpRequestSchema` (`username`, `credential` - unlike sign-in'
|
|
361
|
+
**`POST /auth/sign-up`** - public unless `requireAuthenticatedSignUp: true`. Body defaults to a flat `SignUpRequestSchema` (`username`, `credential`) - unlike sign-in, the shape isn't nested.
|
|
343
362
|
|
|
344
363
|
**`POST /auth/change-password`** - always requires JWT. Body defaults to `ChangePasswordRequestSchema` (`scheme`, `oldCredential`, `newCredential`, `userId`).
|
|
345
364
|
|
|
346
365
|
**`POST /auth/token/refresh`** - always requires JWT, no request body. Returns `501` if `IAuthService.refreshToken` isn't implemented.
|
|
347
366
|
|
|
348
|
-
**`GET /auth/who-am-i`** - always requires JWT.
|
|
367
|
+
**`GET /auth/who-am-i`** - always requires JWT. The `withUserInformation` query param (`true`, `false`, `1`, `0`; default `false`) attaches a `userInformation` field from `getUserInformation`. Returns `501` if you request that field without implementing `getUserInformation`.
|
|
349
368
|
|
|
350
369
|
```json
|
|
351
370
|
{ "userId": "123", "roles": [{ "id": "1", "identifier": "admin", "priority": 0 }] }
|
|
352
371
|
```
|
|
353
372
|
|
|
354
|
-
**`GET /auth/me`** - always requires JWT
|
|
373
|
+
**`GET /auth/me`** - always requires JWT, delegating entirely to `getUserInformation(context, {})`. The response is not merged with the JWT payload. Returns `501` if `getUserInformation` isn't implemented.
|
|
355
374
|
|
|
356
375
|
> [!TIP]
|
|
357
|
-
> One `getUserInformation` implementation backs both routes
|
|
376
|
+
> One `getUserInformation` implementation backs both routes. Use `GET /me` for the raw profile. Use `GET /who-am-i?withUserInformation=true` to get it merged with the principal in one round-trip.
|
|
358
377
|
|
|
359
378
|
**`GET /certs`** (Issuer mode only) - public, returns the JSON Web Key Set with `Cache-Control: public, max-age=3600, stale-while-revalidate=86400`.
|
|
360
379
|
|
|
@@ -368,19 +387,22 @@ Column helper functions return pre-configured Drizzle columns for auth-related t
|
|
|
368
387
|
|
|
369
388
|
```typescript
|
|
370
389
|
import { pgTable, text } from 'drizzle-orm/pg-core';
|
|
371
|
-
import {
|
|
390
|
+
import {
|
|
391
|
+
extraUserColumns, extraRoleColumns, extraPermissionColumns, extraPolicyDefinitionColumns,
|
|
392
|
+
generateIdColumnDefs, generateTzColumnDefs,
|
|
393
|
+
} from '@venizia/ignis';
|
|
372
394
|
|
|
373
395
|
export const users = pgTable('users', {
|
|
374
|
-
...
|
|
375
|
-
...
|
|
396
|
+
...generateIdColumnDefs(),
|
|
397
|
+
...generateTzColumnDefs(),
|
|
376
398
|
...extraUserColumns(),
|
|
377
399
|
username: text('username').unique().notNull(),
|
|
378
400
|
passwordHash: text('password_hash').notNull(),
|
|
379
401
|
});
|
|
380
402
|
|
|
381
|
-
export const roles = pgTable('roles', { ...
|
|
382
|
-
export const permissions = pgTable('permissions', { ...
|
|
383
|
-
export const policyDefinitions = pgTable('policy_definitions', { ...
|
|
403
|
+
export const roles = pgTable('roles', { ...generateIdColumnDefs(), ...generateTzColumnDefs(), ...extraRoleColumns() });
|
|
404
|
+
export const permissions = pgTable('permissions', { ...generateIdColumnDefs(), ...generateTzColumnDefs(), ...extraPermissionColumns() });
|
|
405
|
+
export const policyDefinitions = pgTable('policy_definitions', { ...generateIdColumnDefs(), ...generateTzColumnDefs(), ...extraPolicyDefinitionColumns() });
|
|
384
406
|
```
|
|
385
407
|
|
|
386
408
|
**`extraUserColumns(opts?: { idType })`**
|
|
@@ -398,7 +420,7 @@ export const policyDefinitions = pgTable('policy_definitions', { ...withSerialId
|
|
|
398
420
|
|
|
399
421
|
| Column | DB column | Type | Default | Description |
|
|
400
422
|
|--------|-----------|------|---------|-------------|
|
|
401
|
-
| `identifier` | `identifier` | `text` (unique) | -- |
|
|
423
|
+
| `identifier` | `identifier` | `text` (unique) | -- | For example, `'admin'`, `'editor'` |
|
|
402
424
|
| `name` | `name` | `text` | -- | Human-readable name |
|
|
403
425
|
| `description` | `description` | `text` | `null` | Optional |
|
|
404
426
|
| `priority` | `priority` | `integer` | -- | Lower = higher priority |
|
|
@@ -410,9 +432,9 @@ export const policyDefinitions = pgTable('policy_definitions', { ...withSerialId
|
|
|
410
432
|
|--------|-----------|------|---------|-------------|
|
|
411
433
|
| `code` | `code` | `text` (unique) | -- | Unique permission code |
|
|
412
434
|
| `name` | `name` | `text` | -- | Display name |
|
|
413
|
-
| `subject` | `subject` | `text` | -- |
|
|
414
|
-
| `method` | `method` | `text` | -- |
|
|
415
|
-
| `action` | `action` | `text` | -- |
|
|
435
|
+
| `subject` | `subject` | `text` | -- | For example, `'User'`, `'Order'` |
|
|
436
|
+
| `method` | `method` | `text` | -- | For example, `'GET'`, `'POST'` |
|
|
437
|
+
| `action` | `action` | `text` | -- | For example, `'read'`, `'write'` |
|
|
416
438
|
| `scope` | `scope` | `text` | -- | Permission scope |
|
|
417
439
|
| `description` | `description` | `text` | `null` | Optional |
|
|
418
440
|
| `parentId` | `parent_id` | `text` or `integer` | `null` | Depends on `idType` |
|
|
@@ -421,16 +443,19 @@ export const policyDefinitions = pgTable('policy_definitions', { ...withSerialId
|
|
|
421
443
|
|
|
422
444
|
| Column | DB column | Type | Nullable | Description |
|
|
423
445
|
|--------|-----------|------|----------|-------------|
|
|
424
|
-
| `variant` | `variant` | `text` | No | `
|
|
425
|
-
| `subjectType` | `subject_type` | `text` | No |
|
|
426
|
-
| `targetType` | `target_type` | `text` | No |
|
|
446
|
+
| `variant` | `variant` | `text` | No | One of the seven `AuthorizationPolicyVariants` edge kinds: `grant`, `assign_role`, `role_inherits`, `join_domain`, `domain_inherits`, `resource_inherits`, `action_inherits` |
|
|
447
|
+
| `subjectType` | `subject_type` | `text` | No | For example, `'user'`, `'Role'` |
|
|
448
|
+
| `targetType` | `target_type` | `text` | No | For example, `'Permission'`, `'Role'` |
|
|
427
449
|
| `action` | `action` | `text` | Yes | Policy action |
|
|
428
450
|
| `effect` | `effect` | `text` | Yes | `'allow'` / `'deny'` |
|
|
429
451
|
| `domain` | `domain` | `text` | Yes | Multi-tenancy domain |
|
|
430
452
|
| `subjectId` | `subject_id` | `text` or `integer` | No | Depends on `idType` |
|
|
431
453
|
| `targetId` | `target_id` | `text` or `integer` | No | Depends on `idType` |
|
|
454
|
+
| `metadata` | `metadata` | `jsonb` | Yes | Free-form metadata. Only some grants populate it |
|
|
455
|
+
|
|
456
|
+
All `idType` options default to `'number'` (`integer` columns). Pass `'string'` for `text` columns - UUID primary keys, for example.
|
|
432
457
|
|
|
433
|
-
|
|
458
|
+
`'p'` and `'g'` are Casbin rule prefixes, not `variant` values - each `variant` maps to one of them internally. See the [Authorization component](../authorization/) to build these tables end to end. Its [Usage guide](../authorization/usage) covers policy definitions, domain scoping, and the adapter that reads this table.
|
|
434
459
|
|
|
435
460
|
## See also
|
|
436
461
|
|