@rineex/auth-core 0.0.0 → 0.0.2
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/Architecture.md +257 -0
- package/CHANGELOG.md +16 -0
- package/Definition.md +1490 -0
- package/Develop.md +0 -0
- package/RULES.md +1470 -0
- package/eslint.config.mjs +58 -0
- package/package.json +22 -20
- package/src/domain/aggregates/authentication-attempt.aggregate.ts +119 -0
- package/src/domain/entities/identity.entity.ts +13 -0
- package/src/domain/events/authentication-failed.event.ts +24 -0
- package/src/domain/events/authentication-started.event.ts +29 -0
- package/src/domain/events/authentication-succeeded.event.ts +24 -0
- package/src/domain/value-objects/auth-attempt-id.vo.ts +19 -0
- package/src/domain/value-objects/auth-method.vo.ts +21 -0
- package/src/domain/value-objects/auth-status.vo.ts +38 -0
- package/src/domain/value-objects/identity-id.vo.ts +22 -0
- package/src/index.ts +1 -0
- package/src/ports/inbound/auth-method.port.ts +18 -0
- package/src/ports/outbound/authentication-attempt.repository.port.ts +11 -0
- package/src/types/auth-context.type.ts +11 -0
- package/tsconfig.build.json +6 -0
- package/tsconfig.json +25 -0
- package/tsup.config.ts +13 -0
- package/vitest.config.ts +12 -0
- package/dist/index.d.mts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -18
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -1
- package/dist/index.mjs.map +0 -1
package/RULES.md
ADDED
|
@@ -0,0 +1,1470 @@
|
|
|
1
|
+
# Step 7 — Port & Adapter Map (with Bundle-Size & Modularization First)
|
|
2
|
+
|
|
3
|
+
This step defines **what gets compiled together, what is optional, and what can
|
|
4
|
+
be tree-shaken**.
|
|
5
|
+
|
|
6
|
+
If this is wrong, your “install only what you need” promise is impossible.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Hard Requirement (Restated Clearly)
|
|
11
|
+
|
|
12
|
+
Your auth core must satisfy **all** of these:
|
|
13
|
+
|
|
14
|
+
- Zero required adapters
|
|
15
|
+
- Zero required auth methods
|
|
16
|
+
- Zero required storage
|
|
17
|
+
- Domain usable alone
|
|
18
|
+
- Each auth method installable separately
|
|
19
|
+
- Each adapter installable separately
|
|
20
|
+
- No transitive bloat
|
|
21
|
+
- Tree-shakable
|
|
22
|
+
- Side-effect free modules
|
|
23
|
+
|
|
24
|
+
If any package pulls more than it needs → fail.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. High-Level Package Topology (Non-Negotiable)
|
|
29
|
+
|
|
30
|
+
This is the **only sane layout**:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
@auth/core-domain ← pure domain (mandatory)
|
|
34
|
+
@auth/core-ports ← domain ports (mandatory)
|
|
35
|
+
|
|
36
|
+
@auth/method-password
|
|
37
|
+
@auth/method-passwordless
|
|
38
|
+
@auth/method-otp
|
|
39
|
+
@auth/method-oauth
|
|
40
|
+
@auth/method-passkey
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
@auth/adapter-memory
|
|
44
|
+
@auth/adapter-postgres
|
|
45
|
+
@auth/adapter-redis
|
|
46
|
+
@auth/adapter-http
|
|
47
|
+
@auth/adapter-jwt
|
|
48
|
+
@auth/adapter-oidc
|
|
49
|
+
...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Rules
|
|
53
|
+
|
|
54
|
+
- `core-domain` depends on **nothing**
|
|
55
|
+
- `core-ports` depends only on `core-domain`
|
|
56
|
+
- Methods depend on `core-domain + core-ports`
|
|
57
|
+
- Adapters depend on `core-ports`
|
|
58
|
+
- No reverse dependencies. Ever.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 3. What Lives in `@auth/core-domain`
|
|
63
|
+
|
|
64
|
+
**Only meaning and rules.**
|
|
65
|
+
|
|
66
|
+
Includes:
|
|
67
|
+
|
|
68
|
+
- Aggregates
|
|
69
|
+
- Entities
|
|
70
|
+
- Value Objects
|
|
71
|
+
- Domain Services
|
|
72
|
+
- Invariants
|
|
73
|
+
- Domain Events
|
|
74
|
+
|
|
75
|
+
Explicitly excluded:
|
|
76
|
+
|
|
77
|
+
- Interfaces to infra
|
|
78
|
+
- Repositories
|
|
79
|
+
- Verification logic
|
|
80
|
+
- Serialization
|
|
81
|
+
- Config loading
|
|
82
|
+
|
|
83
|
+
Bundle size: **tiny** This package should never change often.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 4. What Lives in `@auth/core-ports`
|
|
88
|
+
|
|
89
|
+
This is the **only dependency surface** for extensions.
|
|
90
|
+
|
|
91
|
+
### 4.1 Persistence Ports
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
PrincipalRepository;
|
|
95
|
+
CredentialRepository;
|
|
96
|
+
AuthenticationAttemptRepository;
|
|
97
|
+
AuthenticationSessionRepository;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Rules:
|
|
101
|
+
|
|
102
|
+
- No SQL
|
|
103
|
+
- No ORM
|
|
104
|
+
- No query builders
|
|
105
|
+
- No pagination logic
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### 4.2 Capability / SPI Ports
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
AuthMethodRegistry;
|
|
113
|
+
AuthProofVerifier;
|
|
114
|
+
ChallengeIssuer;
|
|
115
|
+
RiskEvaluator;
|
|
116
|
+
TokenIssuer;
|
|
117
|
+
SessionRepresentationFactory;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Rules:
|
|
121
|
+
|
|
122
|
+
- Stateless interfaces
|
|
123
|
+
- No default implementations
|
|
124
|
+
- No optional methods
|
|
125
|
+
- No framework types
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 5. Auth Methods as Separately Installable Units
|
|
130
|
+
|
|
131
|
+
Each auth method is **one package**.
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
@auth/method-password
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Contains:
|
|
140
|
+
|
|
141
|
+
- AuthMethodDefinition
|
|
142
|
+
- Required input schema (domain-level)
|
|
143
|
+
- Proof mapping
|
|
144
|
+
- Registration function
|
|
145
|
+
|
|
146
|
+
Does NOT contain:
|
|
147
|
+
|
|
148
|
+
- Hashing logic
|
|
149
|
+
- DB access
|
|
150
|
+
- HTTP handling
|
|
151
|
+
- SDKs
|
|
152
|
+
|
|
153
|
+
This guarantees:
|
|
154
|
+
|
|
155
|
+
- Installing OTP does not install OAuth
|
|
156
|
+
- Installing OAuth does not install JWT
|
|
157
|
+
- Bundle size stays minimal
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 6. Adapter Packages (Infra Only)
|
|
162
|
+
|
|
163
|
+
Adapters implement **one port group only**.
|
|
164
|
+
|
|
165
|
+
Bad ❌
|
|
166
|
+
|
|
167
|
+
> one adapter doing DB + HTTP + JWT
|
|
168
|
+
|
|
169
|
+
Good ✅
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
@auth/adapter-postgres
|
|
173
|
+
@auth/adapter-redis
|
|
174
|
+
@auth/adapter-jwt
|
|
175
|
+
@auth/adapter-http-express
|
|
176
|
+
@auth/adapter-http-hono
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Each adapter:
|
|
180
|
+
|
|
181
|
+
- Implements interfaces from `core-ports`
|
|
182
|
+
- Knows nothing about domain internals
|
|
183
|
+
- Can be swapped freely
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 7. Dependency Direction (Strict)
|
|
188
|
+
|
|
189
|
+
This diagram must never be violated:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
[ core-domain ]
|
|
193
|
+
↑
|
|
194
|
+
[ core-ports ]
|
|
195
|
+
↑
|
|
196
|
+
[ methods ] [ adapters ]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Never:
|
|
200
|
+
|
|
201
|
+
- Adapter → Method
|
|
202
|
+
- Method → Adapter
|
|
203
|
+
- Domain → Port implementation
|
|
204
|
+
- Domain → Method
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 8. Tree-Shaking & Bundle Rules
|
|
209
|
+
|
|
210
|
+
To make bundle-size guarantees real:
|
|
211
|
+
|
|
212
|
+
- No side effects in module root
|
|
213
|
+
- No auto-registration
|
|
214
|
+
- Explicit `register()` calls
|
|
215
|
+
- Pure ES modules
|
|
216
|
+
- No global singletons
|
|
217
|
+
- No reflection-based loading
|
|
218
|
+
|
|
219
|
+
If a user doesn’t import it → it must not exist in bundle.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 9. Runtime Composition (User Responsibility)
|
|
224
|
+
|
|
225
|
+
The **consumer** wires things together:
|
|
226
|
+
|
|
227
|
+
- Registers methods
|
|
228
|
+
- Provides adapters
|
|
229
|
+
- Selects flows
|
|
230
|
+
- Configures policies
|
|
231
|
+
|
|
232
|
+
Your core:
|
|
233
|
+
|
|
234
|
+
- Does not auto-wire
|
|
235
|
+
- Does not scan
|
|
236
|
+
- Does not assume environment
|
|
237
|
+
|
|
238
|
+
This keeps:
|
|
239
|
+
|
|
240
|
+
- Core small
|
|
241
|
+
- Behavior explicit
|
|
242
|
+
- Enterprise-safe
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 10. Validation Checklist (Must Pass)
|
|
247
|
+
|
|
248
|
+
Before moving forward:
|
|
249
|
+
|
|
250
|
+
✔ Core domain installs alone ✔ No auth method is mandatory ✔ No adapter is
|
|
251
|
+
mandatory ✔ Importing one method doesn’t pull others ✔ Infra choices are
|
|
252
|
+
replaceable ✔ Bundle analyzer shows only used code
|
|
253
|
+
|
|
254
|
+
If any fails → restructure now.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Step 7 Status
|
|
259
|
+
|
|
260
|
+
✅ Port boundaries defined ✅ Modular packaging model defined ✅ Bundle-size
|
|
261
|
+
safe ✅ Tree-shakable ✅ Enterprise-usable
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
# Step 9 — Minimal End-to-End Walkthrough (Passwordless)
|
|
266
|
+
|
|
267
|
+
> Goal: Show a complete authentication cycle using **Passwordless Email**, fully
|
|
268
|
+
> abstract, modular, and documented.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 1. Module Setup (Separable & Installable)
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
@auth/core-domain
|
|
276
|
+
@auth/core-ports
|
|
277
|
+
@auth/method-passwordless
|
|
278
|
+
@auth/adapter-memory (optional for demonstration)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Rules:
|
|
282
|
+
|
|
283
|
+
- Core-domain + core-ports always installed
|
|
284
|
+
- Method installed separately
|
|
285
|
+
- Adapter optional
|
|
286
|
+
- No infra dependencies required
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 2. Example Domain Objects Involved
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
// AuthenticationAttempt aggregate
|
|
294
|
+
const attempt = AuthenticationAttempt.create({
|
|
295
|
+
flowId: 'passwordless_flow',
|
|
296
|
+
principalId: 'user-123',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Principal entity exists
|
|
300
|
+
const principal = Principal.create({ id: 'user-123' });
|
|
301
|
+
|
|
302
|
+
// Credential for Passwordless (domain-only)
|
|
303
|
+
const credential = Credential.create({
|
|
304
|
+
principalId: principal.id,
|
|
305
|
+
methodType: 'passwordless_email',
|
|
306
|
+
factor: 'possession',
|
|
307
|
+
status: 'Active',
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Notes:**
|
|
312
|
+
|
|
313
|
+
- All objects are **domain-pure**.
|
|
314
|
+
- No secrets stored in domain.
|
|
315
|
+
- All lifecycle and invariants enforced.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## 3. AuthMethod Registration (SPI Usage)
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
const registry: IAuthMethodRegistry = new AuthMethodRegistry();
|
|
323
|
+
|
|
324
|
+
// Passwordless Method (installed separately)
|
|
325
|
+
const passwordlessMethod: IAuthMethod<{ email: string }, AuthProof> =
|
|
326
|
+
new PasswordlessEmailMethod();
|
|
327
|
+
|
|
328
|
+
registry.register(passwordlessMethod);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Documentation Notes:**
|
|
332
|
+
|
|
333
|
+
- Registration explicit, tree-shakable.
|
|
334
|
+
- SPI ensures domain does not know implementation.
|
|
335
|
+
- Developers can see registry calls in code history for traceability.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 4. Authentication Flow Definition (DSL)
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
const passwordlessFlow = new AuthenticationFlow({
|
|
343
|
+
flowId: 'passwordless_flow',
|
|
344
|
+
name: 'Passwordless Email Flow',
|
|
345
|
+
steps: [
|
|
346
|
+
{
|
|
347
|
+
stepId: 'email_link_step',
|
|
348
|
+
authMethodType: 'passwordless_email',
|
|
349
|
+
required: true,
|
|
350
|
+
onSuccess: { targetStepId: 'terminal_success' },
|
|
351
|
+
onFailure: { targetStepId: 'terminal_failed' },
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
entryConditions: [],
|
|
355
|
+
exitConditions: [],
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Documentation Notes:**
|
|
360
|
+
|
|
361
|
+
- Flow is declarative and versionable.
|
|
362
|
+
- Steps link to AuthMethodType.
|
|
363
|
+
- Developers reading code see the exact orchestration chain.
|
|
364
|
+
- Policy references are explicit (future step-up integration).
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 5. Creating AuthenticationAttempt
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
attempt.startFlow(passwordlessFlow);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
- Creates domain snapshot
|
|
375
|
+
- Status = `InProgress`
|
|
376
|
+
- Step = first step
|
|
377
|
+
- Immutable reference to flow
|
|
378
|
+
|
|
379
|
+
**Documentation Notes:**
|
|
380
|
+
|
|
381
|
+
- All state transitions logged in code via events
|
|
382
|
+
- Chainable domain events can be traced to attempt creation
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 6. Issue Challenge (Passwordless Email Example)
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
const challenge = await passwordlessMethod.issueChallenge?.({
|
|
390
|
+
principalId: principal.id,
|
|
391
|
+
attemptId: attempt.id,
|
|
392
|
+
metadata: { email: 'user@example.com' },
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
- Returns a Challenge object (domain-agnostic)
|
|
397
|
+
- No SMTP logic in domain
|
|
398
|
+
- Code clearly separates **domain vs infra**
|
|
399
|
+
- Challenge metadata traceable in code comments
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## 7. Verify Challenge / Authenticate Step
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
const proof = await passwordlessMethod.verifyChallenge?.(
|
|
407
|
+
{
|
|
408
|
+
challengeId: challenge.id,
|
|
409
|
+
response: 'user-clicked-link',
|
|
410
|
+
},
|
|
411
|
+
{ principalId: principal.id, attemptId: attempt.id },
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
if (proof) {
|
|
415
|
+
attempt.completeStep(proof);
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Documentation Notes:**
|
|
420
|
+
|
|
421
|
+
- Proof is immutable and domain-pure
|
|
422
|
+
- Chainable: attempt -> proof -> session creation
|
|
423
|
+
- Developer reading this sees exact flow progression
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## 8. Session Creation
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
const session = AuthenticationSession.createFromAttempt(attempt, {
|
|
431
|
+
trustLevel: 'Medium',
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
- Generates domain session only
|
|
436
|
+
- No tokens, cookies, or DB
|
|
437
|
+
- TrustLevel reflects factors used
|
|
438
|
+
- ContextSnapshot stored for audit
|
|
439
|
+
|
|
440
|
+
**Documentation Notes:**
|
|
441
|
+
|
|
442
|
+
- Sessions are traceable back to attempt
|
|
443
|
+
- Code comments and JSDoc describe domain reasoning
|
|
444
|
+
- Any developer reading can follow entire chain
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## 9. Policy Integration (Optional Hook)
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
const policyResult = policyEvaluator.evaluate({ attempt, session, context });
|
|
452
|
+
if (policyResult.decision === 'RequireStepUp') {
|
|
453
|
+
// Step-up flow can be chained
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Notes:**
|
|
458
|
+
|
|
459
|
+
- Policies are referenced, not hard-coded
|
|
460
|
+
- Fully traceable, chainable in domain events
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## 10. Summary of Traceable Chain
|
|
465
|
+
|
|
466
|
+
```
|
|
467
|
+
Principal -> Credential -> AuthFlow -> AuthenticationAttempt -> Challenge -> AuthProof -> AuthenticationSession
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**All events are documented, chainable, and domain-pure**. Developers reading
|
|
471
|
+
code can **trace every step** without touching infra.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## 11. Developer Documentation Strategy (Recommended)
|
|
476
|
+
|
|
477
|
+
- Each class/interface has **JSDoc** explaining:
|
|
478
|
+
- Purpose
|
|
479
|
+
- Usage
|
|
480
|
+
- Domain invariants
|
|
481
|
+
- Chainable events
|
|
482
|
+
- Hooks for adapters
|
|
483
|
+
|
|
484
|
+
- SPI registration, flows, attempts, proofs, sessions — **all documented**
|
|
485
|
+
- Link to external docs:
|
|
486
|
+
- Passwordless installation guide
|
|
487
|
+
- Adapter setup
|
|
488
|
+
- Policy DSL examples
|
|
489
|
+
|
|
490
|
+
- Optional: `@link` in JSDoc for chainable navigation
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## Step 9 Status
|
|
495
|
+
|
|
496
|
+
✅ Minimal end-to-end flow defined (Passwordless) ✅ Domain-only, infra-agnostic
|
|
497
|
+
✅ Traceable chain with chainable documentation ✅ Modular, tree-shakable,
|
|
498
|
+
extensible
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
# Step 10 — Policy DSL Formalization
|
|
503
|
+
|
|
504
|
+
> Goal: Enable fully declarative policies for authentication, step-up, risk, and
|
|
505
|
+
> multi-factor flows. Policies must be **versionable, chainable, and
|
|
506
|
+
> traceable**.
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## 1. Core Principles
|
|
511
|
+
|
|
512
|
+
- Policies are **data**, not logic.
|
|
513
|
+
- Evaluated by a domain service (e.g., `AuthenticationPolicyEvaluator`).
|
|
514
|
+
- Traceable: each policy evaluation leaves a chainable record.
|
|
515
|
+
- Extensible: new conditions, actions, or flows can be added without code
|
|
516
|
+
change.
|
|
517
|
+
- Enterprise-ready: multi-tenant, step-up, conditional flows supported.
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## 2. Policy Structure
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
interface AuthenticationPolicy {
|
|
525
|
+
/**
|
|
526
|
+
* Unique identifier
|
|
527
|
+
*/
|
|
528
|
+
policyId: string;
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Human-readable name
|
|
532
|
+
*/
|
|
533
|
+
name: string;
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Scope of the policy: global, flow, principal, method
|
|
537
|
+
*/
|
|
538
|
+
scope: PolicyScope;
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Ordered rules
|
|
542
|
+
*/
|
|
543
|
+
rules: PolicyRule[];
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Optional metadata for auditing
|
|
547
|
+
*/
|
|
548
|
+
metadata?: Record<string, unknown>;
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## 3. PolicyScope
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
type PolicyScope =
|
|
558
|
+
| 'Global'
|
|
559
|
+
| 'Principal'
|
|
560
|
+
| 'AuthMethod'
|
|
561
|
+
| 'AuthFlow'
|
|
562
|
+
| 'Resource';
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
- Declarative scope ensures traceability.
|
|
566
|
+
- Policies applied to same subject are evaluated in order.
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## 4. PolicyRule
|
|
571
|
+
|
|
572
|
+
```ts
|
|
573
|
+
interface PolicyRule {
|
|
574
|
+
/**
|
|
575
|
+
* Condition to evaluate
|
|
576
|
+
*/
|
|
577
|
+
condition: ConditionExpression;
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Action if condition is true
|
|
581
|
+
*/
|
|
582
|
+
action: PolicyAction;
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Optional reason for audit trail
|
|
586
|
+
*/
|
|
587
|
+
reason?: string;
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
- Rules are **atomic**, traceable, and versionable.
|
|
592
|
+
- Reason field ensures **chainable audit logging**.
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## 5. ConditionExpression
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
interface ConditionExpression {
|
|
600
|
+
subject: string; // e.g., 'risk.score', 'device.trusted', 'principal.type'
|
|
601
|
+
operator: Operator; // equals, notEquals, greaterThan, lessThan, in, notIn
|
|
602
|
+
value: unknown; // domain-agnostic value
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
- Minimal, domain-agnostic
|
|
607
|
+
- Serializable
|
|
608
|
+
- Developers can trace which condition triggered which action
|
|
609
|
+
|
|
610
|
+
### Operators
|
|
611
|
+
|
|
612
|
+
```ts
|
|
613
|
+
type Operator =
|
|
614
|
+
| 'equals'
|
|
615
|
+
| 'notEquals'
|
|
616
|
+
| 'greaterThan'
|
|
617
|
+
| 'lessThan'
|
|
618
|
+
| 'in'
|
|
619
|
+
| 'notIn';
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
## 6. PolicyAction
|
|
625
|
+
|
|
626
|
+
```ts
|
|
627
|
+
type PolicyAction =
|
|
628
|
+
| { type: 'Allow' }
|
|
629
|
+
| { type: 'Deny' }
|
|
630
|
+
| { type: 'RequireStepUp'; requiredFactors: string[] }
|
|
631
|
+
| { type: 'SelectFlow'; flowId: string }
|
|
632
|
+
| { type: 'LimitTrustLevel'; level: TrustLevel };
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
- Fully declarative
|
|
636
|
+
- Chainable
|
|
637
|
+
- Traceable in logs
|
|
638
|
+
- Supports step-up, conditional flows, multi-factor, trust downgrades
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## 7. Policy Evaluation Result
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
interface PolicyEvaluationResult {
|
|
646
|
+
decision: 'Allow' | 'Deny' | 'StepUpRequired' | 'FlowSelected';
|
|
647
|
+
triggeredRules: PolicyRule[];
|
|
648
|
+
metadata?: Record<string, unknown>;
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
- All rules evaluated or short-circuited
|
|
653
|
+
- `triggeredRules` allows **audit trail chaining**
|
|
654
|
+
- Metadata includes reason, timestamp, and evaluation context
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
## 8. Policy Evaluation Flow (Abstract)
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
class AuthenticationPolicyEvaluator {
|
|
662
|
+
async evaluate(
|
|
663
|
+
context: AuthEvaluationContext,
|
|
664
|
+
policies: AuthenticationPolicy[],
|
|
665
|
+
): Promise<PolicyEvaluationResult> {
|
|
666
|
+
// Abstract: domain only
|
|
667
|
+
// Implementations will run the rules, produce decision, and trigger events
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
- `AuthEvaluationContext` includes attempt, session, principal, device, risk,
|
|
673
|
+
etc.
|
|
674
|
+
- Result is **domain-only**, no infra logic.
|
|
675
|
+
- Each evaluation is **traceable, versionable, chainable**.
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## 9. Example Policy (Passwordless Step-Up)
|
|
680
|
+
|
|
681
|
+
```ts
|
|
682
|
+
const highRiskStepUpPolicy: AuthenticationPolicy = {
|
|
683
|
+
policyId: 'policy-001',
|
|
684
|
+
name: 'High Risk Step-Up',
|
|
685
|
+
scope: 'Global',
|
|
686
|
+
rules: [
|
|
687
|
+
{
|
|
688
|
+
condition: { subject: 'risk.score', operator: 'greaterThan', value: 70 },
|
|
689
|
+
action: { type: 'RequireStepUp', requiredFactors: ['otp'] },
|
|
690
|
+
reason: 'High-risk login requires additional verification',
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
};
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
- Traceable in logs (`policyId`, `reason`)
|
|
697
|
+
- Serializable for storage or audit
|
|
698
|
+
- Extensible for enterprise tenants
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
## 10. Multi-Rule & Flow Selection Example
|
|
703
|
+
|
|
704
|
+
```ts
|
|
705
|
+
const enterpriseFlowPolicy: AuthenticationPolicy = {
|
|
706
|
+
policyId: 'policy-002',
|
|
707
|
+
name: 'Enterprise Flow Selector',
|
|
708
|
+
scope: 'Principal',
|
|
709
|
+
rules: [
|
|
710
|
+
{
|
|
711
|
+
condition: {
|
|
712
|
+
subject: 'principal.type',
|
|
713
|
+
operator: 'equals',
|
|
714
|
+
value: 'Service',
|
|
715
|
+
},
|
|
716
|
+
action: { type: 'SelectFlow', flowId: 'm2m_flow' },
|
|
717
|
+
reason: 'Service accounts must use machine-to-machine flow',
|
|
718
|
+
},
|
|
719
|
+
],
|
|
720
|
+
};
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
- Policies **do not execute flows**, just select them
|
|
724
|
+
- Chainable and auditable
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## 11. Documentation & Traceability Rules
|
|
729
|
+
|
|
730
|
+
- Every policy and rule must have:
|
|
731
|
+
- `policyId`
|
|
732
|
+
- `reason`
|
|
733
|
+
- Optional metadata linking to docs or JSDoc
|
|
734
|
+
|
|
735
|
+
- All evaluations **emit triggeredRules** → chainable audit trail
|
|
736
|
+
- Developers can **follow evaluation chain in code** and see exact reasoning
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
## 12. Extensibility Guarantees
|
|
741
|
+
|
|
742
|
+
- New operators: add to `Operator` enum
|
|
743
|
+
- New actions: extend `PolicyAction` union
|
|
744
|
+
- New condition subjects: extend `AuthEvaluationContext`
|
|
745
|
+
- All changes are **modular, traceable, and versionable**
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## Step 10 Status
|
|
750
|
+
|
|
751
|
+
✅ Policy DSL formalized ✅ Declarative, auditable, traceable ✅
|
|
752
|
+
Enterprise-ready (step-up, multi-flow, trust-level) ✅ Chainable in code for
|
|
753
|
+
developer reference
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
# Step 11 — Multi-Tenant / Enterprise Extension Model
|
|
758
|
+
|
|
759
|
+
> Goal: Enable the auth core to serve multiple tenants or enterprise clients
|
|
760
|
+
> with isolated configurations, policies, flows, and custom auth methods,
|
|
761
|
+
> without changing core domain.
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## 1. Core Principles
|
|
766
|
+
|
|
767
|
+
- Each tenant can have **custom flows, policies, and auth methods**.
|
|
768
|
+
- Domain objects remain **tenant-agnostic**.
|
|
769
|
+
- Configuration is **composable**, versioned, and auditable.
|
|
770
|
+
- No core domain changes required for new tenants.
|
|
771
|
+
- Traceable: every domain object records **tenant context**.
|
|
772
|
+
|
|
773
|
+
---
|
|
774
|
+
|
|
775
|
+
## 2. Tenant Context (Value Object)
|
|
776
|
+
|
|
777
|
+
```ts
|
|
778
|
+
interface TenantContext {
|
|
779
|
+
tenantId: string;
|
|
780
|
+
name?: string;
|
|
781
|
+
metadata?: Record<string, unknown>;
|
|
782
|
+
}
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
- Attached to **Principal, AuthenticationAttempt, AuthenticationSession, Policy
|
|
786
|
+
evaluation**.
|
|
787
|
+
- Provides **traceable chain** of which tenant a domain object belongs to.
|
|
788
|
+
- Supports **multi-tenant logging and auditing**.
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
## 3. Tenant-Specific Flows
|
|
793
|
+
|
|
794
|
+
- Flows can be defined **per tenant**.
|
|
795
|
+
- Example:
|
|
796
|
+
|
|
797
|
+
```ts
|
|
798
|
+
const tenantFlow = new AuthenticationFlow({
|
|
799
|
+
flowId: 'enterprise_passwordless',
|
|
800
|
+
tenantId: 'tenant-123',
|
|
801
|
+
name: 'Enterprise Passwordless Flow',
|
|
802
|
+
steps: [
|
|
803
|
+
{
|
|
804
|
+
stepId: 'email_link_step',
|
|
805
|
+
authMethodType: 'passwordless_email',
|
|
806
|
+
required: true,
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
});
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
- `tenantId` ensures flows are isolated.
|
|
813
|
+
- Developers can **trace flows by tenant**.
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
## 4. Tenant-Specific Policies
|
|
818
|
+
|
|
819
|
+
- Policies are scoped per tenant.
|
|
820
|
+
- Example:
|
|
821
|
+
|
|
822
|
+
```ts
|
|
823
|
+
const enterprisePolicy: AuthenticationPolicy = {
|
|
824
|
+
policyId: 'policy-tenant-001',
|
|
825
|
+
name: 'Tenant 123 MFA Policy',
|
|
826
|
+
scope: 'Principal',
|
|
827
|
+
rules: [
|
|
828
|
+
{
|
|
829
|
+
condition: { subject: 'risk.score', operator: 'greaterThan', value: 50 },
|
|
830
|
+
action: { type: 'RequireStepUp', requiredFactors: ['otp'] },
|
|
831
|
+
reason: 'Tenant requires additional verification for medium-risk logins',
|
|
832
|
+
},
|
|
833
|
+
],
|
|
834
|
+
metadata: { tenantId: 'tenant-123' },
|
|
835
|
+
};
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
- Policies are **versioned** and **audit-traceable** per tenant.
|
|
839
|
+
- Chainable evaluation ensures **who read/triggered each policy** is recorded.
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
## 5. Tenant-Specific Auth Methods
|
|
844
|
+
|
|
845
|
+
- Methods can be **enabled or disabled per tenant**.
|
|
846
|
+
- Registration API supports tenant-scoping:
|
|
847
|
+
|
|
848
|
+
```ts
|
|
849
|
+
tenantRegistry.register('tenant-123', passwordlessMethod);
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
- Core domain sees only abstract `IAuthMethod`.
|
|
853
|
+
- Tree-shakable: uninstalled methods do not affect bundle size.
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
857
|
+
## 6. Tenant-Specific Credential Configuration
|
|
858
|
+
|
|
859
|
+
- Credential lifecycles can be configured per tenant:
|
|
860
|
+
- Expiration time
|
|
861
|
+
- Rotation rules
|
|
862
|
+
- Step-up requirements
|
|
863
|
+
|
|
864
|
+
- Credentials remain domain-agnostic; tenant-specific behavior enforced via
|
|
865
|
+
policies and adapters.
|
|
866
|
+
|
|
867
|
+
---
|
|
868
|
+
|
|
869
|
+
## 7. Tenant Context in AuthenticationAttempt
|
|
870
|
+
|
|
871
|
+
- Attach `tenantId` to each attempt:
|
|
872
|
+
|
|
873
|
+
```ts
|
|
874
|
+
const attempt = AuthenticationAttempt.create({
|
|
875
|
+
flowId: 'enterprise_passwordless',
|
|
876
|
+
principalId: 'user-456',
|
|
877
|
+
tenantId: 'tenant-123',
|
|
878
|
+
});
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
- Ensures **flow, policies, session, and proofs** are evaluated within tenant
|
|
882
|
+
scope.
|
|
883
|
+
- Supports **audit and traceability** across multi-tenant systems.
|
|
884
|
+
|
|
885
|
+
---
|
|
886
|
+
|
|
887
|
+
## 8. Tenant Context in AuthenticationSession
|
|
888
|
+
|
|
889
|
+
- Session includes tenant metadata:
|
|
890
|
+
|
|
891
|
+
```ts
|
|
892
|
+
const session = AuthenticationSession.createFromAttempt(attempt, {
|
|
893
|
+
trustLevel: 'Medium',
|
|
894
|
+
tenantContext: { tenantId: 'tenant-123' },
|
|
895
|
+
});
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
- Downstream authorization can enforce **tenant isolation**.
|
|
899
|
+
- Chainable: developers can trace session → attempt → tenant.
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## 9. Extensibility & Overrides
|
|
904
|
+
|
|
905
|
+
- Tenants can:
|
|
906
|
+
- Add custom flows
|
|
907
|
+
- Override default policies
|
|
908
|
+
- Enable/disable auth methods
|
|
909
|
+
- Adjust trust-level rules
|
|
910
|
+
|
|
911
|
+
- Core domain is **unchanged**.
|
|
912
|
+
- Chainable documentation ensures each override is traceable.
|
|
913
|
+
|
|
914
|
+
---
|
|
915
|
+
|
|
916
|
+
## 10. Multi-Tenant Repository Pattern
|
|
917
|
+
|
|
918
|
+
```ts
|
|
919
|
+
interface TenantAwareRepository<T> {
|
|
920
|
+
findByTenant(tenantId: string, id: string): Promise<T | null>;
|
|
921
|
+
saveForTenant(tenantId: string, entity: T): Promise<void>;
|
|
922
|
+
}
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
- Ensures **tenant isolation at domain level**.
|
|
926
|
+
- Infrastructure handles actual storage, domain only sees tenant-scoped
|
|
927
|
+
interfaces.
|
|
928
|
+
|
|
929
|
+
---
|
|
930
|
+
|
|
931
|
+
## 11. Audit and Traceability
|
|
932
|
+
|
|
933
|
+
- Every domain event, policy evaluation, session creation, or flow execution:
|
|
934
|
+
- Includes `tenantId`
|
|
935
|
+
- Includes `principalId`
|
|
936
|
+
- Optional metadata for chainable docs or code references
|
|
937
|
+
|
|
938
|
+
- Developers reading code can follow **exact tenant-specific logic**.
|
|
939
|
+
|
|
940
|
+
---
|
|
941
|
+
|
|
942
|
+
## 12. Step 11 Status
|
|
943
|
+
|
|
944
|
+
✅ Multi-tenant support defined ✅ Tenant-scoped flows, policies, auth methods,
|
|
945
|
+
sessions, attempts ✅ Chainable, traceable for audit ✅ Core domain remains
|
|
946
|
+
tenant-agnostic ✅ Enterprise-ready, tree-shakable, modular
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
# Step 12 — Additional AuthMethod Implementations (Abstract)
|
|
951
|
+
|
|
952
|
+
> Goal: Provide modular contracts and structure for common auth methods beyond
|
|
953
|
+
> Passwordless, ensuring traceability, enterprise readiness, and minimal bundle
|
|
954
|
+
> size.
|
|
955
|
+
|
|
956
|
+
---
|
|
957
|
+
|
|
958
|
+
## 1. Module Layout
|
|
959
|
+
|
|
960
|
+
```
|
|
961
|
+
@auth/method-otp
|
|
962
|
+
@auth/method-oauth
|
|
963
|
+
@auth/method-passkey
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
- Each package is **optional**.
|
|
967
|
+
- Depends only on:
|
|
968
|
+
- `@auth/core-domain`
|
|
969
|
+
- `@auth/core-ports`
|
|
970
|
+
|
|
971
|
+
- Does **not depend** on other methods or adapters.
|
|
972
|
+
|
|
973
|
+
---
|
|
974
|
+
|
|
975
|
+
## 2. OTP Method (Abstract)
|
|
976
|
+
|
|
977
|
+
### Purpose
|
|
978
|
+
|
|
979
|
+
- One-time codes delivered via email, SMS, or push.
|
|
980
|
+
- Supports **step-up, MFA, or primary auth**.
|
|
981
|
+
|
|
982
|
+
### SPI Skeleton
|
|
983
|
+
|
|
984
|
+
```ts
|
|
985
|
+
export interface IOtpMethod extends IAuthMethod<
|
|
986
|
+
{ destination: string },
|
|
987
|
+
AuthProof
|
|
988
|
+
> {
|
|
989
|
+
/**
|
|
990
|
+
* Generate and issue OTP challenge
|
|
991
|
+
*/
|
|
992
|
+
issueChallenge(
|
|
993
|
+
context: AuthContext,
|
|
994
|
+
options?: { length?: number; ttl?: number },
|
|
995
|
+
): Promise<Challenge>;
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Verify OTP response
|
|
999
|
+
*/
|
|
1000
|
+
verifyChallenge(
|
|
1001
|
+
response: ChallengeResponse,
|
|
1002
|
+
context: AuthContext,
|
|
1003
|
+
): Promise<AuthProof>;
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
**Documentation Notes:**
|
|
1008
|
+
|
|
1009
|
+
- `destination` is transport-agnostic.
|
|
1010
|
+
- Challenge and proof remain domain-pure.
|
|
1011
|
+
- Developers can trace OTP flow from attempt → challenge → proof → session.
|
|
1012
|
+
|
|
1013
|
+
---
|
|
1014
|
+
|
|
1015
|
+
## 3. OAuth Method (Abstract)
|
|
1016
|
+
|
|
1017
|
+
### Purpose
|
|
1018
|
+
|
|
1019
|
+
- Delegated authentication via third-party providers (Google, Azure, etc.)
|
|
1020
|
+
- Supports SSO and enterprise logins.
|
|
1021
|
+
|
|
1022
|
+
### SPI Skeleton
|
|
1023
|
+
|
|
1024
|
+
```ts
|
|
1025
|
+
export interface IOAuthMethod extends IAuthMethod<
|
|
1026
|
+
{ code: string; provider: string },
|
|
1027
|
+
AuthProof
|
|
1028
|
+
> {
|
|
1029
|
+
/**
|
|
1030
|
+
* Initiate OAuth authorization
|
|
1031
|
+
*/
|
|
1032
|
+
initiateAuth(context: AuthContext, provider: string): Promise<string>; // URL to redirect
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Complete OAuth authentication with provider code
|
|
1036
|
+
*/
|
|
1037
|
+
completeAuth(code: string, context: AuthContext): Promise<AuthProof>;
|
|
1038
|
+
}
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
**Documentation Notes:**
|
|
1042
|
+
|
|
1043
|
+
- Domain sees only `AuthProof`.
|
|
1044
|
+
- No HTTP or token parsing in domain.
|
|
1045
|
+
- Chainable: principal → attempt → proof → session → policy evaluation.
|
|
1046
|
+
|
|
1047
|
+
---
|
|
1048
|
+
|
|
1049
|
+
## 4. Passkey / WebAuthn Method (Abstract)
|
|
1050
|
+
|
|
1051
|
+
### Purpose
|
|
1052
|
+
|
|
1053
|
+
- Hardware-backed or platform credentials (biometrics, security keys)
|
|
1054
|
+
- Supports phishing-resistant enterprise login
|
|
1055
|
+
|
|
1056
|
+
### SPI Skeleton
|
|
1057
|
+
|
|
1058
|
+
```ts
|
|
1059
|
+
export interface IPasskeyMethod extends IAuthMethod<
|
|
1060
|
+
{ clientData: unknown },
|
|
1061
|
+
AuthProof
|
|
1062
|
+
> {
|
|
1063
|
+
/**
|
|
1064
|
+
* Initiate passkey registration or authentication
|
|
1065
|
+
*/
|
|
1066
|
+
initiate(
|
|
1067
|
+
context: AuthContext,
|
|
1068
|
+
options?: { type: 'register' | 'authenticate' },
|
|
1069
|
+
): Promise<Challenge>;
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Verify passkey response
|
|
1073
|
+
*/
|
|
1074
|
+
verify(response: ChallengeResponse, context: AuthContext): Promise<AuthProof>;
|
|
1075
|
+
}
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
**Documentation Notes:**
|
|
1079
|
+
|
|
1080
|
+
- `clientData` is abstracted; adapter handles platform specifics.
|
|
1081
|
+
- Challenge/proof remain domain-pure.
|
|
1082
|
+
- Developers can trace registration/authentication per attempt.
|
|
1083
|
+
|
|
1084
|
+
---
|
|
1085
|
+
|
|
1086
|
+
## 5. Common Principles Across Methods
|
|
1087
|
+
|
|
1088
|
+
- Tree-shakable: install only the methods you need.
|
|
1089
|
+
- Domain-agnostic: all proofs and challenges are abstractions.
|
|
1090
|
+
- Chainable: attempts, proofs, sessions, and policies are auditable.
|
|
1091
|
+
- Extensible: adding new methods requires only a new package implementing
|
|
1092
|
+
`IAuthMethod`.
|
|
1093
|
+
|
|
1094
|
+
---
|
|
1095
|
+
|
|
1096
|
+
## 6. Method Registration (Example)
|
|
1097
|
+
|
|
1098
|
+
```ts
|
|
1099
|
+
const registry = new AuthMethodRegistry();
|
|
1100
|
+
registry.register(otpMethod); // @auth/method-otp
|
|
1101
|
+
registry.register(oauthMethod); // @auth/method-oauth
|
|
1102
|
+
registry.register(passkeyMethod); // @auth/method-passkey
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
- Explicit registration ensures **tree-shakability**.
|
|
1106
|
+
- Developers can trace **which methods are enabled per tenant or flow**.
|
|
1107
|
+
|
|
1108
|
+
---
|
|
1109
|
+
|
|
1110
|
+
## 7. Step 12 Status
|
|
1111
|
+
|
|
1112
|
+
✅ Additional auth methods abstracted ✅ OTP, OAuth, Passkey supported ✅
|
|
1113
|
+
Modular, optional, installable separately ✅ Traceable chain from attempt →
|
|
1114
|
+
proof → session → policy ✅ Enterprise-ready, extensible, tree-shakable
|
|
1115
|
+
|
|
1116
|
+
---
|
|
1117
|
+
|
|
1118
|
+
# Step 13 — Adapter Implementation Guides
|
|
1119
|
+
|
|
1120
|
+
> Goal: Provide a **modular, optional, and tree-shakable** adapter structure for
|
|
1121
|
+
> storage, token management, and transport layers.
|
|
1122
|
+
|
|
1123
|
+
---
|
|
1124
|
+
|
|
1125
|
+
## 1. Adapter Principles
|
|
1126
|
+
|
|
1127
|
+
- Implements **ports defined in `@auth/core-ports`**.
|
|
1128
|
+
- Tree-shakable: install only what you need.
|
|
1129
|
+
- Fully replaceable: e.g., Postgres ↔ Redis ↔ Memory.
|
|
1130
|
+
- Modular: one adapter per concern.
|
|
1131
|
+
- Traceable: every adapter operation can emit domain events or logs for
|
|
1132
|
+
auditing.
|
|
1133
|
+
|
|
1134
|
+
---
|
|
1135
|
+
|
|
1136
|
+
## 2. Adapter Package Layout
|
|
1137
|
+
|
|
1138
|
+
```
|
|
1139
|
+
@auth/adapter-memory ← in-memory demo & tests
|
|
1140
|
+
@auth/adapter-postgres ← persistence
|
|
1141
|
+
@auth/adapter-redis ← caching / OTP / session storage
|
|
1142
|
+
@auth/adapter-jwt ← token issuance / verification
|
|
1143
|
+
@auth/adapter-oidc ← external SSO / OIDC provider
|
|
1144
|
+
@auth/adapter-http-express ← HTTP integration
|
|
1145
|
+
@auth/adapter-http-hono ← alternative HTTP framework
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
- Each package is optional.
|
|
1149
|
+
- Depends only on `@auth/core-ports`.
|
|
1150
|
+
- No cross-dependencies.
|
|
1151
|
+
|
|
1152
|
+
---
|
|
1153
|
+
|
|
1154
|
+
## 3. Persistence Adapters
|
|
1155
|
+
|
|
1156
|
+
### 3.1 Credential Repository Adapter
|
|
1157
|
+
|
|
1158
|
+
```ts
|
|
1159
|
+
export interface CredentialRepositoryAdapter extends CredentialRepository {
|
|
1160
|
+
// Adapter-specific configuration (Postgres table, Redis hash, etc.)
|
|
1161
|
+
}
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
**Documentation Notes:**
|
|
1165
|
+
|
|
1166
|
+
- Must implement domain interface `CredentialRepository`.
|
|
1167
|
+
- Domain only sees abstract operations:
|
|
1168
|
+
- save()
|
|
1169
|
+
- findById()
|
|
1170
|
+
- revoke()
|
|
1171
|
+
|
|
1172
|
+
- Storage details hidden in adapter.
|
|
1173
|
+
- Traceable via adapter logs or emitted events.
|
|
1174
|
+
|
|
1175
|
+
---
|
|
1176
|
+
|
|
1177
|
+
### 3.2 Principal / Session / Attempt Repositories
|
|
1178
|
+
|
|
1179
|
+
- Same pattern: implement `PrincipalRepository`,
|
|
1180
|
+
`AuthenticationSessionRepository`, `AuthenticationAttemptRepository`.
|
|
1181
|
+
- Example adapter: `PostgresPrincipalRepositoryAdapter`.
|
|
1182
|
+
- Optional: emit domain events for auditing.
|
|
1183
|
+
|
|
1184
|
+
---
|
|
1185
|
+
|
|
1186
|
+
## 4. Token Adapters
|
|
1187
|
+
|
|
1188
|
+
### 4.1 JWT Adapter
|
|
1189
|
+
|
|
1190
|
+
```ts
|
|
1191
|
+
export interface JwtAdapter extends SessionRepresentationFactory {
|
|
1192
|
+
issue(session: AuthenticationSession, options?: JwtOptions): Promise<string>;
|
|
1193
|
+
verify(token: string): Promise<AuthProof>;
|
|
1194
|
+
}
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
- Purely optional.
|
|
1198
|
+
- Domain never depends on JWT format.
|
|
1199
|
+
- Traceable: JWT issuance linked to sessionId → tenantId.
|
|
1200
|
+
|
|
1201
|
+
---
|
|
1202
|
+
|
|
1203
|
+
### 4.2 OIDC / OAuth Adapter
|
|
1204
|
+
|
|
1205
|
+
```ts
|
|
1206
|
+
export interface OidcAdapter extends IOAuthMethod {
|
|
1207
|
+
initiateAuth(context: AuthContext, provider: string): Promise<string>;
|
|
1208
|
+
completeAuth(code: string, context: AuthContext): Promise<AuthProof>;
|
|
1209
|
+
}
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
- Adapter handles HTTP redirect, code exchange.
|
|
1213
|
+
- Domain sees only `AuthProof`.
|
|
1214
|
+
- Traceable through context and tenant metadata.
|
|
1215
|
+
|
|
1216
|
+
---
|
|
1217
|
+
|
|
1218
|
+
## 5. Caching / Temporary Storage Adapters
|
|
1219
|
+
|
|
1220
|
+
- OTP codes, challenge states, rate limiting.
|
|
1221
|
+
- Interface: implement `ChallengeStore` or `CredentialMaterialStore`.
|
|
1222
|
+
- Example: `RedisOtpAdapter`, `MemoryOtpAdapter`.
|
|
1223
|
+
- Modular: can swap infra without affecting domain.
|
|
1224
|
+
|
|
1225
|
+
---
|
|
1226
|
+
|
|
1227
|
+
## 6. HTTP Adapters
|
|
1228
|
+
|
|
1229
|
+
- Optional transport layer integration: Express, Hono, Fastify.
|
|
1230
|
+
- Implement endpoint wiring:
|
|
1231
|
+
- Receive request
|
|
1232
|
+
- Call registered auth method
|
|
1233
|
+
- Return proof/session
|
|
1234
|
+
|
|
1235
|
+
- Domain remains **transport-agnostic**.
|
|
1236
|
+
|
|
1237
|
+
---
|
|
1238
|
+
|
|
1239
|
+
## 7. Adapter Best Practices
|
|
1240
|
+
|
|
1241
|
+
1. **Install separately** — don’t bundle with core.
|
|
1242
|
+
2. **No domain logic** — only map ports → infra.
|
|
1243
|
+
3. **Emit events** for auditing / tracing.
|
|
1244
|
+
4. **Configurable per tenant** — adapter can read tenant-specific metadata.
|
|
1245
|
+
5. **Tree-shakable** — unused adapters never included in bundle.
|
|
1246
|
+
6. **Traceable code links** — each adapter operation references domain events or
|
|
1247
|
+
JSDoc for auditing.
|
|
1248
|
+
|
|
1249
|
+
---
|
|
1250
|
+
|
|
1251
|
+
## 8. Adapter Documentation Strategy
|
|
1252
|
+
|
|
1253
|
+
- Each adapter includes:
|
|
1254
|
+
- JSDoc explaining mapping to ports
|
|
1255
|
+
- Tenant-aware behavior
|
|
1256
|
+
- Optional emitted events for traceability
|
|
1257
|
+
- Usage example (tree-shakable)
|
|
1258
|
+
|
|
1259
|
+
- Chainable: core-domain → port → adapter → infra action
|
|
1260
|
+
- Developers can trace **who read / executed** each operation.
|
|
1261
|
+
|
|
1262
|
+
---
|
|
1263
|
+
|
|
1264
|
+
## Step 13 Status
|
|
1265
|
+
|
|
1266
|
+
✅ Adapter types and patterns defined ✅ Storage, caching, tokens, HTTP, OIDC,
|
|
1267
|
+
JWT covered ✅ Modular, tree-shakable, optional ✅ Traceable chain from domain →
|
|
1268
|
+
port → adapter ✅ Enterprise-ready and multi-tenant safe
|
|
1269
|
+
|
|
1270
|
+
---
|
|
1271
|
+
|
|
1272
|
+
# Step 14 — End-to-End Modular Integration Examples
|
|
1273
|
+
|
|
1274
|
+
> Goal: Show how to wire **core-domain, core-ports, methods, adapters, policies,
|
|
1275
|
+
> and multi-tenant context** together in a modular, traceable, and
|
|
1276
|
+
> enterprise-ready way.
|
|
1277
|
+
|
|
1278
|
+
---
|
|
1279
|
+
|
|
1280
|
+
## 1. Installable Modules
|
|
1281
|
+
|
|
1282
|
+
```
|
|
1283
|
+
@auth/core-domain
|
|
1284
|
+
@auth/core-ports
|
|
1285
|
+
@auth/method-passwordless
|
|
1286
|
+
@auth/method-otp
|
|
1287
|
+
@auth/method-oauth
|
|
1288
|
+
@auth/adapter-memory
|
|
1289
|
+
@auth/adapter-jwt
|
|
1290
|
+
```
|
|
1291
|
+
|
|
1292
|
+
- Each module optional
|
|
1293
|
+
- Installed only if needed → tree-shakable
|
|
1294
|
+
|
|
1295
|
+
---
|
|
1296
|
+
|
|
1297
|
+
## 2. Tenant Setup
|
|
1298
|
+
|
|
1299
|
+
```ts
|
|
1300
|
+
const tenant: TenantContext = { tenantId: 'tenant-123', name: 'Acme Corp' };
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
- All subsequent flows, policies, and sessions are **scoped to tenant**
|
|
1304
|
+
- Chainable: attempt → session → policy → tenant
|
|
1305
|
+
|
|
1306
|
+
---
|
|
1307
|
+
|
|
1308
|
+
## 3. AuthMethod Registration per Tenant
|
|
1309
|
+
|
|
1310
|
+
```ts
|
|
1311
|
+
const registry = new AuthMethodRegistry();
|
|
1312
|
+
|
|
1313
|
+
registry.registerForTenant(tenant.tenantId, passwordlessMethod);
|
|
1314
|
+
registry.registerForTenant(tenant.tenantId, otpMethod);
|
|
1315
|
+
registry.registerForTenant(tenant.tenantId, oauthMethod);
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
- Tree-shakable: unused methods not included
|
|
1319
|
+
- Traceable: registry logs tenant + methodType
|
|
1320
|
+
|
|
1321
|
+
---
|
|
1322
|
+
|
|
1323
|
+
## 4. Authentication Flow Setup
|
|
1324
|
+
|
|
1325
|
+
```ts
|
|
1326
|
+
const flow = new AuthenticationFlow({
|
|
1327
|
+
flowId: 'enterprise_flow',
|
|
1328
|
+
tenantId: tenant.tenantId,
|
|
1329
|
+
name: 'Enterprise Multi-Method Flow',
|
|
1330
|
+
steps: [
|
|
1331
|
+
{
|
|
1332
|
+
stepId: 'passwordless_step',
|
|
1333
|
+
authMethodType: 'passwordless_email',
|
|
1334
|
+
required: true,
|
|
1335
|
+
},
|
|
1336
|
+
{ stepId: 'otp_step', authMethodType: 'otp', required: false },
|
|
1337
|
+
],
|
|
1338
|
+
});
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
- Steps refer to registered methods
|
|
1342
|
+
- Flow scoped per tenant
|
|
1343
|
+
- Chainable: each step → proof → policy evaluation
|
|
1344
|
+
|
|
1345
|
+
---
|
|
1346
|
+
|
|
1347
|
+
## 5. Policy Setup per Tenant
|
|
1348
|
+
|
|
1349
|
+
```ts
|
|
1350
|
+
const policy: AuthenticationPolicy = {
|
|
1351
|
+
policyId: 'policy-tenant-001',
|
|
1352
|
+
name: 'Step-Up for High-Risk Logins',
|
|
1353
|
+
scope: 'Principal',
|
|
1354
|
+
rules: [
|
|
1355
|
+
{
|
|
1356
|
+
condition: { subject: 'risk.score', operator: 'greaterThan', value: 70 },
|
|
1357
|
+
action: { type: 'RequireStepUp', requiredFactors: ['otp'] },
|
|
1358
|
+
reason: 'High-risk logins require OTP',
|
|
1359
|
+
},
|
|
1360
|
+
],
|
|
1361
|
+
metadata: { tenantId: tenant.tenantId },
|
|
1362
|
+
};
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
- Policies applied **before or after each step**
|
|
1366
|
+
- Chainable evaluation logged for traceability
|
|
1367
|
+
|
|
1368
|
+
---
|
|
1369
|
+
|
|
1370
|
+
## 6. Authentication Attempt & Execution
|
|
1371
|
+
|
|
1372
|
+
```ts
|
|
1373
|
+
const principal = Principal.create({
|
|
1374
|
+
id: 'user-001',
|
|
1375
|
+
tenantId: tenant.tenantId,
|
|
1376
|
+
});
|
|
1377
|
+
const attempt = AuthenticationAttempt.create({
|
|
1378
|
+
flowId: flow.flowId,
|
|
1379
|
+
principalId: principal.id,
|
|
1380
|
+
tenantId: tenant.tenantId,
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
// Execute passwordless step
|
|
1384
|
+
const passwordlessProof = await passwordlessMethod.authenticate(
|
|
1385
|
+
{ email: 'user@example.com' },
|
|
1386
|
+
{ principalId: principal.id, attemptId: attempt.id },
|
|
1387
|
+
);
|
|
1388
|
+
|
|
1389
|
+
// Update attempt
|
|
1390
|
+
attempt.completeStep(passwordlessProof);
|
|
1391
|
+
|
|
1392
|
+
// Evaluate policy after step
|
|
1393
|
+
const policyResult = await policyEvaluator.evaluate(
|
|
1394
|
+
{ attempt, session: null, context: { tenantId: tenant.tenantId } },
|
|
1395
|
+
[policy],
|
|
1396
|
+
);
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
- Domain-only, infra-agnostic
|
|
1400
|
+
- Proof → session → policy evaluation chain traceable
|
|
1401
|
+
- Developers can see **full execution path** for audit
|
|
1402
|
+
|
|
1403
|
+
---
|
|
1404
|
+
|
|
1405
|
+
## 7. Session Creation
|
|
1406
|
+
|
|
1407
|
+
```ts
|
|
1408
|
+
const session = AuthenticationSession.createFromAttempt(attempt, {
|
|
1409
|
+
trustLevel: 'Medium',
|
|
1410
|
+
tenantContext: tenant,
|
|
1411
|
+
});
|
|
1412
|
+
```
|
|
1413
|
+
|
|
1414
|
+
- Captures **tenant, principal, flow, steps, proofs**
|
|
1415
|
+
- Immutable ContextSnapshot stored
|
|
1416
|
+
- Domain-pure, transport-agnostic
|
|
1417
|
+
|
|
1418
|
+
---
|
|
1419
|
+
|
|
1420
|
+
## 8. Adapter Wiring Example
|
|
1421
|
+
|
|
1422
|
+
```ts
|
|
1423
|
+
const credentialRepo = new MemoryCredentialRepositoryAdapter();
|
|
1424
|
+
const sessionRepo = new MemoryAuthenticationSessionRepositoryAdapter();
|
|
1425
|
+
const jwtAdapter = new JwtAdapter({ secret: 'dummy' });
|
|
1426
|
+
|
|
1427
|
+
// Connect adapters to ports
|
|
1428
|
+
// Domain only interacts with repositories via core-ports interfaces
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
- Tree-shakable: only installed adapters included
|
|
1432
|
+
- Modular: can swap Postgres / Redis / JWT
|
|
1433
|
+
- Traceable: all actions reference tenant + attempt + session
|
|
1434
|
+
|
|
1435
|
+
---
|
|
1436
|
+
|
|
1437
|
+
## 9. Chainable Audit Trail
|
|
1438
|
+
|
|
1439
|
+
```
|
|
1440
|
+
Tenant -> Principal -> AuthenticationAttempt -> AuthMethod Step -> Proof -> Session -> Policy Evaluation -> Adapter Event
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
- Every step **documented with JSDoc**
|
|
1444
|
+
- Events or logs link **who executed / evaluated**
|
|
1445
|
+
- Developers can trace **full end-to-end path** without touching infra
|
|
1446
|
+
|
|
1447
|
+
---
|
|
1448
|
+
|
|
1449
|
+
## 10. Developer Documentation Strategy
|
|
1450
|
+
|
|
1451
|
+
- Use **JSDoc** for:
|
|
1452
|
+
- Modules
|
|
1453
|
+
- SPI methods
|
|
1454
|
+
- Flows and steps
|
|
1455
|
+
- Policy evaluation
|
|
1456
|
+
- Tenant context
|
|
1457
|
+
- Adapter mapping
|
|
1458
|
+
|
|
1459
|
+
- Include `@link` to related domain objects
|
|
1460
|
+
- Chainable audit references included for every step
|
|
1461
|
+
|
|
1462
|
+
---
|
|
1463
|
+
|
|
1464
|
+
## Step 14 Status
|
|
1465
|
+
|
|
1466
|
+
✅ Full modular integration example complete ✅ Multi-tenant, multi-method,
|
|
1467
|
+
policy-driven ✅ Tree-shakable and modular ✅ Domain-only traceable chain ✅
|
|
1468
|
+
Enterprise-ready
|
|
1469
|
+
|
|
1470
|
+
---
|