diff --git a/PACKAGE_GENERATOR.md b/PACKAGE_GENERATOR.md index ff91a35..63b17b0 100644 --- a/PACKAGE_GENERATOR.md +++ b/PACKAGE_GENERATOR.md @@ -8,7 +8,7 @@ established structure and configuration standards. ### Option 1: Using npm script (Recommended) ```bash -pnpm generate:package [description] +pnpm g:pkg [description] ``` ### Option 2: Using Node.js directly @@ -28,19 +28,19 @@ node scripts/generate-package.mjs [description] ### Create a basic package ```bash -pnpm generate:package validation +pnpm g:pkg validation ``` ### Create a package with description ```bash -pnpm generate:package validation "Input validation utilities" +pnpm g:pkg validation "Input validation utilities" ``` ### Create a more complex package ```bash -pnpm generate:package cache-manager "Redis and in-memory cache management" +pnpm g:pkg cache-manager "Redis and in-memory cache management" ``` ## What Gets Generated diff --git a/README.md b/README.md index be57ba4..3edc495 100644 --- a/README.md +++ b/README.md @@ -107,21 +107,20 @@ For in-depth guidance, examples, and best practices, see the Objects, Entities, Aggregates, Events - πŸ”§ [Complete API Reference](./packages/ddd/README.md#api-reference) - All classes, interfaces, and methods -- πŸ’‘ [Real-World Examples](./packages/ddd/README.md#examples) - Full order - management system implementation -- βœ… [Best Practices](./packages/ddd/README.md#best-practices) - FAANG-level - patterns and principles -- 🚨 [Error Handling](./packages/ddd/README.md#error-handling) - Proper error - management strategies +- πŸ’‘ [Examples](./packages/ddd/README.md#examples) - Order aggregate and + integration patterns +- βœ… [Best Practices](./packages/ddd/README.md#best-practices) - DDD patterns + and principles +- 🚨 [Error Handling](./packages/ddd/README.md#error-handling) - Registry-backed + domain errors and Result - 🀝 [Contributing Guide](./packages/ddd/README.md#contributing) - Development setup and guidelines #### Included Value Objects - `AggregateId` - Unique identifier for aggregates +- `Email` - Email address validation - `IPAddress` - IPv4/IPv6 validation -- `URL` - Web URL validation -- `UserAgent` - User agent string parsing --- @@ -254,11 +253,14 @@ multiple authentication methods. - **Domain-Driven Design** - Built on `@rineex/ddd` for maintainable authentication logic -- **Extensible Architecture** - Support for OTP, passwordless, and social login - methods +- **Extensible Architecture** - OTP and passwordless method packages; + `AuthMethodPort` plugin model - **Type-Safe** - Full TypeScript support with strict typing - **Framework Agnostic** - Core domain logic independent of framework specifics +See [Architecture.md](./packages/authentication/core/Architecture.md) for what +is implemented vs in-tree. + For complete documentation, see the [@rineex/auth-core README](./packages/authentication/core/README.md). @@ -354,11 +356,8 @@ TypeScript compiler settings. β”‚ β”œβ”€β”€ pg-slonik/ # PostgreSQL adapter using Slonik β”‚ β”œβ”€β”€ libs/ # Utility libraries β”‚ β”œβ”€β”€ authentication/ # Authentication packages -β”‚ β”‚ β”œβ”€β”€ core/ # Core authentication abstractions -β”‚ β”‚ β”œβ”€β”€ methods/ # Authentication methods (OTP, passwordless) -β”‚ β”‚ β”œβ”€β”€ adapters/ # Framework adapters -β”‚ β”‚ β”œβ”€β”€ orchestration/ # Authentication orchestration -β”‚ β”‚ └── policies/ # Authentication policies +β”‚ β”‚ β”œβ”€β”€ core/ # @rineex/auth-core +β”‚ β”‚ └── methods/ # OTP, passwordless method packages β”‚ β”œβ”€β”€ nest/ # NestJS middleware modules β”‚ β”‚ β”œβ”€β”€ helmet-middleware-module/ β”‚ β”‚ β”œβ”€β”€ cors-middleware-module/ @@ -377,8 +376,8 @@ TypeScript compiler settings. ### Prerequisites -- **Node.js**: 18.0 or higher -- **pnpm**: 8.0 or higher (npm/yarn supported but not recommended) +- **Node.js**: 18.0 or higher (Volta pins Node 24 in this repo) +- **pnpm**: 10.0 or higher (workspace uses pnpm 10.22) - **TypeScript**: 5.9 or higher ### Getting Started @@ -489,6 +488,19 @@ pnpm g:pkg [description] For more details, see [PACKAGE_GENERATOR.md](./PACKAGE_GENERATOR.md). +## Documentation Map + +| Package / area | Primary docs | +| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Monorepo | [README.md](./README.md), [PACKAGE_GENERATOR.md](./PACKAGE_GENERATOR.md) | +| `@rineex/ddd` | [packages/ddd/README.md](./packages/ddd/README.md) | +| `@rineex/auth-core` | [packages/authentication/core/README.md](./packages/authentication/core/README.md), [Architecture.md](./packages/authentication/core/Architecture.md) | +| Auth methods | [passwordless README](./packages/authentication/methods/passwordless/README.md), [OTP README](./packages/authentication/methods/otp/README.md) | +| `@rineex/pg-slonik` | [README](./packages/pg-slonik/README.md), [API](./packages/pg-slonik/docs/API.md) | +| `@rineex/ioredis` | [README](./packages/ioredis/README.md) | +| Nest middleware | `packages/nest/*/README.md` | +| Domain sub-docs | [auth errors](./packages/authentication/core/src/domain/errors/README.md), [auth VOs](./packages/authentication/core/src/domain/value-objects/README.md) | + ## License This project is licensed under the Apache License 2.0 - see the diff --git a/packages/authentication/core/Architecture.md b/packages/authentication/core/Architecture.md index 109be09..6f93e3b 100644 --- a/packages/authentication/core/Architecture.md +++ b/packages/authentication/core/Architecture.md @@ -1,257 +1,164 @@ -# Auth Core β€” Domain Foundations (DDD + Hexagonal) +# Auth Core β€” Architecture (As Implemented) -## 1. Purpose +Framework-agnostic authentication core built on `@rineex/ddd`. This document +describes **what exists in code today**, not aspirational designs. For planned +features (Flow DSL, Principal/Credential model), see [FUTURE.md](./FUTURE.md). -Design a **framework-agnostic, storage-agnostic authentication core** that: +## Purpose -- Works with **zero customization** -- Supports **incremental auth methods** -- Is suitable for **enterprise and simple products** -- Is extensible without modifying existing domain code -- Is independent of HTTP, DB, tokens, providers, or frameworks +- Orchestrate authentication attempts via pluggable auth methods +- Enforce policy decisions before and during authentication +- Emit domain events for attempt lifecycle +- Stay independent of HTTP, databases, and token formats -This module defines **what authentication is**, not **how it is transported or -stored**. +## Packages ---- +| Package | Role | +| -------------------------------------------- | --------------------------------------------------------------- | +| `@rineex/auth-core` | Shared identity model, attempt aggregate, policy engine, ports | +| `@rineex/authentication-method-otp` | `AuthMethodPort` adapter for OTP | +| `@rineex/authentication-method-passwordless` | Standalone challenge issue/verify (not yet on `AuthMethodPort`) | -## 2. Architectural Constraints (Hard Rules) +## Bounded Context -These rules must never be violated: +**In scope:** verifying proof of access, attempt orchestration, policy +evaluation. -1. Authentication is **not identity** -2. Authentication methods are **plugins** -3. Authentication flows are **data-driven** -4. Infrastructure choices are **external** -5. Domain owns **rules and invariants only** -6. No provider, protocol, or framework knowledge in domain -7. No auth method may require changing existing aggregates +**Out of scope:** user profiles, authorization/permissions, HTTP, UI, concrete +token formats, database schemas. -If a new auth method breaks a rule β†’ architecture is wrong. +## Ubiquitous Language (Implemented) ---- +| Term | Meaning in code | +| ------------ | -------------------------------------------------------------------- | +| Identity | Actor that can authenticate (`Identity` entity, exported) | +| Auth Attempt | One authentication execution (`AuthenticationAttempt` aggregate) | +| Auth Method | Concrete mechanism (`AuthMethod` VO; `AuthMethodPort` for plugins) | +| Auth Factor | Category of proof (`AuthFactor` VO) | +| Auth Policy | Named policy slot (`AuthPolicy` VO; evaluated by `AuthPolicyEngine`) | +| Session | Post-auth continuity (`Session` entity β€” in-tree, not exported) | +| MFA Session | Step-up verification context (`MFASession` aggregate β€” in-tree) | -## 3. Bounded Context +**Not implemented:** Principal, Credential, Auth Proof, TrustLevel, Flow DSL. -### Authentication Context (this module) +> **Naming note:** Two types named `Identity` exist β€” an **entity** (exported) +> and an **aggregate** (in-tree, unexported). Prefer the entity in new code +> until the aggregate is published or renamed. -Responsibilities: +## Public Surface vs In-Tree -- Verifying proof of access -- Orchestrating authentication flows -- Enforcing policies -- Producing authenticated sessions +### Exported from `@rineex/auth-core` -Non-responsibilities: +- **Aggregate:** `AuthenticationAttempt` +- **Entity:** `Identity` +- **Value objects:** `AuthAttemptId`, `AuthFactor`, `AuthMethod`, `AuthPolicy`, + `AuthStatus`, `IdentityId`, `IdentityProvider`, `RiskSignal` +- **Events:** `AuthenticationStartedEvent`, `AuthenticationFailedEvent`, + `AuthenticationSucceededEvent` +- **Policy:** `AuthPolicyEvaluator`, `AuthPolicyContext`, `AuthPolicyDecision`, + `AuthPolicyEngine` +- **Ports:** `AuthMethodPort`, `AuthenticationAttemptRepositoryPort`, + `IdentityRepository`, `DomainEventPublisherPort` -- User profile management -- Authorization / permissions -- HTTP handling -- UI flows -- Token formats -- Database schemas +### In-tree (not exported) ---- +- Domains: MFA, OAuth, session, token +- Application services: flow start/verify, MFA start/issue/verify, OAuth + authorize (stub) +- Error registries and most domain errors +- Additional repository and observability ports -## 4. Ubiquitous Language (Canonical) +## Core Aggregates and Entities -These terms have **single meanings** and must not be overloaded: +### AuthenticationAttempt (exported aggregate) -| Term | Meaning | -| ------------ | ----------------------------------------------- | -| Principal | Any actor that can authenticate | -| Credential | Authentication material owned by a principal | -| Auth Factor | Category of proof (knowledge, possession, etc.) | -| Auth Method | Concrete mechanism (password, otp, oauth, …) | -| Auth Attempt | One authentication execution | -| Auth Flow | Orchestration rules | -| Auth Policy | Constraints and conditions | -| Auth Proof | Verifiable evidence | -| Auth Result | Outcome of authentication | -| Session | Post-auth continuity | +Lifecycle: `pending` β†’ `succeed` | `failed`. ---- +Methods: `start`, `fail`, `registerAttempt`, `succeed`. Emits started, failed, +succeeded events. -## 5. Core Domain Aggregates +### Identity (exported entity) -### 5.1 Principal (Aggregate Root) +Represents an authenticatable actor. Distinct from the unexported Identity +aggregate in `identity.aggregate.ts`. -Represents **who or what** is authenticating. +### Session (entity, unexported) -**Key rules** +Holds `identityId`, token reference, expiry, revocation. Not an aggregate root. -- Can exist without credentials -- Can represent humans, services, or devices -- Is auth-method agnostic +### MFASession, OauthAuthorization, Token (unexported) ---- +Domain models exist with application services partially implemented. Not on the +public package entry. -### 5.2 Credential (Entity) +## Auth Method Integration -Represents **what the principal owns** to authenticate. +### AuthMethodPort (OTP) -**Key rules** +```typescript +type AuthMethodPort = { + readonly method: AuthMethodName; + start(params: { + authAttemptId: AuthAttemptId; + ctx: unknown; + }): AuthMethodOutcome | Promise; + verify(params: { + authAttemptId: AuthAttemptId; + payload: unknown; + }): AuthMethodOutcome | Promise; +}; +``` -- Domain never stores secrets -- Lifecycle is domain-controlled -- Verification is delegated to infrastructure +`OtpAuthMethod` in `@rineex/authentication-method-otp` implements this port. ---- +### Passwordless (standalone) -### 5.3 AuthenticationAttempt (Aggregate Root) +`IssuePasswordlessChallengeService` and `VerifyPasswordlessChallengeService` +live in the passwordless package. They are **not** wired through +`AuthMethodPort` today. See passwordless package README. -Represents **one authentication process**. +## Policy Engine -**Why it exists** +`AuthPolicyEngine` runs registered `AuthPolicyEvaluator` instances. First deny +wins. Evaluators may return `requiresStepUp` for MFA escalation. No declarative +policy DSL β€” evaluators are code. -- Prevents replay -- Supports MFA and step-up -- Enables audit and risk evaluation +## Error Pattern ---- +Per bounded context: -### 5.4 AuthenticationSession (Aggregate Root) +1. Const registry: `AuthCoreErrorRegistry` with namespaces (`AUTH_CORE_MFA`, + etc.) +2. Error classes extend `DomainError<'NAMESPACE.CODE', Meta>` +3. Architecture tests verify every error code is registered -Represents **authenticated continuity**. +See [src/domain/errors/README.md](./src/domain/errors/README.md). -**Key rules** +## Layering -- Created only after successful authentication -- Stateless vs stateful is infrastructure choice -- Trust level is domain-owned +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Application (flow + MFA services) β”‚ unexported +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ Domain (attempt, identity, policy) β”‚ partially exported +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ Ports (inbound + outbound) β”‚ partially exported +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–² + β”‚ adapters (consumer-provided) +``` ---- +## Hard Rules (still apply) -## 6. Value Objects (Core Concepts) +1. Authentication is not identity management +2. Auth methods are plugins (`AuthMethodPort`) +3. Infrastructure stays outside domain +4. Domain owns invariants only +5. New methods must not require changing `AuthenticationAttempt` -Value Objects define meaning, not storage: +## Related Docs -- AuthMethodType -- AuthFactorType -- AuthProof -- TrustLevel -- RiskScore -- Challenge -- ContextSnapshot - -Auth factors are **categories**, not implementations: - -- Knowledge -- Possession -- Inherence -- Delegated - ---- - -## 7. Authentication Methods (Extensibility Model) - -Authentication methods are **not services** and **not branches**. - -They are **capabilities described by data**. - -An Auth Method defines: - -- Its identifier -- Supported factors -- Required inputs -- Output proof type - -The domain: - -- Does not know _how_ a method works -- Does not know _who_ provides it -- Does not change when a method is added - ---- - -## 8. Domain Services (Pure Logic) - -### Authentication Orchestrator - -Coordinates attempts, proofs, and policies. - -### Policy Evaluator - -Decides: - -- Whether authentication is allowed -- Whether step-up is required -- Which flow applies - -No domain service: - -- Talks to HTTP -- Knows about tokens -- Knows about databases - ---- - -## 9. Hexagonal Ports (Boundaries) - -### Persistence Ports - -- PrincipalRepository -- CredentialRepository -- AuthenticationAttemptRepository -- AuthenticationSessionRepository - -### Capability Ports - -- AuthProofVerifier -- ChallengeIssuer -- RiskEvaluator -- TokenIssuer - -Infrastructure implements ports. Domain only defines **contracts**. - ---- - -## 10. Phase 1 Auth Methods (Initial Scope) - -Start small, cover most use cases: - -1. Password (compatibility) -2. Passwordless (email magic) -3. OTP (TOTP + Email) -4. OAuth2 / OIDC -5. Social Login -6. API Tokens (M2M) - -Everything else is additive. - ---- - -## 11. Explicit Non-Goals (For Now) - -These are **intentionally excluded**: - -- HTTP redirects -- Cookies and headers -- JWT structure -- OAuth provider SDKs -- UI workflows -- Database schemas - -They belong to adapters, not the core. - ---- - -## 12. Extensibility Guarantees - -Before adding any auth feature, verify: - -- No existing aggregate changes -- No domain branching -- No infrastructure assumptions -- No HTTP dependency -- Enabled via configuration or policy - -Failing any β†’ redesign first. - ---- - -## 13. Document Status - -- This document is **authoritative** -- Any implementation must conform to it -- Changes require architectural justification +- [Definition.md](./Definition.md) β€” contracts and registries +- [RULES.md](./RULES.md) β€” package topology and port map +- [GAP_ANALYSIS.md](./GAP_ANALYSIS.md) β€” remaining work +- [ONBOARDING.md](./ONBOARDING.md) β€” contributor guide diff --git a/packages/authentication/core/Definition.md b/packages/authentication/core/Definition.md index 3242fde..fece4e0 100644 --- a/packages/authentication/core/Definition.md +++ b/packages/authentication/core/Definition.md @@ -1,1490 +1,125 @@ -## Step 1 β€” Define **Auth Method SPI (Plugin Contract)** +# Auth Core β€” Definition (As Implemented) -This is the **most critical step**. If this is wrong, everything after becomes -rigid. +Contracts, registries, and integration patterns that exist in code today. For +aspirational Flow DSL, proof system, and Principal/Credential model, see +[FUTURE.md](./FUTURE.md). ---- +## AuthMethodPort -# Auth Method SPI β€” Domain-Level Contract +All auth method modules should implement this inbound port: -## 1. Goal of This Step +| Method | Purpose | +| -------- | ------------------------------------------------------------------ | +| `start` | Initiate the method (send OTP, redirect to OAuth, issue challenge) | +| `verify` | Verify a response (OTP code, callback payload) | -Define **how authentication methods plug into the core** without: +Returns `AuthMethodOutcome` β€” `{ ok: true }` or +`{ ok: false, violation: UseCaseError }`. This is **not** `Result`; `Result` +is used at the application layer. -- Modifying domain logic -- Adding conditionals (`if password`, `if oauth`) -- Leaking protocol / provider / infra concerns -- Forcing storage or transport choices +**Implemented adapter:** `OtpAuthMethod` (`@rineex/authentication-method-otp`). -This SPI must support: +**Not yet adapted:** passwordless (standalone services only). -- Password -- Passwordless -- OTP -- OAuth / OIDC -- Social login -- Future methods (passkeys, DID, etc.) +## Flow Orchestration ---- +`StartAuthenticationFlowApplicationService` and +`VerifyAuthenticationFlowApplicationService` resolve a registered +`AuthMethodPort` and delegate to `start` / `verify` on an +`AuthenticationAttempt`. This is **not** a data-driven Flow DSL β€” it is direct +method dispatch. -## 2. What an Auth Method IS (and is NOT) +## Policy Evaluation -### βœ… IS +| Type | Role | +| --------------------- | ------------------------------------------------ | +| `AuthPolicyEvaluator` | Evaluates one policy; returns allow/deny/step-up | +| `AuthPolicyEngine` | Runs evaluators; first deny wins | +| `AuthPolicyContext` | Input snapshot for evaluation | +| `AuthPolicyDecision` | Outcome: allowed, denied, or requires step-up | -- A **capability descriptor** -- A **set of required inputs** -- A **type of proof produced** -- A **participant in a flow** +Policies are registered by name via `AuthPolicyRegistry` (currently `base` +only). -### ❌ IS NOT +## Type Registries -- A service with business logic -- A protocol implementation -- A provider SDK wrapper -- A controller or handler -- A persistence concern +Closed registries extended via module augmentation: -If logic creeps in here β†’ stop. +| Registry | Current values | +| -------------------------- | ---------------------------------------------------- | +| `AuthMethodRegistry` | `passwordless`, `otp` (augmented by method packages) | +| `AuthFactorRegistry` | `password` | +| `AuthPolicyRegistry` | `base` | +| `IdentityProviderRegistry` | (empty) | +| `RiskSignalRegistry` | (empty) | ---- +## Error Registry -## 3. Core Domain Abstractions +`AuthCoreErrorRegistry` namespaces: -### 3.1 AuthMethodType (Value Object) +- `AUTH_CORE_MFA` β€” challenge/session MFA errors +- `AUTH_CORE_OAUTH` β€” OAuth authorization errors +- `AUTH_CORE_IDENTITY` β€” identity state errors +- `AUTH_CORE_ATTEMPT` β€” attempt failures and not-found +- `AUTH_CORE_SESSION` β€” session invalid +- `AUTH_CORE_TOKEN` β€” token invalid +- `AUTH_CORE_SCOPE` β€” scope invalid -Represents **identity**, not behavior. +Pattern: -```ts -AuthMethodType -- value: string -``` - -Rules: - -- Stable identifier -- Configurable -- No enums hardcoded in domain - -Examples: - -- `password` -- `passwordless_email` -- `otp_totp` -- `oauth_oidc` -- `social_google` - ---- - -### 3.2 AuthMethodDefinition (Domain Object) - -This is the **plugin contract**. - -```ts -AuthMethodDefinition -- type: AuthMethodType -- supportedFactors: Set -- requiredInputs: Set -- outputProof: AuthProofType -- challengeCapable: boolean -``` - -This object: - -- Is immutable -- Is registered at startup -- Contains **no logic** - ---- - -## 4. Auth Inputs (What the method needs) - -Auth methods declare **what they need**, not _how it arrives_. - -```ts -AuthInputType - - identifier - - secret - - otp - - assertion - - authorization_code - - device_context - - redirect_context; -``` - -Rules: - -- Transport-agnostic -- Serializable -- Validated at boundaries - ---- - -## 5. Auth Proof (What the method produces) - -Auth methods **do not authenticate**. They produce **proof**. - -```ts -AuthProofType - password_proof - otp_proof - oauth_proof - assertion_proof; -``` - -The **domain decides** what to do with the proof. - ---- - -## 6. Verification Responsibility (Important) - -Auth Methods **do not verify themselves**. - -Verification is done via **ports**: - -```ts -AuthProofVerifierPort -- verify(proof, context): VerificationResult -``` - -This allows: - -- Swapping providers -- Mocking -- Enterprise overrides -- Multiple verification strategies - ---- - -## 7. Method Registration Model - -Auth methods are **registered**, not hardcoded. - -```ts -AuthMethodRegistry - register(definition) - get(type) - list(); -``` - -Rules: - -- Registry is read-only at runtime -- Domain depends only on interface -- Infrastructure provides implementations - ---- - -## 8. Example (Conceptual, Not Implementation) - -| Method | Inputs | Proof | Factor | -| ---------- | ------------------- | --------------- | ---------- | -| Password | identifier + secret | password_proof | knowledge | -| Magic Link | identifier | assertion_proof | possession | -| OTP | identifier + otp | otp_proof | possession | -| OAuth | authorization_code | oauth_proof | delegated | -| API Token | token | assertion_proof | possession | - -Notice: - -- No HTTP -- No redirects -- No JWT -- No providers - ---- - -## 9. What This Enables - -With this SPI, you can: - -- Add a new auth method **without touching domain logic** -- Compose methods into MFA -- Enable / disable methods via config -- Support enterprise overrides -- Keep core stable for years - ---- - -## 10. Hard Validation Checklist - -Before moving forward, confirm: - -- No logic inside method definitions -- No branching by method type -- No enum explosion -- No provider coupling -- No storage assumptions - -If any fails β†’ redesign now. - ---- - -## Step 1 Status - -βœ… Auth Method SPI defined βœ… Extensible βœ… Domain-pure βœ… Hexagonal-safe - ---- - -# Step 2 β€” Authentication Flow DSL (Orchestration Model) - -This step defines **how authentication happens**, without: - -- Hard-coding flows -- Coupling to methods -- Assuming HTTP redirects -- Embedding logic in code paths - -If flows are not modeled explicitly, you will **never** support enterprise auth -cleanly. - ---- - -## 1. Purpose of Authentication Flow - -An **Authentication Flow** answers one question: - -> _β€œIn what order, under what conditions, and with which rules are auth methods -> executed?”_ - -It must support: - -- Single-step auth (password, social) -- Multi-step auth (OTP, MFA) -- Conditional auth (risk-based) -- Enterprise SSO -- Future orchestration (passkeys, step-up) - ---- - -## 2. Core Principle (Non-Negotiable) - -**Flows are data, not code.** - -No: - -```ts -if (method === 'password') { ... } -``` - -Instead: - -- Declarative steps -- Explicit transitions -- Policy-driven decisions - ---- - -## 3. Flow as a Domain Concept - -### AuthenticationFlow (Domain Object) - -```ts -AuthenticationFlow -- flowId -- name -- steps: AuthFlowStep[] -- entryConditions -- exitConditions -``` - -Rules: - -- Immutable -- Versionable -- Serializable -- Loaded at startup or via config - ---- - -## 4. Flow Step Model - -### AuthFlowStep - -```ts -AuthFlowStep -- stepId -- authMethodType -- required: boolean -- onSuccess: Transition -- onFailure: Transition -- onChallenge?: Transition -``` - -Each step: - -- Executes **exactly one Auth Method** -- Produces a proof -- Does NOT decide success globally - ---- - -## 5. Transition Model - -### Transition - -```ts -Transition -- targetStepId | terminalState -- condition?: PolicyExpression -``` - -Terminal states: - -- `AUTHENTICATED` -- `FAILED` -- `CHALLENGED` - -This enables: - -- Retry -- Step-up -- Short-circuiting - ---- - -## 6. Policy Expressions (Referenced, Not Implemented Yet) - -Flows do **not** evaluate logic themselves. - -They reference policies: - -```ts -PolicyExpression - policyId - parameters; -``` - -Examples: - -- `risk.score > threshold` -- `principal.trustLevel < required` -- `device.notTrusted` - -(Policy language comes in Step 4.) - ---- - -## 7. AuthenticationAttempt Lifecycle (Flow-Driven) - -The flow **drives the attempt**, not the other way around. - -States: - -- `Initialized` -- `InProgress` -- `AwaitingChallenge` -- `Succeeded` -- `Failed` - -Rules: - -- One flow per attempt -- Steps are executed sequentially -- Proofs are accumulated -- Finalization happens only at terminal state - ---- - -## 8. Example Flows (Conceptual) - -### 8.1 Simple Password Flow - -``` -Step 1: password - onSuccess β†’ AUTHENTICATED - onFailure β†’ FAILED -``` - ---- - -### 8.2 Password + OTP (MFA) - -``` -Step 1: password - onSuccess β†’ Step 2 - onFailure β†’ FAILED - -Step 2: otp - onSuccess β†’ AUTHENTICATED - onFailure β†’ FAILED -``` - ---- - -### 8.3 Risk-Based Step-Up - -``` -Step 1: password - onSuccess β†’ - if risk.low β†’ AUTHENTICATED - if risk.high β†’ Step 2 - -Step 2: otp - onSuccess β†’ AUTHENTICATED - onFailure β†’ FAILED -``` - ---- - -### 8.4 OAuth / Social Login - -``` -Step 1: oauth_oidc - onSuccess β†’ AUTHENTICATED - onFailure β†’ FAILED - onChallenge β†’ AWAITING_EXTERNAL_ASSERTION -``` - -No redirects modeled. That’s infrastructure. - ---- - -## 9. What Flows Explicitly Do NOT Know - -Flows do **not** know about: - -- HTTP redirects -- UI screens -- Cookies -- Tokens -- OAuth providers -- Mobile vs web -- DB schemas - -They only know: - -- Steps -- Transitions -- Policies - ---- - -## 10. Extensibility Guarantees - -With this DSL, you can: - -- Add new auth methods without new flows -- Add new flows without code changes -- Customize enterprise flows via config -- Reuse flows across products -- Audit authentication behavior deterministically - ---- - -## 11. Validation Checklist (Must Pass) - -Before moving forward: - -βœ” Flow is declarative βœ” No branching by method type βœ” No protocol assumptions βœ” -No infrastructure knowledge βœ” Supports MFA and step-up βœ” Serializable and -versionable - -If any fails β†’ redesign. - ---- - -## Step 2 Status - -βœ… Authentication Flow DSL defined βœ… Orchestration decoupled from methods βœ… -Enterprise-ready βœ… Hexagonal-safe - ---- - -# Step 3 β€” **AuthenticationAttempt Lifecycle (Aggregate Rules)** - -This step defines **the heart of correctness**. If this aggregate is weak, -you’ll get replay attacks, broken MFA, and inconsistent auth state. - ---- - -## 1. Purpose of AuthenticationAttempt - -An **AuthenticationAttempt** represents: - -> _One and only one execution of an authentication flow._ - -It exists to: - -- Enforce invariants -- Track progress across steps -- Collect proofs -- Coordinate challenges -- Produce a single outcome - -It is the **source of truth** for authentication state. - ---- - -## 2. Why This Must Be an Aggregate Root - -Because it controls **critical invariants**: - -- A step cannot be skipped -- A proof cannot be reused -- An attempt cannot succeed twice -- A failed attempt cannot be resumed -- MFA ordering must be enforced - -If this is not an aggregate β†’ bugs are guaranteed. - ---- - -## 3. AuthenticationAttempt Structure (Conceptual) - -**Aggregate Root: AuthenticationAttempt** - -**Key Attributes** - -- AttemptId -- FlowId -- PrincipalId? (optional initially) -- CurrentStepId -- Status -- CollectedProofs -- StartedAt -- CompletedAt? - ---- - -## 4. Attempt States (Strict State Machine) - -Only these states are allowed: - -1. `Initialized` -2. `InProgress` -3. `AwaitingChallenge` -4. `Succeeded` -5. `Failed` - -No others. No β€œpartial success”. - ---- - -## 5. State Transition Rules (Non-Negotiable) - -### 5.1 Initialization - -- Created with a Flow -- No proofs -- No session -- Status = `Initialized` - -Allowed transitions: - -- β†’ `InProgress` - ---- - -### 5.2 InProgress - -- Actively executing a flow step -- Accepts exactly **one proof per step** - -Allowed transitions: - -- β†’ `AwaitingChallenge` -- β†’ `Succeeded` -- β†’ `Failed` - ---- - -### 5.3 AwaitingChallenge - -Used when: - -- External action is required -- OTP sent -- OAuth assertion pending -- Push approval pending - -Rules: - -- No new step allowed -- Only challenge response accepted - -Allowed transitions: - -- β†’ `InProgress` -- β†’ `Failed` - ---- - -### 5.4 Succeeded (Terminal) - -Rules: - -- Immutable -- Produces AuthenticationSession -- No further actions allowed - ---- - -### 5.5 Failed (Terminal) - -Rules: - -- Immutable -- No retry inside same attempt -- New attempt required - ---- - -## 6. Proof Handling Rules - -Proofs are **append-only**. - -Rules: - -- One proof per step -- Proof must match expected AuthMethodType -- Proof must be verified before progressing -- Proofs cannot be modified or deleted - -This guarantees: - -- Auditability -- Determinism -- Replay prevention - ---- - -## 7. Step Advancement Rules - -The aggregate enforces: - -- Steps must be executed in order -- Transition conditions must be satisfied -- Skipping steps is forbidden -- Optional steps are flow-defined, not attempt-defined - -The attempt **does not decide** what the next step is. It **asks the flow**. - ---- - -## 8. Failure Semantics (Important) - -Failures are **final**. - -Examples: - -- Wrong password β†’ Failed -- OTP expired β†’ Failed -- OAuth assertion invalid β†’ Failed - -Retries: - -- Require a **new AuthenticationAttempt** -- Rate limiting is policy/infrastructure concern - -This avoids: - -- State confusion -- Timing attacks -- Complex rollback logic - ---- - -## 9. Relationship to Other Aggregates - -- Uses **AuthenticationFlow** (read-only) -- Produces **AuthenticationSession** -- References **Principal**, but does not own it -- Uses **Policies**, but does not evaluate them - -No bidirectional coupling. - ---- - -## 10. What AuthenticationAttempt Does NOT Do - -Explicitly forbidden: - -- Creating credentials -- Issuing tokens -- Storing secrets -- Evaluating risk -- Talking to HTTP -- Calling providers - -It enforces **process**, not mechanics. - ---- - -## 11. Invariant Checklist (Must Always Hold) - -βœ” One flow per attempt βœ” One terminal state only βœ” No step skipping βœ” No proof -reuse βœ” No resurrection after failure βœ” Deterministic outcome - -If any invariant breaks β†’ security issue. - ---- - -## 12. Step 3 Status - -βœ… Aggregate boundaries defined βœ… State machine enforced βœ… MFA-safe βœ… -Replay-safe βœ… Audit-ready - ---- - -# Step 4 β€” **Authentication Policy Model (Risk, Conditions, Step-Up)** - -This step defines **who is allowed to authenticate, under what conditions, and -with which strength** β€” without hard-coding logic. - -If policies are not modeled cleanly, you will: - -- Hard-code MFA rules -- Fork enterprise behavior -- Break extensibility -- Lose auditability - ---- - -## 1. Purpose of Policy in Auth Domain - -A **Policy** answers questions like: - -- Is this authentication allowed? -- Is step-up required? -- Which flow should apply? -- Is this context risky? -- Is the current proof sufficient? - -Policies **do not authenticate**. They **constrain and steer** authentication. +```typescript +import { DomainError, InferErrorCodes, Metadata } from '@rineex/ddd'; ---- +export const AuthCoreErrorRegistry = { AUTH_CORE_TOKEN: ['INVALID'] } as const; +export type AuthCoreDomainErrorCode = InferErrorCodes< + typeof AuthCoreErrorRegistry +>; -## 2. Core Principle (Non-Negotiable) - -**Policies are declarative and evaluative β€” never procedural.** - -No: - -```ts -if (ip === 'x') requireOtp(); -``` - -Yes: - -- Policy definitions -- Policy evaluation results -- Policy-driven decisions - ---- - -## 3. Policy as a First-Class Domain Concept - -### AuthenticationPolicy (Domain Object) - -```ts -AuthenticationPolicy -- policyId -- name -- scope -- rules: PolicyRule[] -- effect -``` - -Rules: - -- Immutable -- Versionable -- Serializable -- Configurable per tenant / environment - ---- - -## 4. Policy Scope - -Policies apply at different levels: - -```ts -PolicyScope = - | Global - | Principal - | AuthMethod - | AuthFlow - | Resource -``` - -Examples: - -- Global MFA enforcement -- Principal-specific restrictions -- Method-specific constraints -- Flow selection rules - ---- - -## 5. Policy Rules (Atomic Conditions) - -### PolicyRule - -```ts -PolicyRule -- condition: ConditionExpression -- action: PolicyAction -``` - -Each rule: - -- Evaluates **one condition** -- Produces **one action** -- Has no side effects - ---- - -## 6. Condition Model (What can be checked) - -Conditions evaluate **context**, never infrastructure. - -```ts -ConditionExpression - subject - operator - value; -``` - -### Common Subjects - -- risk.score -- principal.trustLevel -- device.trusted -- location.country -- time.window -- auth.method -- auth.factor -- attempt.count - -### Operators - -- equals -- notEquals -- greaterThan -- lessThan -- in -- notIn - ---- - -## 7. Policy Actions (What can be enforced) - -Actions **do not perform authentication**. - -```ts -PolicyAction = - | Allow - | Deny - | RequireStepUp - | SelectFlow - | LimitTrustLevel -``` - -Examples: - -- Require OTP if risk > threshold -- Deny auth from blocked country -- Force enterprise SSO -- Downgrade session trust - ---- - -## 8. Policy Evaluation Result - -Policies return **decisions**, not behavior. - -```ts -PolicyEvaluationResult -- decision -- requiredFlow? -- requiredAuthFactors? -- maxTrustLevel? -- reasons[] -``` - -This allows: - -- Explainability -- Auditing -- Compliance - ---- - -## 9. Policy Evaluation Responsibility - -Policies are **evaluated by a Domain Service**, not entities. - -```ts -AuthenticationPolicyEvaluator -- evaluate(context): PolicyEvaluationResult -``` - -Context includes: - -- Principal snapshot -- AuthenticationAttempt snapshot -- Device/context snapshot -- Risk assessment (via port) - ---- - -## 10. Relationship with Authentication Flow - -Policies can: - -- Select a flow -- Alter transitions -- Require additional steps - -Flows **reference policies**, but do not contain logic. - -This separation allows: - -- Same flow, different behavior -- Enterprise overrides -- Runtime reconfiguration - ---- - -## 11. Example Policies (Conceptual) - -### 11.1 Risk-Based MFA - -``` -IF risk.score > 70 -THEN RequireStepUp (otp) -``` - ---- - -### 11.2 Geo Restriction - -``` -IF location.country IN blockedCountries -THEN Deny -``` - ---- - -### 11.3 Trust Downgrade - -``` -IF device.trusted = false -THEN LimitTrustLevel = LOW -``` - ---- - -### 11.4 Flow Selection - -``` -IF principal.type = Service -THEN SelectFlow = m2m_flow -``` - ---- - -## 12. What Policies Explicitly Do NOT Do - -Forbidden: - -- Issuing challenges -- Sending OTPs -- Creating sessions -- Calling providers -- Accessing databases directly -- Knowing HTTP headers - -They only **decide**. - ---- - -## 13. Extensibility Guarantees - -With this model you can: - -- Add new conditions without changing flows -- Add new actions without breaking domain -- Support enterprise policy engines later -- Audit decisions deterministically - ---- - -## 14. Step 4 Status - -βœ… Policy model defined βœ… Risk-adaptive ready βœ… Enterprise-grade βœ… -Explainable and auditable - ---- - -# Step 5 β€” **Credential Lifecycle Modeling** - -This step defines **what a credential is**, how it is created, validated, -rotated, revoked, and expired β€” without storing secrets or coupling to storage. - -If credential lifecycle is vague, security degrades fast. - ---- - -## 1. Purpose of Credential in Auth Domain - -A **Credential** represents: - -> _A verifiable authentication capability bound to a Principal._ - -It is **not**: - -- A password hash -- An OTP secret -- A token -- A provider artifact - -Those are **infrastructure details**. - ---- - -## 2. Credential as a Domain Entity - -### Credential (Entity) - -```ts -Credential -- CredentialId -- PrincipalId -- AuthMethodType -- AuthFactorType -- Status -- IssuedAt -- ExpiresAt? -- LastUsedAt? -- Metadata -``` - ---- - -## 3. Credential Status (Finite State) - -```ts -CredentialStatus = - | Active - | Suspended - | Revoked - | Expired - | Compromised -``` - -Rules: - -- Revoked and Compromised are terminal -- Expired is time-based -- Suspended is reversible - ---- - -## 4. Credential Invariants (Strict) - -A credential must always satisfy: - -- Belongs to exactly one Principal -- Is bound to exactly one Auth Method -- Cannot be reused across principals -- Cannot be reactivated after Revocation -- Cannot authenticate if not Active -- Cannot exist without lifecycle metadata - -Violation = security bug. - ---- - -## 5. Credential Creation Rules - -Credential creation: - -- Is explicit -- Requires policy approval -- Produces **no secret** in domain -- Delegates material generation to infrastructure - -Example: - -- Domain: β€œCreate OTP credential” -- Infra: Generates secret, stores it securely - ---- - -## 6. Credential Usage Rules - -On each successful authentication: - -- Credential status must be Active -- Expiration must be checked -- Usage timestamp may be updated -- Compromise signals may suspend or revoke - -Credential usage is **observed**, not enforced here. - ---- - -## 7. Credential Rotation & Replacement - -Rotation is modeled as: - -- Create new credential -- Activate new -- Suspend or revoke old - -No mutation-in-place. - -This allows: - -- Auditability -- Rollback -- Compliance - ---- - -## 8. Credential Revocation - -Revocation reasons: - -- User action -- Admin action -- Policy decision -- Risk detection -- Breach response - -Revocation: - -- Is immediate -- Is irreversible -- Must propagate to infra adapters - ---- - -## 9. Credential Expiration - -Expiration: - -- Is time-based -- Evaluated at authentication time -- Does not delete credential -- Transitions to Expired - -Deletion is **not** domain responsibility. - ---- - -## 10. Relationship to AuthenticationAttempt - -- Attempts **reference** credentials -- Attempts do not own credentials -- Credential state is checked during verification -- No attempt may mutate credential directly - ---- - -## 11. Credential Types (Examples) - -| Method | Credential Meaning | -| --------- | ---------------------- | -| Password | Knowledge credential | -| OTP | Possession credential | -| OAuth | Delegated credential | -| API Token | Possession credential | -| Passkey | Possession + inherence | - -Types are **data**, not inheritance. - ---- - -## 12. What Credential Does NOT Know - -Explicitly forbidden: - -- Hash algorithms -- OTP secrets -- Token formats -- Storage engines -- Providers -- Encryption - -Those live behind ports. - ---- - -## 13. Ports Related to Credentials - -```ts -CredentialRepository; -CredentialMaterialStore(infra); -CredentialUsageReporter; -CredentialRevocationPublisher; -``` - -Domain only depends on interfaces. - ---- - -## 14. Extensibility Guarantees - -With this model: - -- New credential types require no domain change -- Infra can store secrets however it wants -- Enterprise policies can control lifecycle -- Auditing is deterministic - ---- - -## 15. Step 5 Status - -βœ… Credential lifecycle defined βœ… Security invariants enforced βœ… -Storage-agnostic βœ… Enterprise-ready - ---- - -We continue. This is the **last foundational domain step**. - ---- - -# Step 6 β€” **AuthenticationSession & Trust Levels** - -This step defines **what β€œbeing authenticated” actually means** in your system, -independent of tokens, cookies, or transport. - -If this is weak, authorization, SSO, and enterprise security all collapse. - ---- - -## 1. Purpose of AuthenticationSession - -An **AuthenticationSession** represents: - -> _A time-bounded, trust-scoped result of a successful authentication._ - -It is: - -- The output of Authentication -- The input to Authorization -- The anchor for SSO, re-auth, and step-up - -It is **not**: - -- A cookie -- A JWT -- A refresh token -- An HTTP session - -Those are representations. - ---- - -## 2. AuthenticationSession as Aggregate Root - -### AuthenticationSession (Aggregate Root) - -```ts -AuthenticationSession - - SessionId - - PrincipalId - - FlowId - - IssuedAt - - ExpiresAt - - TrustLevel - - AuthFactorsUsed - - ContextSnapshot - - Status; -``` - ---- - -## 3. Session Status (Finite State) - -```ts -SessionStatus = - | Active - | Expired - | Revoked -``` - -Rules: - -- Expired is time-based -- Revoked is explicit and terminal -- Active is the only usable state - ---- - -## 4. Trust Level (Critical Concept) - -### TrustLevel (Value Object) - -Trust is **not binary**. - -```ts -TrustLevel = - | Anonymous - | Low - | Medium - | High +class InvalidAuthTokenError extends DomainError< + 'AUTH_CORE_TOKEN.INVALID', + Metadata<{ actualLength: number }> +> { + readonly code = 'AUTH_CORE_TOKEN.INVALID' as const; +} ``` -Interpretation: - -- **Anonymous** β†’ unauthenticated -- **Low** β†’ weak auth (magic link, social) -- **Medium** β†’ single strong factor -- **High** β†’ MFA / hardware-backed - -Trust is: - -- Computed from proofs + policies -- Stored in session -- Used by authorization - ---- - -## 5. Trust Level Invariants - -- TrustLevel is **assigned at session creation** -- TrustLevel can be **downgraded** -- TrustLevel can **never be upgraded in-place** -- Upgrade requires **new AuthenticationAttempt** - -This prevents silent privilege escalation. - ---- - -## 6. Relationship to AuthenticationAttempt - -- Exactly one session per successful attempt -- Attempt produces session -- Session references attempt context (read-only) -- Session lifecycle is independent afterward - -No circular dependency. - ---- - -## 7. Context Snapshot (Why It Exists) - -### ContextSnapshot (Value Object) - -Captures authentication context **at time of issuance**: - -- Device trust -- Location -- Risk score -- Auth methods used -- Policies applied - -Why: - -- Auditability -- Compliance -- Forensic analysis -- Future policy checks - -Context is **immutable**. - ---- - -## 8. Session Expiration Rules - -Expiration is: - -- Deterministic -- Policy-driven -- Evaluated by infrastructure -- Reflected in domain state - -Domain does not: - -- Refresh sessions -- Slide expiration -- Issue refresh tokens - -Those are adapters. - ---- - -## 9. Session Revocation - -Revocation triggers: - -- User logout -- Admin action -- Credential revocation -- Risk detection -- Policy violation - -Revocation: - -- Is explicit -- Is immediate -- Invalidates all representations - ---- - -## 10. SSO Compatibility (Important) - -SSO is simply: - -- Multiple representations -- One AuthenticationSession - -This model supports: - -- Web SSO -- Mobile SSO -- API SSO -- Cross-app SSO - -Without special logic. - ---- - -## 11. What AuthenticationSession Does NOT Know - -Explicitly forbidden: - -- JWT claims -- Cookies -- Headers -- OAuth tokens -- HTTP -- Storage engine - -It only knows **meaning**, not format. - ---- - -## 12. Ports Related to Sessions - -```ts -AuthenticationSessionRepository; -SessionRevocationPublisher; -SessionExpirationScheduler; -SessionRepresentationFactory(infra); -``` +## Authentication Attempt States -Domain defines contracts only. +| Status | Meaning | +| --------- | ------------------------ | +| `pending` | Attempt in progress | +| `succeed` | Authentication succeeded | +| `failed` | Authentication failed | ---- +## Outbound Ports (exported) -## 13. Extensibility Guarantees +| Port | Responsibility | +| ------------------------------------- | --------------------- | +| `AuthenticationAttemptRepositoryPort` | Persist attempts | +| `IdentityRepository` | Load identities | +| `DomainEventPublisherPort` | Publish domain events | -With this model: +## Outbound Ports (in-tree) -- You can change token formats freely -- You can support SSO without refactor -- You can add step-up auth cleanly -- You can audit auth decisions years later +`SessionRepositoryPort`, `TokenRepository`, `MfaSessionRepository`, +`MfaSessionIdGenerator`, `MfaClock`, `LoggerPort`, `ObservabilityEventPort`. ---- +## MFA Application Services (in-tree) -## 14. Step 6 Status +- `StartMfaSessionApplicationService` +- `IssueMfaChallengeApplicationService` +- `VerifyMfaApplicationService` -βœ… AuthenticationSession modeled βœ… Trust levels explicit βœ… Step-up safe βœ… -Authorization-ready βœ… Enterprise-grade +## OAuth (in-tree, incomplete) ---- +Domain model for OAuth authorization with PKCE value objects exists. +`OAuthAuthorizeService` is a stub. -# 🎯 Foundation Complete +## Passwordless Package -You now have a **complete, extensible, production-grade Auth Domain**: +Separate package with its own aggregate, error registry, and issue/verify +services. See +[methods/passwordless/README.md](../methods/passwordless/README.md). -1. Auth Method SPI -2. Authentication Flow DSL -3. AuthenticationAttempt lifecycle -4. Policy model -5. Credential lifecycle -6. AuthenticationSession & Trust +**Known gap:** package `index.ts` does not export runtime API β€” only type +augmentation. See [GAP_ANALYSIS.md](./GAP_ANALYSIS.md). diff --git a/packages/authentication/core/FUTURE.md b/packages/authentication/core/FUTURE.md new file mode 100644 index 0000000..0146999 --- /dev/null +++ b/packages/authentication/core/FUTURE.md @@ -0,0 +1,1495 @@ +# Future Work β€” Aspirational Auth Design + +> Preserved from the original Definition.md. This describes target-state +> features not yet implemented. See Definition.md for what exists today. + +## Step 1 β€” Define **Auth Method SPI (Plugin Contract)** + +This is the **most critical step**. If this is wrong, everything after becomes +rigid. + +--- + +# Auth Method SPI β€” Domain-Level Contract + +## 1. Goal of This Step + +Define **how authentication methods plug into the core** without: + +- Modifying domain logic +- Adding conditionals (`if password`, `if oauth`) +- Leaking protocol / provider / infra concerns +- Forcing storage or transport choices + +This SPI must support: + +- Password +- Passwordless +- OTP +- OAuth / OIDC +- Social login +- Future methods (passkeys, DID, etc.) + +--- + +## 2. What an Auth Method IS (and is NOT) + +### βœ… IS + +- A **capability descriptor** +- A **set of required inputs** +- A **type of proof produced** +- A **participant in a flow** + +### ❌ IS NOT + +- A service with business logic +- A protocol implementation +- A provider SDK wrapper +- A controller or handler +- A persistence concern + +If logic creeps in here β†’ stop. + +--- + +## 3. Core Domain Abstractions + +### 3.1 AuthMethodType (Value Object) + +Represents **identity**, not behavior. + +```ts +AuthMethodType +- value: string +``` + +Rules: + +- Stable identifier +- Configurable +- No enums hardcoded in domain + +Examples: + +- `password` +- `passwordless_email` +- `otp_totp` +- `oauth_oidc` +- `social_google` + +--- + +### 3.2 AuthMethodDefinition (Domain Object) + +This is the **plugin contract**. + +```ts +AuthMethodDefinition +- type: AuthMethodType +- supportedFactors: Set +- requiredInputs: Set +- outputProof: AuthProofType +- challengeCapable: boolean +``` + +This object: + +- Is immutable +- Is registered at startup +- Contains **no logic** + +--- + +## 4. Auth Inputs (What the method needs) + +Auth methods declare **what they need**, not _how it arrives_. + +```ts +AuthInputType - + identifier - + secret - + otp - + assertion - + authorization_code - + device_context - + redirect_context; +``` + +Rules: + +- Transport-agnostic +- Serializable +- Validated at boundaries + +--- + +## 5. Auth Proof (What the method produces) + +Auth methods **do not authenticate**. They produce **proof**. + +```ts +AuthProofType - password_proof - otp_proof - oauth_proof - assertion_proof; +``` + +The **domain decides** what to do with the proof. + +--- + +## 6. Verification Responsibility (Important) + +Auth Methods **do not verify themselves**. + +Verification is done via **ports**: + +```ts +AuthProofVerifierPort +- verify(proof, context): VerificationResult +``` + +This allows: + +- Swapping providers +- Mocking +- Enterprise overrides +- Multiple verification strategies + +--- + +## 7. Method Registration Model + +Auth methods are **registered**, not hardcoded. + +```ts +AuthMethodRegistry - register(definition) - get(type) - list(); +``` + +Rules: + +- Registry is read-only at runtime +- Domain depends only on interface +- Infrastructure provides implementations + +--- + +## 8. Example (Conceptual, Not Implementation) + +| Method | Inputs | Proof | Factor | +| ---------- | ------------------- | --------------- | ---------- | +| Password | identifier + secret | password_proof | knowledge | +| Magic Link | identifier | assertion_proof | possession | +| OTP | identifier + otp | otp_proof | possession | +| OAuth | authorization_code | oauth_proof | delegated | +| API Token | token | assertion_proof | possession | + +Notice: + +- No HTTP +- No redirects +- No JWT +- No providers + +--- + +## 9. What This Enables + +With this SPI, you can: + +- Add a new auth method **without touching domain logic** +- Compose methods into MFA +- Enable / disable methods via config +- Support enterprise overrides +- Keep core stable for years + +--- + +## 10. Hard Validation Checklist + +Before moving forward, confirm: + +- No logic inside method definitions +- No branching by method type +- No enum explosion +- No provider coupling +- No storage assumptions + +If any fails β†’ redesign now. + +--- + +## Step 1 Status + +βœ… Auth Method SPI defined βœ… Extensible βœ… Domain-pure βœ… Hexagonal-safe + +--- + +# Step 2 β€” Authentication Flow DSL (Orchestration Model) + +This step defines **how authentication happens**, without: + +- Hard-coding flows +- Coupling to methods +- Assuming HTTP redirects +- Embedding logic in code paths + +If flows are not modeled explicitly, you will **never** support enterprise auth +cleanly. + +--- + +## 1. Purpose of Authentication Flow + +An **Authentication Flow** answers one question: + +> _β€œIn what order, under what conditions, and with which rules are auth methods +> executed?”_ + +It must support: + +- Single-step auth (password, social) +- Multi-step auth (OTP, MFA) +- Conditional auth (risk-based) +- Enterprise SSO +- Future orchestration (passkeys, step-up) + +--- + +## 2. Core Principle (Non-Negotiable) + +**Flows are data, not code.** + +No: + +```ts +if (method === 'password') { ... } +``` + +Instead: + +- Declarative steps +- Explicit transitions +- Policy-driven decisions + +--- + +## 3. Flow as a Domain Concept + +### AuthenticationFlow (Domain Object) + +```ts +AuthenticationFlow +- flowId +- name +- steps: AuthFlowStep[] +- entryConditions +- exitConditions +``` + +Rules: + +- Immutable +- Versionable +- Serializable +- Loaded at startup or via config + +--- + +## 4. Flow Step Model + +### AuthFlowStep + +```ts +AuthFlowStep +- stepId +- authMethodType +- required: boolean +- onSuccess: Transition +- onFailure: Transition +- onChallenge?: Transition +``` + +Each step: + +- Executes **exactly one Auth Method** +- Produces a proof +- Does NOT decide success globally + +--- + +## 5. Transition Model + +### Transition + +```ts +Transition +- targetStepId | terminalState +- condition?: PolicyExpression +``` + +Terminal states: + +- `AUTHENTICATED` +- `FAILED` +- `CHALLENGED` + +This enables: + +- Retry +- Step-up +- Short-circuiting + +--- + +## 6. Policy Expressions (Referenced, Not Implemented Yet) + +Flows do **not** evaluate logic themselves. + +They reference policies: + +```ts +PolicyExpression - policyId - parameters; +``` + +Examples: + +- `risk.score > threshold` +- `principal.trustLevel < required` +- `device.notTrusted` + +(Policy language comes in Step 4.) + +--- + +## 7. AuthenticationAttempt Lifecycle (Flow-Driven) + +The flow **drives the attempt**, not the other way around. + +States: + +- `Initialized` +- `InProgress` +- `AwaitingChallenge` +- `Succeeded` +- `Failed` + +Rules: + +- One flow per attempt +- Steps are executed sequentially +- Proofs are accumulated +- Finalization happens only at terminal state + +--- + +## 8. Example Flows (Conceptual) + +### 8.1 Simple Password Flow + +``` +Step 1: password + onSuccess β†’ AUTHENTICATED + onFailure β†’ FAILED +``` + +--- + +### 8.2 Password + OTP (MFA) + +``` +Step 1: password + onSuccess β†’ Step 2 + onFailure β†’ FAILED + +Step 2: otp + onSuccess β†’ AUTHENTICATED + onFailure β†’ FAILED +``` + +--- + +### 8.3 Risk-Based Step-Up + +``` +Step 1: password + onSuccess β†’ + if risk.low β†’ AUTHENTICATED + if risk.high β†’ Step 2 + +Step 2: otp + onSuccess β†’ AUTHENTICATED + onFailure β†’ FAILED +``` + +--- + +### 8.4 OAuth / Social Login + +``` +Step 1: oauth_oidc + onSuccess β†’ AUTHENTICATED + onFailure β†’ FAILED + onChallenge β†’ AWAITING_EXTERNAL_ASSERTION +``` + +No redirects modeled. That’s infrastructure. + +--- + +## 9. What Flows Explicitly Do NOT Know + +Flows do **not** know about: + +- HTTP redirects +- UI screens +- Cookies +- Tokens +- OAuth providers +- Mobile vs web +- DB schemas + +They only know: + +- Steps +- Transitions +- Policies + +--- + +## 10. Extensibility Guarantees + +With this DSL, you can: + +- Add new auth methods without new flows +- Add new flows without code changes +- Customize enterprise flows via config +- Reuse flows across products +- Audit authentication behavior deterministically + +--- + +## 11. Validation Checklist (Must Pass) + +Before moving forward: + +βœ” Flow is declarative βœ” No branching by method type βœ” No protocol assumptions βœ” +No infrastructure knowledge βœ” Supports MFA and step-up βœ” Serializable and +versionable + +If any fails β†’ redesign. + +--- + +## Step 2 Status + +βœ… Authentication Flow DSL defined βœ… Orchestration decoupled from methods βœ… +Enterprise-ready βœ… Hexagonal-safe + +--- + +# Step 3 β€” **AuthenticationAttempt Lifecycle (Aggregate Rules)** + +This step defines **the heart of correctness**. If this aggregate is weak, +you’ll get replay attacks, broken MFA, and inconsistent auth state. + +--- + +## 1. Purpose of AuthenticationAttempt + +An **AuthenticationAttempt** represents: + +> _One and only one execution of an authentication flow._ + +It exists to: + +- Enforce invariants +- Track progress across steps +- Collect proofs +- Coordinate challenges +- Produce a single outcome + +It is the **source of truth** for authentication state. + +--- + +## 2. Why This Must Be an Aggregate Root + +Because it controls **critical invariants**: + +- A step cannot be skipped +- A proof cannot be reused +- An attempt cannot succeed twice +- A failed attempt cannot be resumed +- MFA ordering must be enforced + +If this is not an aggregate β†’ bugs are guaranteed. + +--- + +## 3. AuthenticationAttempt Structure (Conceptual) + +**Aggregate Root: AuthenticationAttempt** + +**Key Attributes** + +- AttemptId +- FlowId +- PrincipalId? (optional initially) +- CurrentStepId +- Status +- CollectedProofs +- StartedAt +- CompletedAt? + +--- + +## 4. Attempt States (Strict State Machine) + +Only these states are allowed: + +1. `Initialized` +2. `InProgress` +3. `AwaitingChallenge` +4. `Succeeded` +5. `Failed` + +No others. No β€œpartial success”. + +--- + +## 5. State Transition Rules (Non-Negotiable) + +### 5.1 Initialization + +- Created with a Flow +- No proofs +- No session +- Status = `Initialized` + +Allowed transitions: + +- β†’ `InProgress` + +--- + +### 5.2 InProgress + +- Actively executing a flow step +- Accepts exactly **one proof per step** + +Allowed transitions: + +- β†’ `AwaitingChallenge` +- β†’ `Succeeded` +- β†’ `Failed` + +--- + +### 5.3 AwaitingChallenge + +Used when: + +- External action is required +- OTP sent +- OAuth assertion pending +- Push approval pending + +Rules: + +- No new step allowed +- Only challenge response accepted + +Allowed transitions: + +- β†’ `InProgress` +- β†’ `Failed` + +--- + +### 5.4 Succeeded (Terminal) + +Rules: + +- Immutable +- Produces AuthenticationSession +- No further actions allowed + +--- + +### 5.5 Failed (Terminal) + +Rules: + +- Immutable +- No retry inside same attempt +- New attempt required + +--- + +## 6. Proof Handling Rules + +Proofs are **append-only**. + +Rules: + +- One proof per step +- Proof must match expected AuthMethodType +- Proof must be verified before progressing +- Proofs cannot be modified or deleted + +This guarantees: + +- Auditability +- Determinism +- Replay prevention + +--- + +## 7. Step Advancement Rules + +The aggregate enforces: + +- Steps must be executed in order +- Transition conditions must be satisfied +- Skipping steps is forbidden +- Optional steps are flow-defined, not attempt-defined + +The attempt **does not decide** what the next step is. It **asks the flow**. + +--- + +## 8. Failure Semantics (Important) + +Failures are **final**. + +Examples: + +- Wrong password β†’ Failed +- OTP expired β†’ Failed +- OAuth assertion invalid β†’ Failed + +Retries: + +- Require a **new AuthenticationAttempt** +- Rate limiting is policy/infrastructure concern + +This avoids: + +- State confusion +- Timing attacks +- Complex rollback logic + +--- + +## 9. Relationship to Other Aggregates + +- Uses **AuthenticationFlow** (read-only) +- Produces **AuthenticationSession** +- References **Principal**, but does not own it +- Uses **Policies**, but does not evaluate them + +No bidirectional coupling. + +--- + +## 10. What AuthenticationAttempt Does NOT Do + +Explicitly forbidden: + +- Creating credentials +- Issuing tokens +- Storing secrets +- Evaluating risk +- Talking to HTTP +- Calling providers + +It enforces **process**, not mechanics. + +--- + +## 11. Invariant Checklist (Must Always Hold) + +βœ” One flow per attempt βœ” One terminal state only βœ” No step skipping βœ” No proof +reuse βœ” No resurrection after failure βœ” Deterministic outcome + +If any invariant breaks β†’ security issue. + +--- + +## 12. Step 3 Status + +βœ… Aggregate boundaries defined βœ… State machine enforced βœ… MFA-safe βœ… +Replay-safe βœ… Audit-ready + +--- + +# Step 4 β€” **Authentication Policy Model (Risk, Conditions, Step-Up)** + +This step defines **who is allowed to authenticate, under what conditions, and +with which strength** β€” without hard-coding logic. + +If policies are not modeled cleanly, you will: + +- Hard-code MFA rules +- Fork enterprise behavior +- Break extensibility +- Lose auditability + +--- + +## 1. Purpose of Policy in Auth Domain + +A **Policy** answers questions like: + +- Is this authentication allowed? +- Is step-up required? +- Which flow should apply? +- Is this context risky? +- Is the current proof sufficient? + +Policies **do not authenticate**. They **constrain and steer** authentication. + +--- + +## 2. Core Principle (Non-Negotiable) + +**Policies are declarative and evaluative β€” never procedural.** + +No: + +```ts +if (ip === 'x') requireOtp(); +``` + +Yes: + +- Policy definitions +- Policy evaluation results +- Policy-driven decisions + +--- + +## 3. Policy as a First-Class Domain Concept + +### AuthenticationPolicy (Domain Object) + +```ts +AuthenticationPolicy +- policyId +- name +- scope +- rules: PolicyRule[] +- effect +``` + +Rules: + +- Immutable +- Versionable +- Serializable +- Configurable per tenant / environment + +--- + +## 4. Policy Scope + +Policies apply at different levels: + +```ts +PolicyScope = + | Global + | Principal + | AuthMethod + | AuthFlow + | Resource +``` + +Examples: + +- Global MFA enforcement +- Principal-specific restrictions +- Method-specific constraints +- Flow selection rules + +--- + +## 5. Policy Rules (Atomic Conditions) + +### PolicyRule + +```ts +PolicyRule +- condition: ConditionExpression +- action: PolicyAction +``` + +Each rule: + +- Evaluates **one condition** +- Produces **one action** +- Has no side effects + +--- + +## 6. Condition Model (What can be checked) + +Conditions evaluate **context**, never infrastructure. + +```ts +ConditionExpression - subject - operator - value; +``` + +### Common Subjects + +- risk.score +- principal.trustLevel +- device.trusted +- location.country +- time.window +- auth.method +- auth.factor +- attempt.count + +### Operators + +- equals +- notEquals +- greaterThan +- lessThan +- in +- notIn + +--- + +## 7. Policy Actions (What can be enforced) + +Actions **do not perform authentication**. + +```ts +PolicyAction = + | Allow + | Deny + | RequireStepUp + | SelectFlow + | LimitTrustLevel +``` + +Examples: + +- Require OTP if risk > threshold +- Deny auth from blocked country +- Force enterprise SSO +- Downgrade session trust + +--- + +## 8. Policy Evaluation Result + +Policies return **decisions**, not behavior. + +```ts +PolicyEvaluationResult +- decision +- requiredFlow? +- requiredAuthFactors? +- maxTrustLevel? +- reasons[] +``` + +This allows: + +- Explainability +- Auditing +- Compliance + +--- + +## 9. Policy Evaluation Responsibility + +Policies are **evaluated by a Domain Service**, not entities. + +```ts +AuthenticationPolicyEvaluator +- evaluate(context): PolicyEvaluationResult +``` + +Context includes: + +- Principal snapshot +- AuthenticationAttempt snapshot +- Device/context snapshot +- Risk assessment (via port) + +--- + +## 10. Relationship with Authentication Flow + +Policies can: + +- Select a flow +- Alter transitions +- Require additional steps + +Flows **reference policies**, but do not contain logic. + +This separation allows: + +- Same flow, different behavior +- Enterprise overrides +- Runtime reconfiguration + +--- + +## 11. Example Policies (Conceptual) + +### 11.1 Risk-Based MFA + +``` +IF risk.score > 70 +THEN RequireStepUp (otp) +``` + +--- + +### 11.2 Geo Restriction + +``` +IF location.country IN blockedCountries +THEN Deny +``` + +--- + +### 11.3 Trust Downgrade + +``` +IF device.trusted = false +THEN LimitTrustLevel = LOW +``` + +--- + +### 11.4 Flow Selection + +``` +IF principal.type = Service +THEN SelectFlow = m2m_flow +``` + +--- + +## 12. What Policies Explicitly Do NOT Do + +Forbidden: + +- Issuing challenges +- Sending OTPs +- Creating sessions +- Calling providers +- Accessing databases directly +- Knowing HTTP headers + +They only **decide**. + +--- + +## 13. Extensibility Guarantees + +With this model you can: + +- Add new conditions without changing flows +- Add new actions without breaking domain +- Support enterprise policy engines later +- Audit decisions deterministically + +--- + +## 14. Step 4 Status + +βœ… Policy model defined βœ… Risk-adaptive ready βœ… Enterprise-grade βœ… +Explainable and auditable + +--- + +# Step 5 β€” **Credential Lifecycle Modeling** + +This step defines **what a credential is**, how it is created, validated, +rotated, revoked, and expired β€” without storing secrets or coupling to storage. + +If credential lifecycle is vague, security degrades fast. + +--- + +## 1. Purpose of Credential in Auth Domain + +A **Credential** represents: + +> _A verifiable authentication capability bound to a Principal._ + +It is **not**: + +- A password hash +- An OTP secret +- A token +- A provider artifact + +Those are **infrastructure details**. + +--- + +## 2. Credential as a Domain Entity + +### Credential (Entity) + +```ts +Credential +- CredentialId +- PrincipalId +- AuthMethodType +- AuthFactorType +- Status +- IssuedAt +- ExpiresAt? +- LastUsedAt? +- Metadata +``` + +--- + +## 3. Credential Status (Finite State) + +```ts +CredentialStatus = + | Active + | Suspended + | Revoked + | Expired + | Compromised +``` + +Rules: + +- Revoked and Compromised are terminal +- Expired is time-based +- Suspended is reversible + +--- + +## 4. Credential Invariants (Strict) + +A credential must always satisfy: + +- Belongs to exactly one Principal +- Is bound to exactly one Auth Method +- Cannot be reused across principals +- Cannot be reactivated after Revocation +- Cannot authenticate if not Active +- Cannot exist without lifecycle metadata + +Violation = security bug. + +--- + +## 5. Credential Creation Rules + +Credential creation: + +- Is explicit +- Requires policy approval +- Produces **no secret** in domain +- Delegates material generation to infrastructure + +Example: + +- Domain: β€œCreate OTP credential” +- Infra: Generates secret, stores it securely + +--- + +## 6. Credential Usage Rules + +On each successful authentication: + +- Credential status must be Active +- Expiration must be checked +- Usage timestamp may be updated +- Compromise signals may suspend or revoke + +Credential usage is **observed**, not enforced here. + +--- + +## 7. Credential Rotation & Replacement + +Rotation is modeled as: + +- Create new credential +- Activate new +- Suspend or revoke old + +No mutation-in-place. + +This allows: + +- Auditability +- Rollback +- Compliance + +--- + +## 8. Credential Revocation + +Revocation reasons: + +- User action +- Admin action +- Policy decision +- Risk detection +- Breach response + +Revocation: + +- Is immediate +- Is irreversible +- Must propagate to infra adapters + +--- + +## 9. Credential Expiration + +Expiration: + +- Is time-based +- Evaluated at authentication time +- Does not delete credential +- Transitions to Expired + +Deletion is **not** domain responsibility. + +--- + +## 10. Relationship to AuthenticationAttempt + +- Attempts **reference** credentials +- Attempts do not own credentials +- Credential state is checked during verification +- No attempt may mutate credential directly + +--- + +## 11. Credential Types (Examples) + +| Method | Credential Meaning | +| --------- | ---------------------- | +| Password | Knowledge credential | +| OTP | Possession credential | +| OAuth | Delegated credential | +| API Token | Possession credential | +| Passkey | Possession + inherence | + +Types are **data**, not inheritance. + +--- + +## 12. What Credential Does NOT Know + +Explicitly forbidden: + +- Hash algorithms +- OTP secrets +- Token formats +- Storage engines +- Providers +- Encryption + +Those live behind ports. + +--- + +## 13. Ports Related to Credentials + +```ts +CredentialRepository; +CredentialMaterialStore(infra); +CredentialUsageReporter; +CredentialRevocationPublisher; +``` + +Domain only depends on interfaces. + +--- + +## 14. Extensibility Guarantees + +With this model: + +- New credential types require no domain change +- Infra can store secrets however it wants +- Enterprise policies can control lifecycle +- Auditing is deterministic + +--- + +## 15. Step 5 Status + +βœ… Credential lifecycle defined βœ… Security invariants enforced βœ… +Storage-agnostic βœ… Enterprise-ready + +--- + +We continue. This is the **last foundational domain step**. + +--- + +# Step 6 β€” **AuthenticationSession & Trust Levels** + +This step defines **what β€œbeing authenticated” actually means** in your system, +independent of tokens, cookies, or transport. + +If this is weak, authorization, SSO, and enterprise security all collapse. + +--- + +## 1. Purpose of AuthenticationSession + +An **AuthenticationSession** represents: + +> _A time-bounded, trust-scoped result of a successful authentication._ + +It is: + +- The output of Authentication +- The input to Authorization +- The anchor for SSO, re-auth, and step-up + +It is **not**: + +- A cookie +- A JWT +- A refresh token +- An HTTP session + +Those are representations. + +--- + +## 2. AuthenticationSession as Aggregate Root + +### AuthenticationSession (Aggregate Root) + +```ts +AuthenticationSession - + SessionId - + PrincipalId - + FlowId - + IssuedAt - + ExpiresAt - + TrustLevel - + AuthFactorsUsed - + ContextSnapshot - + Status; +``` + +--- + +## 3. Session Status (Finite State) + +```ts +SessionStatus = + | Active + | Expired + | Revoked +``` + +Rules: + +- Expired is time-based +- Revoked is explicit and terminal +- Active is the only usable state + +--- + +## 4. Trust Level (Critical Concept) + +### TrustLevel (Value Object) + +Trust is **not binary**. + +```ts +TrustLevel = + | Anonymous + | Low + | Medium + | High +``` + +Interpretation: + +- **Anonymous** β†’ unauthenticated +- **Low** β†’ weak auth (magic link, social) +- **Medium** β†’ single strong factor +- **High** β†’ MFA / hardware-backed + +Trust is: + +- Computed from proofs + policies +- Stored in session +- Used by authorization + +--- + +## 5. Trust Level Invariants + +- TrustLevel is **assigned at session creation** +- TrustLevel can be **downgraded** +- TrustLevel can **never be upgraded in-place** +- Upgrade requires **new AuthenticationAttempt** + +This prevents silent privilege escalation. + +--- + +## 6. Relationship to AuthenticationAttempt + +- Exactly one session per successful attempt +- Attempt produces session +- Session references attempt context (read-only) +- Session lifecycle is independent afterward + +No circular dependency. + +--- + +## 7. Context Snapshot (Why It Exists) + +### ContextSnapshot (Value Object) + +Captures authentication context **at time of issuance**: + +- Device trust +- Location +- Risk score +- Auth methods used +- Policies applied + +Why: + +- Auditability +- Compliance +- Forensic analysis +- Future policy checks + +Context is **immutable**. + +--- + +## 8. Session Expiration Rules + +Expiration is: + +- Deterministic +- Policy-driven +- Evaluated by infrastructure +- Reflected in domain state + +Domain does not: + +- Refresh sessions +- Slide expiration +- Issue refresh tokens + +Those are adapters. + +--- + +## 9. Session Revocation + +Revocation triggers: + +- User logout +- Admin action +- Credential revocation +- Risk detection +- Policy violation + +Revocation: + +- Is explicit +- Is immediate +- Invalidates all representations + +--- + +## 10. SSO Compatibility (Important) + +SSO is simply: + +- Multiple representations +- One AuthenticationSession + +This model supports: + +- Web SSO +- Mobile SSO +- API SSO +- Cross-app SSO + +Without special logic. + +--- + +## 11. What AuthenticationSession Does NOT Know + +Explicitly forbidden: + +- JWT claims +- Cookies +- Headers +- OAuth tokens +- HTTP +- Storage engine + +It only knows **meaning**, not format. + +--- + +## 12. Ports Related to Sessions + +```ts +AuthenticationSessionRepository; +SessionRevocationPublisher; +SessionExpirationScheduler; +SessionRepresentationFactory(infra); +``` + +Domain defines contracts only. + +--- + +## 13. Extensibility Guarantees + +With this model: + +- You can change token formats freely +- You can support SSO without refactor +- You can add step-up auth cleanly +- You can audit auth decisions years later + +--- + +## 14. Step 6 Status + +βœ… AuthenticationSession modeled βœ… Trust levels explicit βœ… Step-up safe βœ… +Authorization-ready βœ… Enterprise-grade + +--- + +# 🎯 Foundation Complete + +You now have a **complete, extensible, production-grade Auth Domain**: + +1. Auth Method SPI +2. Authentication Flow DSL +3. AuthenticationAttempt lifecycle +4. Policy model +5. Credential lifecycle +6. AuthenticationSession & Trust diff --git a/packages/authentication/core/GAP_ANALYSIS.md b/packages/authentication/core/GAP_ANALYSIS.md index 0be0aa2..e56a31b 100644 --- a/packages/authentication/core/GAP_ANALYSIS.md +++ b/packages/authentication/core/GAP_ANALYSIS.md @@ -1,402 +1,47 @@ -# Gap Analysis: Design vs Implementation +# Auth Core β€” Future Work Tracker -This document identifies what's **documented** in `Definition.md` and -`Architecture.md` vs what's **actually implemented** in the codebase. +Gaps between the **current implementation** and a complete authentication +platform. Aspirational designs are preserved in [FUTURE.md](./FUTURE.md). -## βœ… Implemented (Complete) +## Code Gaps (priority order) -### 1. Auth Method SPI (Step 1) - βœ… PARTIALLY +| # | Gap | Impact | +| --- | ----------------------------------------------- | --------------------------------------------------------------- | +| 1 | Passwordless not on `AuthMethodPort` | Two integration patterns; flow services cannot use passwordless | +| 2 | Passwordless `index.ts` exports nothing | README API not importable from package entry | +| 3 | MFA/OAuth/session/token domains unexported | Consumers cannot use in-tree models without deep imports | +| 4 | `OAuthAuthorizeService` is a stub | OAuth flow incomplete at application layer | +| 5 | Email/SMS channel stubs throw `not implemented` | Passwordless delivery not wired | +| 6 | Two `Identity` types (entity vs aggregate) | Naming collision confuses contributors | +| 7 | `Token` aggregate throws plain `Error` | Inconsistent with registry-backed `DomainError` pattern | +| 8 | `OtpCode` in core throws generic `Error` | Should use domain error class | -- βœ… `AuthMethod` value object exists -- βœ… `AuthMethodPort` interface exists -- βœ… `AuthMethodRegistry` type exists (extensible) -- ❌ **Missing**: `AuthMethodDefinition` domain object (capability descriptor) -- ❌ **Missing**: `AuthInputType` value object enum -- ❌ **Missing**: `AuthProofType` value object enum -- ❌ **Missing**: `AuthMethodRegistry` domain service +## Documentation Gaps (addressed in this sync) -### 2. AuthenticationAttempt Lifecycle (Step 3) - βœ… BASIC +- Design docs described Principal/Credential/Flow DSL β€” rewritten to match code +- Auth errors README documented removed `type` field β€” updated for v6 registry +- Passwordless docs used `Result.fail` / `getValue()` β€” updated to v5 API +- Root README had broken ddd anchors and wrong project tree β€” fixed -- βœ… `AuthenticationAttempt` aggregate exists -- βœ… Basic state machine (PENDING β†’ SUCCEEDED/FAILED) -- βœ… Domain events (Started, Succeeded, Failed) -- ❌ **Missing**: Flow-driven lifecycle (no `FlowId` property) -- ❌ **Missing**: Step tracking (`CurrentStepId`) -- ❌ **Missing**: Proof collection (`CollectedProofs`) -- ❌ **Missing**: States: `Initialized`, `InProgress`, `AwaitingChallenge` -- ❌ **Missing**: Step advancement logic -- ❌ **Missing**: Proof validation per step +## Integration Gaps -### 3. Policy Engine (Step 4) - βœ… BASIC +| Desired flow | Blocker | +| ----------------------------------------- | ---------------------------------------------------- | +| End-to-end OTP login via flow services | Needs consumer adapters for repos + `OtpChannelPort` | +| End-to-end passwordless via flow services | Needs `PasswordlessAuthMethod` adapter | +| MFA step-up after policy deny | MFA services exist but are unexported | +| OAuth authorization code flow | Domain exists; app service incomplete | -- βœ… `AuthPolicyEngine` exists -- βœ… `AuthPolicyEvaluator` contract exists -- βœ… `AuthPolicyContext` exists -- βœ… `AuthPolicyDecision` exists -- ❌ **Missing**: `AuthenticationPolicy` domain object (immutable, versionable) -- ❌ **Missing**: `PolicyRule` with `ConditionExpression` and `PolicyAction` -- ❌ **Missing**: `PolicyScope` value object (Global, Principal, AuthMethod, - etc.) -- ❌ **Missing**: Declarative policy definitions (currently procedural) -- ❌ **Missing**: Policy serialization/versioning +## Export Tiers (proposed) -### 4. MFA Domain - βœ… IMPLEMENTED +| Tier | Contents | +| ------------ | ------------------------------ | +| Stable | Current `index.ts` exports | +| Experimental | MFA, OAuth subpath exports | +| Internal | Everything else until promoted | -- βœ… `MFASession` aggregate exists -- βœ… Challenge issuance -- βœ… Verification logic -- βœ… Attempt tracking +## Related -### 5. OAuth Domain - βœ… PARTIALLY - -- βœ… `OAuthAuthorization` aggregate exists -- βœ… Basic OAuth flow structure - -### 6. Session Domain - βœ… BASIC - -- βœ… `Session` entity exists -- βœ… Expiration and revocation -- ❌ **Missing**: `AuthenticationSession` aggregate (as per design) -- ❌ **Missing**: `TrustLevel` value object -- ❌ **Missing**: `ContextSnapshot` value object -- ❌ **Missing**: `AuthFactorsUsed` tracking -- ❌ **Missing**: Trust level computation from proofs + policies - ---- - -## ❌ Missing (Not Implemented) - -### 1. Authentication Flow DSL (Step 2) - ❌ NOT IMPLEMENTED - -**Documented Design:** - -```ts -AuthenticationFlow -- flowId -- name -- steps: AuthFlowStep[] -- entryConditions -- exitConditions - -AuthFlowStep -- stepId -- authMethodType -- required: boolean -- onSuccess: Transition -- onFailure: Transition -- onChallenge?: Transition - -Transition -- targetStepId | terminalState -- condition?: PolicyExpression -``` - -**Status:** ❌ **COMPLETELY MISSING** - -**Impact:** - -- Cannot support multi-step authentication flows -- Cannot support conditional flows (risk-based step-up) -- Cannot support MFA orchestration -- Orchestrator directly calls methods instead of executing flows -- No declarative flow definitions - -**Files to Create:** - -- `src/domain/identity/aggregates/authentication-flow.aggregate.ts` -- `src/domain/identity/value-objects/auth-flow-step.vo.ts` -- `src/domain/identity/value-objects/transition.vo.ts` -- `src/domain/identity/value-objects/flow-id.vo.ts` - ---- - -### 2. Credential Lifecycle (Step 5) - ❌ NOT IMPLEMENTED - -**Documented Design:** - -```ts -Credential (Entity) -- CredentialId -- PrincipalId -- AuthMethodType -- AuthFactorType -- Status (Active | Suspended | Revoked | Expired | Compromised) -- IssuedAt -- ExpiresAt? -- LastUsedAt? -- Metadata -``` - -**Status:** ❌ **COMPLETELY MISSING** - -**Impact:** - -- Cannot track credential lifecycle -- Cannot support credential rotation -- Cannot support credential revocation -- Cannot check credential status during authentication -- No audit trail for credential usage - -**Files to Create:** - -- `src/domain/identity/entities/credential.entity.ts` -- `src/domain/identity/value-objects/credential-id.vo.ts` -- `src/domain/identity/value-objects/credential-status.vo.ts` -- `src/ports/outbound/credential-repository.port.ts` - ---- - -### 3. AuthenticationSession Aggregate (Step 6) - ❌ NOT IMPLEMENTED - -**Current:** `Session` entity exists but doesn't match design - -**Documented Design:** - -```ts -AuthenticationSession (Aggregate Root) -- SessionId -- PrincipalId -- FlowId -- IssuedAt -- ExpiresAt -- TrustLevel (Anonymous | Low | Medium | High) -- AuthFactorsUsed -- ContextSnapshot -- Status (Active | Expired | Revoked) -``` - -**Status:** ❌ **MISSING KEY FEATURES** - -**What's Missing:** - -- `TrustLevel` value object -- `ContextSnapshot` value object -- `AuthFactorsUsed` tracking -- Trust level computation -- `createFromAttempt()` factory method -- Flow reference - -**Files to Create/Update:** - -- `src/domain/session/aggregates/authentication-session.aggregate.ts` (new - aggregate) -- `src/domain/session/value-objects/trust-level.vo.ts` -- `src/domain/session/value-objects/context-snapshot.vo.ts` -- Update `Session` entity or replace with `AuthenticationSession` - ---- - -### 4. Auth Proof System - ❌ NOT IMPLEMENTED - -**Documented Design:** - -```ts -AuthProof (Value Object) -- proofType (password_proof | otp_proof | oauth_proof | assertion_proof) -- methodType -- issuedAt -- metadata -``` - -**Status:** ❌ **COMPLETELY MISSING** - -**Impact:** - -- Cannot collect proofs during authentication -- Cannot validate proofs -- Cannot track which factors were used -- Cannot compute trust levels from proofs - -**Files to Create:** - -- `src/domain/identity/value-objects/auth-proof.vo.ts` -- `src/domain/identity/value-objects/auth-proof-type.vo.ts` - ---- - -### 5. Flow-Driven AuthenticationAttempt - ❌ NOT IMPLEMENTED - -**Current:** `AuthenticationAttempt` has basic state but no flow integration - -**Missing Properties:** - -- `flowId: FlowId` -- `currentStepId: StepId` -- `collectedProofs: AuthProof[]` -- States: `Initialized`, `InProgress`, `AwaitingChallenge` - -**Missing Methods:** - -- `startFlow(flow: AuthenticationFlow)` -- `completeStep(proof: AuthProof)` -- `advanceToNextStep()` -- `awaitChallenge()` -- `resumeFromChallenge()` - -**Impact:** - -- Cannot execute multi-step flows -- Cannot track progress through flow steps -- Cannot collect proofs for trust computation -- Cannot support MFA flows properly - ---- - -### 6. Principal Aggregate - ❌ NOT IMPLEMENTED - -**Documented:** Architecture.md mentions `Principal` as aggregate root - -**Current:** Only `Identity` entity exists (minimal) - -**Missing:** - -- `Principal` aggregate root -- Relationship between Principal and Credentials -- Principal lifecycle management - -**Note:** `Identity` might be the Principal, but design doc suggests separate -concept. - ---- - -### 7. Auth Method Definition System - ❌ NOT IMPLEMENTED - -**Documented Design:** - -```ts -AuthMethodDefinition -- type: AuthMethodType -- supportedFactors: Set -- requiredInputs: Set -- outputProof: AuthProofType -- challengeCapable: boolean -``` - -**Status:** ❌ **COMPLETELY MISSING** - -**Impact:** - -- Cannot declare method capabilities -- Cannot validate method requirements -- Cannot compose methods into flows -- Methods are opaque to domain - ---- - -### 8. Policy Domain Objects - ❌ NOT IMPLEMENTED - -**Current:** Only contracts exist, no domain objects - -**Missing:** - -- `AuthenticationPolicy` domain object -- `PolicyRule` value object -- `ConditionExpression` value object -- `PolicyAction` value object -- `PolicyScope` value object - -**Impact:** - -- Policies cannot be serialized/versioned -- Policies cannot be stored/configured declaratively -- Policies are procedural, not data-driven - ---- - -## πŸ”„ Partially Implemented (Needs Completion) - -### 1. Auth Orchestrator Service - -- βœ… Basic method resolution -- βœ… Attempt creation -- ❌ **Missing**: Flow execution -- ❌ **Missing**: Step orchestration -- ❌ **Missing**: Policy evaluation integration -- ❌ **Missing**: Proof collection -- ❌ **Missing**: Session creation from attempt - -### 2. Policy Engine - -- βœ… Basic evaluation loop -- βœ… Step-up detection -- ❌ **Missing**: Declarative policy definitions -- ❌ **Missing**: Condition evaluation -- ❌ **Missing**: Flow selection -- ❌ **Missing**: Trust level limiting - ---- - -## πŸ“‹ Implementation Priority - -### Phase 1: Core Flow System (Critical) - -1. βœ… Create `AuthenticationFlow` aggregate -2. βœ… Create `AuthFlowStep` value object -3. βœ… Create `Transition` value object -4. βœ… Update `AuthenticationAttempt` to be flow-driven -5. βœ… Add proof collection to attempts -6. βœ… Update orchestrator to execute flows - -### Phase 2: Proof & Session (High Priority) - -1. βœ… Create `AuthProof` value object -2. βœ… Create `AuthenticationSession` aggregate -3. βœ… Create `TrustLevel` value object -4. βœ… Create `ContextSnapshot` value object -5. βœ… Implement session creation from attempt - -### Phase 3: Credential Lifecycle (Medium Priority) - -1. βœ… Create `Credential` entity -2. βœ… Create credential status value object -3. βœ… Implement credential repository port -4. βœ… Integrate credential checks in authentication - -### Phase 4: Policy Domain Objects (Medium Priority) - -1. βœ… Create `AuthenticationPolicy` domain object -2. βœ… Create `PolicyRule` value object -3. βœ… Create `ConditionExpression` value object -4. βœ… Make policies serializable/versionable - -### Phase 5: Auth Method Definitions (Low Priority) - -1. βœ… Create `AuthMethodDefinition` domain object -2. βœ… Create method registry service -3. βœ… Integrate with flow system - ---- - -## 🎯 Summary - -**What Works:** - -- Basic authentication attempt tracking -- MFA session management -- OAuth structure -- Basic policy evaluation contracts -- Plugin-based auth method system - -**What's Missing:** - -- **Flow DSL** (most critical - blocks MFA, step-up, enterprise flows) -- **Proof system** (blocks trust computation, auditability) -- **Credential lifecycle** (blocks security features) -- **Proper AuthenticationSession** (blocks SSO, authorization) -- **Flow-driven attempts** (blocks multi-step auth) - -**Architecture Compliance:** - -- Current implementation: ~40% of documented design -- Core flow system: 0% implemented -- Credential system: 0% implemented -- Session system: 30% implemented (basic entity exists) -- Policy system: 50% implemented (engine exists, domain objects missing) - ---- - -## πŸ“š References - -- **Design Document**: `Definition.md` (Steps 1-6) -- **Architecture Document**: `Architecture.md` -- **Rules Document**: `RULES.md` +- [IMPLEMENTATION_PLAN.md](./IMPLEMENTATION_PLAN.md) β€” actionable tasks +- [ROADMAP.md](./ROADMAP.md) β€” phased timeline +- [FUTURE.md](./FUTURE.md) β€” original aspirational specification diff --git a/packages/authentication/core/IMPLEMENTATION_PLAN.md b/packages/authentication/core/IMPLEMENTATION_PLAN.md index be0ed10..ebe7049 100644 --- a/packages/authentication/core/IMPLEMENTATION_PLAN.md +++ b/packages/authentication/core/IMPLEMENTATION_PLAN.md @@ -1,928 +1,51 @@ -# Implementation Plan: Completing Auth Core +# Auth Core β€” Implementation Plan -This document outlines the step-by-step plan to implement all missing pieces -identified in the gap analysis. +Open work items only. Completed fiction removed. -## 🎯 Goals +## 1. Passwordless integration -1. Implement Authentication Flow DSL (Step 2) -2. Add Auth Proof system -3. Implement Credential lifecycle -4. Complete AuthenticationSession aggregate with Trust Levels -5. Make AuthenticationAttempt flow-driven -6. Enhance Policy domain objects +1. Export `IssuePasswordlessChallengeService`, + `VerifyPasswordlessChallengeService`, aggregate, and ports from + `methods/passwordless/src/index.ts` +2. Create `PasswordlessAuthMethod` implementing `AuthMethodPort` +3. Register in flow services alongside `OtpAuthMethod` +4. Add integration tests for attempt β†’ passwordless β†’ succeed path ---- +## 2. Export hygiene -## πŸ“‹ Phase 1: Foundation - Flow DSL & Proof System +1. Document export tiers in README (stable / experimental / internal) +2. Add subpath exports for MFA and OAuth OR promote to main entry +3. Export `AuthCoreErrorRegistry` and error classes for consumer handling +4. Rename or remove duplicate `Identity` aggregate -**Goal:** Enable multi-step authentication flows and proof tracking +## 3. OAuth completion -**Duration:** ~2-3 weeks +1. Implement `OAuthAuthorizeService.execute` with repository port +2. Export OAuth domain types and ports +3. Add OAuth method package or extend core adapter -### Task 1.1: Create Flow Value Objects +## 4. Error consistency -**Priority:** Critical -**Dependencies:** None +1. Migrate `Token` aggregate to `EntityValidationError` / registry errors +2. Migrate `OtpCode` validation to domain error class +3. Ensure all new errors pass `auth-core-error.architecture.spec.ts` -**Files to Create:** +## 5. Channel implementations -- `src/domain/identity/value-objects/flow-id.vo.ts` -- `src/domain/identity/value-objects/auth-flow-step.vo.ts` -- `src/domain/identity/value-objects/transition.vo.ts` -- `src/domain/identity/value-objects/terminal-state.vo.ts` +1. Replace `EmailChannel` / `SMSChannelImp` stubs with real adapters or remove +2. Document consumer responsibility for channel delivery -**Implementation Steps:** +## Out of scope (see FUTURE.md) -1. Create `FlowId` value object (similar to `AuthAttemptId`) -2. Create `TerminalState` enum/value object: - `AUTHENTICATED | FAILED | CHALLENGED` -3. Create `Transition` value object: - ```typescript - Transition { - targetStepId?: string - terminalState?: TerminalState - condition?: PolicyExpression // Reference for now - } - ``` -4. Create `AuthFlowStep` value object: - ```typescript - AuthFlowStep { - stepId: string - authMethodType: AuthMethodName - required: boolean - onSuccess: Transition - onFailure: Transition - onChallenge?: Transition - } - ``` +- Flow DSL compiler +- Principal/Credential aggregates +- Trust level model +- Package split into core-domain / core-ports -**Acceptance Criteria:** +## Testing approach -- βœ… All value objects are immutable -- βœ… Validation logic in place -- βœ… Serializable to JSON -- βœ… Tests for each value object +- Architecture tests for error registry coverage (existing pattern) +- Application service tests with port mocks (see passwordless package) +- No tests for documentation-only changes ---- - -### Task 1.2: Create AuthenticationFlow Aggregate - -**Priority:** Critical -**Dependencies:** Task 1.1 - -**Files to Create:** - -- `src/domain/identity/aggregates/authentication-flow.aggregate.ts` -- `src/domain/identity/events/flow-created.event.ts` (if needed) - -**Implementation Steps:** - -1. Create `AuthenticationFlow` aggregate root: - ```typescript - AuthenticationFlow { - flowId: FlowId - name: string - steps: AuthFlowStep[] - entryConditions?: PolicyExpression[] - exitConditions?: PolicyExpression[] - } - ``` -2. Add factory method: `create(flowId, name, steps)` -3. Add validation: - - At least one step - - First step must exist - - Terminal states must be reachable - - No circular dependencies -4. Add method: `getStep(stepId): AuthFlowStep | null` -5. Add method: `getFirstStep(): AuthFlowStep` -6. Add method: `isValid(): boolean` - -**Acceptance Criteria:** - -- βœ… Flow is immutable after creation -- βœ… Validation prevents invalid flows -- βœ… Can serialize/deserialize flows -- βœ… Tests for flow validation -- βœ… Tests for step retrieval - ---- - -### Task 1.3: Create Auth Proof System - -**Priority:** Critical -**Dependencies:** None (can be parallel with Task 1.1) - -**Files to Create:** - -- `src/domain/identity/value-objects/auth-proof-type.vo.ts` -- `src/domain/identity/value-objects/auth-proof.vo.ts` - -**Implementation Steps:** - -1. Create `AuthProofType` value object: - ```typescript - AuthProofType = - | 'password_proof' - | 'otp_proof' - | 'oauth_proof' - | 'assertion_proof' - | 'challenge_proof' - ``` -2. Create `AuthProof` value object: - ```typescript - AuthProof { - proofId: string (UUID) - proofType: AuthProofType - methodType: AuthMethodName - issuedAt: Date - metadata: Record - } - ``` -3. Add validation: - - proofId must be unique - - issuedAt must be valid date - - metadata is optional but serializable -4. Add method: `toObject()` for serialization - -**Acceptance Criteria:** - -- βœ… Proof is immutable -- βœ… Can serialize/deserialize -- βœ… Tests for proof creation and validation - ---- - -### Task 1.4: Update AuthenticationAttempt for Flow-Driven - -**Priority:** Critical -**Dependencies:** Task 1.1, Task 1.2, Task 1.3 - -**Files to Update:** - -- `src/domain/identity/aggregates/authentication-attempt.aggregate.ts` -- `src/domain/identity/value-objects/auth-status.vo.ts` (add new states) - -**Implementation Steps:** - -1. Update `AuthStatus` to include: - ```typescript - AuthStatus = - | 'INITIALIZED' - | 'IN_PROGRESS' - | 'AWAITING_CHALLENGE' - | 'SUCCEEDED' - | 'FAILED' - ``` -2. Update `AuthenticationAttemptProps`: - ```typescript - interface AuthenticationAttemptProps { - flowId: FlowId; // NEW - currentStepId?: string; // NEW - collectedProofs: AuthProof[]; // NEW - status: AuthStatus; - method: AuthMethod; // Keep for backward compat - identityId?: IdentityId; - } - ``` -3. Update factory method `start()`: - - Accept `AuthenticationFlow` instead of just `AuthMethod` - - Set `flowId` and `currentStepId` to first step - - Initialize `collectedProofs` as empty array - - Set status to `INITIALIZED` -4. Add method: `startFlow(flow: AuthenticationFlow): void` - - Transition from `INITIALIZED` β†’ `IN_PROGRESS` - - Set `currentStepId` to first step -5. Add method: `completeStep(proof: AuthProof): void` - - Validate proof matches current step's method - - Add proof to `collectedProofs` - - Transition based on flow step's `onSuccess` -6. Add method: `failStep(reason: string): void` - - Transition based on flow step's `onFailure` -7. Add method: `awaitChallenge(): void` - - Transition to `AWAITING_CHALLENGE` -8. Add method: `resumeFromChallenge(): void` - - Transition back to `IN_PROGRESS` -9. Add method: `getCurrentStep(): AuthFlowStep | null` - - Query flow for current step -10. Add method: `advanceToNextStep(transition: Transition): void` - - Validate transition - - Update `currentStepId` - - Handle terminal states -11. Update `succeed()` and `fail()`: - - Only allow if in terminal state transition - - Validate all required steps completed - -**Acceptance Criteria:** - -- βœ… Attempt tracks flow and current step -- βœ… Proofs are collected per step -- βœ… State transitions follow flow definition -- βœ… Cannot skip steps -- βœ… Cannot reuse proofs -- βœ… Tests for all state transitions -- βœ… Tests for proof collection -- βœ… Tests for step advancement - ---- - -### Task 1.5: Create Flow Repository Port - -**Priority:** High -**Dependencies:** Task 1.2 - -**Files to Create:** - -- `src/ports/outbound/authentication-flow-repository.port.ts` - -**Implementation Steps:** - -1. Create port interface: - ```typescript - AuthenticationFlowRepositoryPort { - findById(flowId: FlowId): Promise - findAll(): Promise - save(flow: AuthenticationFlow): Promise - } - ``` - -**Acceptance Criteria:** - -- βœ… Port interface defined -- βœ… No implementation (infrastructure concern) - ---- - -## πŸ“‹ Phase 2: Trust & Session System - -**Goal:** Implement trust levels and complete session aggregate - -**Duration:** ~1-2 weeks - -### Task 2.1: Create Trust Level Value Object - -**Priority:** High -**Dependencies:** None - -**Files to Create:** - -- `src/domain/session/value-objects/trust-level.vo.ts` - -**Implementation Steps:** - -1. Create `TrustLevel` value object: - ```typescript - TrustLevel = - | 'ANONYMOUS' // 0 - unauthenticated - | 'LOW' // 1 - weak auth (magic link, social) - | 'MEDIUM' // 2 - single strong factor - | 'HIGH' // 3 - MFA / hardware-backed - ``` -2. Add method: `compare(other: TrustLevel): number` (for ordering) -3. Add method: `isAtLeast(level: TrustLevel): boolean` -4. Add method: `downgradeTo(level: TrustLevel): TrustLevel` (only allows - downgrade) - -**Acceptance Criteria:** - -- βœ… Trust levels are ordered -- βœ… Cannot upgrade in-place (new attempt required) -- βœ… Tests for comparison and downgrade logic - ---- - -### Task 2.2: Create Context Snapshot Value Object - -**Priority:** High -**Dependencies:** None - -**Files to Create:** - -- `src/domain/session/value-objects/context-snapshot.vo.ts` - -**Implementation Steps:** - -1. Create `ContextSnapshot` value object: - ```typescript - ContextSnapshot { - deviceTrusted?: boolean - location?: { - country?: string - ipAddress?: string - } - riskScore?: number - authMethodsUsed: AuthMethodName[] - policiesApplied: string[] // policy IDs - capturedAt: Date - } - ``` -2. Add factory: `capture(context: AuthContext, ...): ContextSnapshot` -3. Add validation for required fields - -**Acceptance Criteria:** - -- βœ… Immutable snapshot -- βœ… Serializable -- βœ… Tests for snapshot creation - ---- - -### Task 2.3: Create AuthenticationSession Aggregate - -**Priority:** High -**Dependencies:** Task 2.1, Task 2.2, Task 1.3 - -**Files to Create:** - -- `src/domain/session/aggregates/authentication-session.aggregate.ts` -- `src/domain/session/value-objects/session-status.vo.ts` -- `src/domain/session/events/session-created.event.ts` -- `src/domain/session/events/session-revoked.event.ts` - -**Files to Update/Replace:** - -- `src/domain/session/entities/session.entity.ts` (may need to replace or - refactor) - -**Implementation Steps:** - -1. Create `SessionStatus` value object: - ```typescript - SessionStatus = 'ACTIVE' | 'EXPIRED' | 'REVOKED'; - ``` -2. Create `AuthenticationSession` aggregate: - ```typescript - AuthenticationSession { - sessionId: SessionId - principalId: IdentityId - flowId: FlowId - attemptId: AuthAttemptId - issuedAt: Date - expiresAt: Date - trustLevel: TrustLevel - authFactorsUsed: AuthFactor[] - contextSnapshot: ContextSnapshot - status: SessionStatus - } - ``` -3. Add factory: - `createFromAttempt(attempt: AuthenticationAttempt, ...): AuthenticationSession` - - Extract proofs from attempt - - Compute trust level from proofs + policies - - Capture context snapshot - - Set expiration based on policy -4. Add method: - `computeTrustLevel(proofs: AuthProof[], policies: Policy[]): TrustLevel` - - Rules: - - No proofs β†’ ANONYMOUS - - Only passwordless/social β†’ LOW - - Single strong factor β†’ MEDIUM - - MFA or hardware β†’ HIGH -5. Add method: `revoke(at: Date): void` - - Transition to REVOKED - - Emit event -6. Add method: `isExpired(now: Date): boolean` -7. Add method: `downgradeTrust(level: TrustLevel): void` - - Only allows downgrade -8. Add method: `extractAuthFactors(proofs: AuthProof[]): AuthFactor[]` - -**Acceptance Criteria:** - -- βœ… Session created only from successful attempt -- βœ… Trust level computed correctly -- βœ… Cannot upgrade trust in-place -- βœ… Revocation works correctly -- βœ… Tests for session creation -- βœ… Tests for trust computation -- βœ… Tests for revocation - ---- - -### Task 2.4: Create Session Repository Port - -**Priority:** High -**Dependencies:** Task 2.3 - -**Files to Create:** - -- `src/ports/outbound/authentication-session-repository.port.ts` - -**Implementation Steps:** - -1. Create port interface: - ```typescript - AuthenticationSessionRepositoryPort { - save(session: AuthenticationSession): Promise - findById(sessionId: SessionId): Promise - findByPrincipalId(principalId: IdentityId): Promise - revoke(sessionId: SessionId): Promise - } - ``` - -**Acceptance Criteria:** - -- βœ… Port interface defined - ---- - -## πŸ“‹ Phase 3: Credential Lifecycle - -**Goal:** Implement credential management - -**Duration:** ~1-2 weeks - -### Task 3.1: Create Credential Value Objects - -**Priority:** Medium -**Dependencies:** None - -**Files to Create:** - -- `src/domain/identity/value-objects/credential-id.vo.ts` -- `src/domain/identity/value-objects/credential-status.vo.ts` - -**Implementation Steps:** - -1. Create `CredentialId` value object (UUID-based) -2. Create `CredentialStatus` value object: - ```typescript - CredentialStatus = - | 'ACTIVE' - | 'SUSPENDED' - | 'REVOKED' - | 'EXPIRED' - | 'COMPROMISED' - ``` -3. Add validation for status transitions: - - REVOKED and COMPROMISED are terminal - - EXPIRED is time-based - - SUSPENDED is reversible - -**Acceptance Criteria:** - -- βœ… Status transitions validated -- βœ… Tests for status transitions - ---- - -### Task 3.2: Create Credential Entity - -**Priority:** Medium -**Dependencies:** Task 3.1 - -**Files to Create:** - -- `src/domain/identity/entities/credential.entity.ts` -- `src/domain/identity/events/credential-created.event.ts` -- `src/domain/identity/events/credential-revoked.event.ts` -- `src/domain/identity/events/credential-suspended.event.ts` - -**Implementation Steps:** - -1. Create `Credential` entity: - ```typescript - Credential { - credentialId: CredentialId - principalId: IdentityId - authMethodType: AuthMethodName - authFactorType: AuthFactorType - status: CredentialStatus - issuedAt: Date - expiresAt?: Date - lastUsedAt?: Date - metadata: Record - } - ``` -2. Add factory: `create(...): Credential` - - Status starts as ACTIVE - - No secrets stored (infrastructure concern) -3. Add method: `suspend(): void` - - Transition to SUSPENDED - - Emit event -4. Add method: `revoke(reason: string): void` - - Transition to REVOKED (terminal) - - Emit event -5. Add method: `markAsCompromised(): void` - - Transition to COMPROMISED (terminal) - - Emit event -6. Add method: `recordUsage(at: Date): void` - - Update `lastUsedAt` -7. Add method: `checkExpiration(now: Date): void` - - Transition to EXPIRED if expired -8. Add method: `isUsable(): boolean` - - Returns true only if ACTIVE and not expired -9. Add validation: - - Belongs to exactly one Principal - - Bound to exactly one Auth Method - - Cannot reactivate after REVOKED/COMPROMISED - -**Acceptance Criteria:** - -- βœ… Credential lifecycle enforced -- βœ… Cannot authenticate with non-ACTIVE credential -- βœ… Revocation is terminal -- βœ… Tests for all lifecycle operations - ---- - -### Task 3.3: Create Credential Repository Port - -**Priority:** Medium -**Dependencies:** Task 3.2 - -**Files to Create:** - -- `src/ports/outbound/credential-repository.port.ts` - -**Implementation Steps:** - -1. Create port interface: - ```typescript - CredentialRepositoryPort { - save(credential: Credential): Promise - findById(credentialId: CredentialId): Promise - findByPrincipalId(principalId: IdentityId): Promise - findByPrincipalAndMethod( - principalId: IdentityId, - method: AuthMethodName - ): Promise - } - ``` - -**Acceptance Criteria:** - -- βœ… Port interface defined - ---- - -## πŸ“‹ Phase 4: Enhanced Orchestration - -**Goal:** Update orchestrator to execute flows properly - -**Duration:** ~1 week - -### Task 4.1: Update AuthOrchestratorService - -**Priority:** Critical -**Dependencies:** Phase 1 complete, Phase 2 complete - -**Files to Update:** - -- `src/application/services/auth-orchestrator.service.ts` - -**Implementation Steps:** - -1. Update constructor to accept: - - `flowRepository: AuthenticationFlowRepositoryPort` - - `sessionRepository: AuthenticationSessionRepositoryPort` - - `policyEngine: AuthPolicyEngine` (already exists) -2. Update `execute()` method: - - ```typescript - async execute(command: StartAuthenticationCommand): Promise { - // 1. Resolve or select flow (from command or policy) - const flow = await this.selectFlow(command); - - // 2. Create attempt with flow - const attempt = AuthenticationAttempt.start(attemptId, flow, command.identityId); - - // 3. Start flow execution - attempt.startFlow(flow); - - // 4. Execute first step - await this.executeStep(attempt, flow); - - // 5. Save attempt - await this.attemptRepository.save(attempt); - - return attemptId; - } - ``` - -3. Add method: `selectFlow(command): Promise` - - Check if command specifies flow - - Otherwise, use policy to select flow - - Default to single-step flow for method -4. Add method: `executeStep(attempt, flow): Promise` - - Get current step from flow - - Resolve auth method for step - - Call method's authenticate() - - Handle result: - - Success β†’ collect proof, advance - - Failure β†’ fail step - - Challenge β†’ await challenge -5. Add method: `handleStepResult(attempt, proof, flow): Promise` - - Add proof to attempt - - Get transition from current step - - Evaluate transition condition (if any) - - Advance to next step or terminal state -6. Add method: `finalizeAttempt(attempt): Promise` - - Only if attempt succeeded - - Create session from attempt - - Save session - - Return session - -**Acceptance Criteria:** - -- βœ… Orchestrator executes flows, not just methods -- βœ… Steps executed sequentially -- βœ… Proofs collected properly -- βœ… Sessions created from attempts -- βœ… Tests for multi-step flows -- βœ… Tests for challenge handling - ---- - -### Task 4.2: Create Flow Execution Service (Optional) - -**Priority:** Medium -**Dependencies:** Task 4.1 - -**Files to Create:** - -- `src/application/services/flow-execution.service.ts` - -**Implementation Steps:** - -1. Extract flow execution logic from orchestrator -2. Handle step-by-step execution -3. Manage challenge states -4. Handle retries and failures - -**Acceptance Criteria:** - -- βœ… Flow execution is testable independently -- βœ… Can be reused by orchestrator - ---- - -## πŸ“‹ Phase 5: Policy Domain Objects - -**Goal:** Make policies declarative and serializable - -**Duration:** ~1 week - -### Task 5.1: Create Policy Domain Objects - -**Priority:** Medium -**Dependencies:** None - -**Files to Create:** - -- `src/domain/policy/aggregates/authentication-policy.aggregate.ts` -- `src/domain/policy/value-objects/policy-rule.vo.ts` -- `src/domain/policy/value-objects/condition-expression.vo.ts` -- `src/domain/policy/value-objects/policy-action.vo.ts` -- `src/domain/policy/value-objects/policy-scope.vo.ts` - -**Implementation Steps:** - -1. Create `PolicyScope` value object: - ```typescript - PolicyScope = - | 'GLOBAL' - | 'PRINCIPAL' - | 'AUTH_METHOD' - | 'AUTH_FLOW' - | 'RESOURCE' - ``` -2. Create `ConditionExpression` value object: - ```typescript - ConditionExpression { - subject: string // e.g., 'risk.score', 'device.trusted' - operator: Operator // equals, greaterThan, in, etc. - value: unknown - } - ``` -3. Create `PolicyAction` value object: - ```typescript - PolicyAction = - | { type: 'ALLOW' } - | { type: 'DENY'; reason: string } - | { type: 'REQUIRE_STEP_UP'; requiredFactors: string[] } - | { type: 'SELECT_FLOW'; flowId: FlowId } - | { type: 'LIMIT_TRUST_LEVEL'; level: TrustLevel } - ``` -4. Create `PolicyRule` value object: - ```typescript - PolicyRule { - condition: ConditionExpression - action: PolicyAction - reason?: string // for audit - } - ``` -5. Create `AuthenticationPolicy` aggregate: - ```typescript - AuthenticationPolicy { - policyId: string - name: string - scope: PolicyScope - rules: PolicyRule[] - version: number - metadata?: Record - } - ``` -6. Add factory: `create(policyId, name, scope, rules)` -7. Add method: `evaluate(context: AuthPolicyContext): PolicyEvaluationResult` - - Evaluate rules in order - - Return first matching action -8. Add validation: - - At least one rule - - Rules are valid - -**Acceptance Criteria:** - -- βœ… Policies are immutable -- βœ… Policies are serializable -- βœ… Policies can be versioned -- βœ… Tests for policy evaluation -- βœ… Tests for serialization - ---- - -### Task 5.2: Update Policy Engine to Use Domain Objects - -**Priority:** Medium -**Dependencies:** Task 5.1 - -**Files to Update:** - -- `src/domain/policy/engine/auth-policy-engine.ts` - -**Implementation Steps:** - -1. Update to accept `AuthenticationPolicy[]` instead of `AuthPolicyEvaluator[]` -2. Call `policy.evaluate(context)` on each policy -3. Aggregate results - -**Acceptance Criteria:** - -- βœ… Engine uses domain objects -- βœ… Backward compatible if needed - ---- - -## πŸ“‹ Phase 6: Integration & Testing - -**Goal:** Integrate all pieces and ensure everything works together - -**Duration:** ~1 week - -### Task 6.1: Update Domain Exports - -**Priority:** High -**Dependencies:** All previous phases - -**Files to Update:** - -- `src/domain/index.ts` -- `src/index.ts` - -**Implementation Steps:** - -1. Export all new aggregates, entities, value objects -2. Ensure proper module boundaries - ---- - -### Task 6.2: Integration Tests - -**Priority:** High -**Dependencies:** All previous phases - -**Files to Create:** - -- `src/__tests__/integration/flow-execution.test.ts` -- `src/__tests__/integration/mfa-flow.test.ts` -- `src/__tests__/integration/trust-level-computation.test.ts` -- `src/__tests__/integration/session-creation.test.ts` - -**Test Scenarios:** - -1. Single-step password flow -2. Multi-step password + OTP flow -3. Risk-based step-up flow -4. Session creation with trust levels -5. Credential lifecycle operations -6. Policy evaluation in flows - ---- - -### Task 6.3: Update Documentation - -**Priority:** Medium -**Dependencies:** All phases - -**Files to Update:** - -- `README.md` -- `Architecture.md` (if needed) -- Add examples of flow definitions - ---- - -## πŸ“Š Implementation Timeline - -``` -Week 1-2: Phase 1 (Flow DSL & Proof System) -Week 3: Phase 2 (Trust & Session) -Week 4: Phase 3 (Credential Lifecycle) -Week 5: Phase 4 (Enhanced Orchestration) -Week 6: Phase 5 (Policy Domain Objects) -Week 7: Phase 6 (Integration & Testing) -``` - -**Total Estimated Duration:** 6-7 weeks - ---- - -## 🎯 Success Criteria - -### Phase 1 Complete When: - -- βœ… Can define authentication flows declaratively -- βœ… Attempts track flow and steps -- βœ… Proofs are collected per step -- βœ… Multi-step flows execute correctly - -### Phase 2 Complete When: - -- βœ… Sessions have trust levels -- βœ… Trust computed from proofs -- βœ… Sessions created from attempts -- βœ… Trust downgrade works - -### Phase 3 Complete When: - -- βœ… Credentials can be created/revoked -- βœ… Credential status checked during auth -- βœ… Credential lifecycle enforced - -### Phase 4 Complete When: - -- βœ… Orchestrator executes flows -- βœ… Multi-step authentication works end-to-end -- βœ… Challenge handling works - -### Phase 5 Complete When: - -- βœ… Policies are declarative -- βœ… Policies are serializable/versionable -- βœ… Policy evaluation works - -### Phase 6 Complete When: - -- βœ… All integration tests pass -- βœ… Documentation updated -- βœ… Examples provided - ---- - -## πŸ” Risk Mitigation - -### Risk 1: Breaking Changes - -**Mitigation:** - -- Keep backward compatibility where possible -- Use feature flags if needed -- Gradual migration path - -### Risk 2: Complexity - -**Mitigation:** - -- Implement incrementally -- Test each phase thoroughly -- Refactor as needed - -### Risk 3: Performance - -**Mitigation:** - -- Profile flow execution -- Optimize proof collection -- Cache flows if needed - ---- - -## πŸ“ Notes - -1. **Start with Phase 1** - Everything else depends on flows -2. **Test incrementally** - Don't wait until the end -3. **Keep architecture pure** - No infrastructure leaks -4. **Follow Definition.md** - It's the source of truth -5. **Update as you go** - Don't let docs drift - ---- - -## πŸš€ Getting Started - -1. Review this plan -2. Set up feature branch -3. Start with Task 1.1 (Flow Value Objects) -4. Write tests first (TDD approach recommended) -5. Implement incrementally -6. Review and iterate - -Good luck! πŸŽ‰ +See [TASK_BREAKDOWN.md](./TASK_BREAKDOWN.md) for issue-sized slices. diff --git a/packages/authentication/core/ONBOARDING.md b/packages/authentication/core/ONBOARDING.md index 2c72dd2..b9d5891 100644 --- a/packages/authentication/core/ONBOARDING.md +++ b/packages/authentication/core/ONBOARDING.md @@ -1,284 +1,57 @@ -# Quick Onboarding Guide: What's Missing in Auth Core +# Auth Core β€” Contributor Onboarding -## 🚨 Critical Missing Pieces +Welcome. Start here before diving into source. -### 1. **Authentication Flow DSL** (Step 2) +## 1. Read the docs (in order) -**Status:** ❌ **NOT IMPLEMENTED** -**Impact:** Cannot support multi-step auth, MFA orchestration, or conditional -flows +1. [README.md](./README.md) β€” what is exported vs in-tree +2. [Architecture.md](./Architecture.md) β€” domain model as implemented +3. [Definition.md](./Definition.md) β€” ports, registries, contracts +4. [RULES.md](./RULES.md) β€” package topology +5. [GAP_ANALYSIS.md](./GAP_ANALYSIS.md) β€” what is still missing -**What Should Exist:** +For aspirational designs (Flow DSL, Principal model), see +[FUTURE.md](./FUTURE.md). -```typescript -// Should exist but doesn't: -AuthenticationFlow { - flowId: FlowId - steps: AuthFlowStep[] - // ... -} +## 2. Understand the public API -AuthFlowStep { - stepId: string - authMethodType: AuthMethodName - onSuccess: Transition - onFailure: Transition - // ... -} -``` - -**Current Reality:** - -- `AuthOrchestratorService` directly calls `authMethod.authenticate()` -- No flow definition or step tracking -- No multi-step support - ---- - -### 2. **Flow-Driven AuthenticationAttempt** - -**Status:** ❌ **MISSING FEATURES** - -**What's Missing:** - -```typescript -// Current AuthenticationAttempt has: -- status: PENDING | SUCCEEDED | FAILED -- method: AuthMethod -- identityId?: IdentityId - -// But should also have: -- flowId: FlowId // ❌ MISSING -- currentStepId: StepId // ❌ MISSING -- collectedProofs: AuthProof[] // ❌ MISSING -- States: Initialized | InProgress | AwaitingChallenge // ❌ MISSING -``` - -**Impact:** - -- Cannot track progress through multi-step flows -- Cannot collect proofs for trust computation -- Cannot support MFA properly +Check `src/index.ts` β€” only re-exported symbols are public. Much of the tree +(MFA, OAuth, session, token) is in-tree but unpublished. ---- +## 3. Key patterns -### 3. **Auth Proof System** +- **Domain errors:** `AuthCoreErrorRegistry` + `DomainError<'NS.CODE'>` +- **Application outcomes:** `Result` from `@rineex/ddd` (v5+ β€” use `Result.err`, + not `Result.fail`) +- **Auth methods:** implement `AuthMethodPort` (see OTP package) +- **Policy:** implement `AuthPolicyEvaluator`, register with `AuthPolicyEngine` -**Status:** ❌ **NOT IMPLEMENTED** +## 4. Run locally -**What Should Exist:** - -```typescript -// Should exist but doesn't: -AuthProof { - proofType: AuthProofType - methodType: AuthMethodName - issuedAt: Date - metadata: Record -} +```bash +cd packages/authentication/core +pnpm install # from monorepo root +pnpm test +pnpm lint +pnpm check-types ``` -**Impact:** - -- Cannot track which authentication factors were used -- Cannot compute trust levels -- No audit trail of proofs - ---- - -### 4. **Credential Entity** - -**Status:** ❌ **NOT IMPLEMENTED** - -**What Should Exist:** - -```typescript -// Should exist but doesn't: -Credential { - credentialId: CredentialId - principalId: IdentityId - authMethodType: AuthMethodName - status: Active | Suspended | Revoked | Expired | Compromised - issuedAt: Date - expiresAt?: Date - lastUsedAt?: Date -} -``` - -**Impact:** - -- Cannot track credential lifecycle -- Cannot revoke credentials -- Cannot check credential status during auth -- No credential rotation support - ---- - -### 5. **AuthenticationSession Aggregate** - -**Status:** ⚠️ **PARTIALLY IMPLEMENTED** - -**Current:** `Session` entity exists but is minimal - -**What's Missing:** - -```typescript -// Current Session has: -- identityId -- token -- expiresAt -- revokedAt - -// Should also have: -- trustLevel: TrustLevel // ❌ MISSING -- contextSnapshot: ContextSnapshot // ❌ MISSING -- authFactorsUsed: AuthFactor[] // ❌ MISSING -- flowId: FlowId // ❌ MISSING -- createFromAttempt() factory // ❌ MISSING -``` - -**Impact:** - -- Cannot compute or store trust levels -- Cannot support step-up authentication -- Cannot audit authentication context - ---- - -### 6. **Trust Level System** - -**Status:** ❌ **NOT IMPLEMENTED** - -**What Should Exist:** - -```typescript -// Should exist but doesn't: -TrustLevel = Anonymous | Low | Medium | High - -// Computed from: -- Auth factors used -- Policies applied -- Risk assessment -``` - -**Impact:** - -- Cannot differentiate authentication strength -- Cannot enforce trust-based authorization -- Cannot support step-up flows - ---- - -### 7. **Policy Domain Objects** - -**Status:** ⚠️ **PARTIALLY IMPLEMENTED** - -**Current:** Only contracts exist (`AuthPolicyEvaluator`) - -**What's Missing:** - -```typescript -// Should exist but doesn't: -AuthenticationPolicy { - policyId: string - scope: PolicyScope - rules: PolicyRule[] - // ... -} - -PolicyRule { - condition: ConditionExpression - action: PolicyAction -} -``` - -**Impact:** - -- Policies are procedural, not declarative -- Cannot serialize/version policies -- Cannot configure policies via data - ---- - -## πŸ“Š Implementation Status - -| Component | Documented | Implemented | Status | -| --------------------- | ---------- | ----------- | ---------------------------------- | -| Auth Method SPI | βœ… | ⚠️ Partial | Basic structure exists | -| **Flow DSL** | βœ… | ❌ **0%** | **CRITICAL MISSING** | -| AuthenticationAttempt | βœ… | ⚠️ 40% | Basic states, no flow | -| **Auth Proof** | βœ… | ❌ **0%** | **CRITICAL MISSING** | -| **Credential** | βœ… | ❌ **0%** | **CRITICAL MISSING** | -| AuthenticationSession | βœ… | ⚠️ 30% | Basic entity, missing trust | -| **Trust Level** | βœ… | ❌ **0%** | **CRITICAL MISSING** | -| Policy Engine | βœ… | ⚠️ 50% | Contracts exist, no domain objects | -| MFA Domain | βœ… | βœ… 90% | Well implemented | -| OAuth Domain | βœ… | ⚠️ 60% | Basic structure exists | - ---- - -## πŸ” Where to Look - -### Current Working Code - -- βœ… `src/domain/identity/aggregates/authentication-attempt.aggregate.ts` - - Basic attempt tracking -- βœ… `src/domain/mfa/aggregates/mfa-session.aggregate.ts` - MFA implementation -- βœ… `src/application/services/auth-orchestrator.service.ts` - Basic - orchestration -- βœ… `src/domain/policy/engine/auth-policy-engine.ts` - Policy evaluation - -### Design Documents - -- πŸ“– `Definition.md` - Complete design (Steps 1-6) -- πŸ“– `Architecture.md` - Architectural principles -- πŸ“– `RULES.md` - Implementation rules - -### What Needs to Be Built - -1. **Flow System** - (`src/domain/identity/aggregates/authentication-flow.aggregate.ts`) -2. **Proof System** (`src/domain/identity/value-objects/auth-proof.vo.ts`) -3. **Credential Entity** (`src/domain/identity/entities/credential.entity.ts`) -4. **Trust Level** (`src/domain/session/value-objects/trust-level.vo.ts`) -5. **Session Aggregate** - (`src/domain/session/aggregates/authentication-session.aggregate.ts`) - ---- - -## 🎯 Next Steps for Implementation - -### Priority 1: Flow System (Blocks Everything) - -1. Create `AuthenticationFlow` aggregate -2. Create `AuthFlowStep` and `Transition` value objects -3. Update `AuthenticationAttempt` to track flow and steps -4. Update orchestrator to execute flows - -### Priority 2: Proof & Trust (Enables Advanced Features) - -1. Create `AuthProof` value object -2. Create `TrustLevel` value object -3. Implement proof collection in attempts -4. Compute trust levels from proofs - -### Priority 3: Credential & Session (Security & Audit) +## 5. Where to add code -1. Create `Credential` entity -2. Create `AuthenticationSession` aggregate -3. Implement session creation from attempts -4. Add credential status checks +| Change | Location | +| ------------------ | ----------------------------------------- | +| New auth method | `packages/authentication/methods//` | +| Core domain rule | `src/domain/` | +| New outbound port | `src/ports/outbound/` | +| Flow orchestration | `src/application/services/` | ---- +## 6. Common pitfalls -## πŸ’‘ Key Insights +- Do not assume Principal/Credential aggregates exist β€” use `Identity` entity +- Passwordless is not on `AuthMethodPort` yet +- Two types named `Identity` β€” check import path +- `ApplicationServicePort` returns `Promise`, not `Result` -1. **Current orchestrator is too simple** - It just calls methods directly, no - flow execution -2. **No proof tracking** - Cannot audit what factors were used -3. **No trust levels** - Cannot differentiate authentication strength -4. **No credential lifecycle** - Security features missing -5. **Policies are procedural** - Should be declarative/data-driven +## 7. Next tasks -**The core is ~40% complete** - Foundation exists but critical orchestration -pieces are missing. +See [TASK_BREAKDOWN.md](./TASK_BREAKDOWN.md) for grab-able work items. diff --git a/packages/authentication/core/README.md b/packages/authentication/core/README.md index ff4eae7..f3d37b7 100644 --- a/packages/authentication/core/README.md +++ b/packages/authentication/core/README.md @@ -1,35 +1,65 @@ -# Auth Core +# @rineex/auth-core -Core authentication and authorization package for Rineex framework. +Core authentication package for the Rineex framework. Built on `@rineex/ddd` +with a plugin model for auth methods. -## Features +## What Is Implemented -- Authentication abstractions -- Authorization utilities -- Social login support -- OTP functionality -- Passwordless authentication -- SSO integration +| Area | Status | +| ---------------------------------------------- | --------------------------------------------------------- | +| Authentication attempt aggregate + events | Exported | +| Identity entity + value objects | Exported | +| Policy engine (`AuthPolicyEngine`) | Exported | +| `AuthMethodPort` + flow orchestration services | Port exported; services in-tree | +| OTP method adapter | `@rineex/authentication-method-otp` | +| Passwordless challenge domain | `@rineex/authentication-method-passwordless` (standalone) | +| MFA session domain + services | In-tree, not exported | +| OAuth authorization domain | In-tree, not exported | +| Session / token domains | In-tree, not exported | + +**Not implemented:** SSO, social login, credential lifecycle, Flow DSL, trust +levels. ## Installation ```bash -pnpm add @rineex/auth-core +pnpm add @rineex/auth-core @rineex/ddd ``` +## Documentation + +| Document | Description | +| ----------------------------------------------------- | ------------------------------------------ | +| [Architecture.md](./Architecture.md) | Domain model and layering (as implemented) | +| [Definition.md](./Definition.md) | Contracts, registries, ports | +| [RULES.md](./RULES.md) | Package topology and port map | +| [GAP_ANALYSIS.md](./GAP_ANALYSIS.md) | Remaining work | +| [ONBOARDING.md](./ONBOARDING.md) | Contributor guide | +| [Domain errors](./src/domain/errors/README.md) | Error classes and codes | +| [Value objects](./src/domain/value-objects/README.md) | VO reference | +| [FUTURE.md](./FUTURE.md) | Aspirational design (not implemented) | + +## Quick Example + +```typescript +import { + AuthenticationAttempt, + AuthAttemptId, + AuthMethodPort, +} from '@rineex/auth-core'; + +// Register an AuthMethodPort implementation (e.g. OtpAuthMethod) at composition root +// Wire AuthenticationAttemptRepositoryPort, IdentityRepository, DomainEventPublisherPort +``` + +See `@rineex/authentication-method-otp` for a complete method adapter. + ## Development ```bash -# Build the package pnpm build - -# Run tests pnpm test - -# Lint code pnpm lint - -# Check types pnpm check-types ``` diff --git a/packages/authentication/core/ROADMAP.md b/packages/authentication/core/ROADMAP.md index 96b2036..cd26584 100644 --- a/packages/authentication/core/ROADMAP.md +++ b/packages/authentication/core/ROADMAP.md @@ -1,293 +1,38 @@ -# Implementation Roadmap: Visual Overview +# Auth Core β€” Roadmap -## πŸ—ΊοΈ High-Level Roadmap +Phased plan from current state. Timelines are indicative. -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ PHASE 1: Foundation β”‚ -β”‚ Flow DSL + Proof System (Critical Path) β”‚ -β”‚ Duration: 2-3 weeks β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Flow VOs β”‚β†’ β”‚ Flow Agg β”‚β†’ β”‚ Proof β”‚β†’ β”‚ Attempt β”‚ β”‚ -β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ System β”‚ β”‚ Update β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - ↓ -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ PHASE 2: Trust & Session β”‚ -β”‚ Duration: 1-2 weeks β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Trust β”‚β†’ β”‚ Context β”‚β†’ β”‚ Session β”‚ β”‚ -β”‚ β”‚ Level β”‚ β”‚ Snapshot β”‚ β”‚ Agg β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - ↓ -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ PHASE 3: Credentials β”‚ -β”‚ Duration: 1-2 weeks β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚Credentialβ”‚β†’ β”‚Credentialβ”‚ β”‚ -β”‚ β”‚ VOs β”‚ β”‚ Entity β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - ↓ -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ PHASE 4: Orchestration β”‚ -β”‚ Duration: 1 week β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Update AuthOrchestratorService β”‚ β”‚ -β”‚ β”‚ Execute flows, collect proofs β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - ↓ -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ PHASE 5: Policies β”‚ -β”‚ Duration: 1 week β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Policy Domain Objects β”‚ β”‚ -β”‚ β”‚ Declarative, Serializable β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - ↓ -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ PHASE 6: Integration β”‚ -β”‚ Duration: 1 week β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Tests + Documentation β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` +## Phase 1 β€” Integration consistency (current focus) ---- +- Export passwordless runtime API from package entry +- Add `PasswordlessAuthMethod` implementing `AuthMethodPort` +- Wire passwordless into flow orchestration services +- Implement email/SMS channel adapters (or document consumer responsibility) -## πŸ“… Timeline View +## Phase 2 β€” Publish in-tree domains -``` -Week 1: [════════════════════════════════════════════════════════] - Phase 1: Flow Value Objects + Flow Aggregate +- Export MFA session domain + services (or subpath `@rineex/auth-core/mfa`) +- Complete `OAuthAuthorizeService` and export OAuth domain +- Export session and token domains with clear stability tiers +- Resolve `Identity` entity vs aggregate naming -Week 2: [════════════════════════════════════════════════════════] - Phase 1: Proof System + Attempt Update +## Phase 3 β€” Policy and risk -Week 3: [════════════════════════════════════════════════════════] - Phase 2: Trust Level + Session Aggregate +- Expand `AuthPolicyRegistry` beyond `base` +- Populate `RiskSignalRegistry` and wire evaluators +- Step-up MFA flow end-to-end through policy engine -Week 4: [════════════════════════════════════════════════════════] - Phase 3: Credential Lifecycle +## Phase 4 β€” Platform features (from FUTURE.md) -Week 5: [════════════════════════════════════════════════════════] - Phase 4: Orchestration Update +- Flow DSL (data-driven authentication flows) +- Credential lifecycle +- Trust levels on sessions +- Additional methods: password, passkeys, social, API tokens -Week 6: [════════════════════════════════════════════════════════] - Phase 5: Policy Domain Objects +## Completed (documentation sync) -Week 7: [════════════════════════════════════════════════════════] - Phase 6: Integration & Testing -``` +- Rewrote Architecture, Definition, RULES to match implementation +- Synced docs with `@rineex/ddd` v5/v6 (Result union, registry errors) +- Created FUTURE.md preserving aspirational design ---- - -## πŸ”— Dependency Graph - -``` - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ Flow VOs β”‚ - β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” - β”‚ Flow Agg β”‚ - β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” - β”‚ Proof β”‚ - β”‚ System β”‚ - β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ β”‚ β”‚ -β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” -β”‚ Attempt β”‚ β”‚ Trust Level β”‚ β”‚ Credential β”‚ -β”‚ Update β”‚ β”‚ + Session β”‚ β”‚ Entity β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ - β”‚ β”‚ β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” - β”‚Orchestrator β”‚ - β”‚ Update β”‚ - β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” - β”‚ Policies β”‚ - β”‚ Domain Obj β”‚ - β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” - β”‚Integration β”‚ - β”‚ & Tests β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` - ---- - -## 🎯 Milestones - -### Milestone 1: Flow System Complete βœ… - -**Target:** End of Week 2 -**Deliverables:** - -- βœ… Flow DSL implemented -- βœ… Proofs collected -- βœ… Attempts track flow progress - -**Definition of Done:** - -- Can define multi-step flows -- Attempts execute flows step-by-step -- Proofs collected per step -- Unit tests passing - ---- - -### Milestone 2: Trust & Session Complete βœ… - -**Target:** End of Week 3 -**Deliverables:** - -- βœ… Trust levels computed -- βœ… Sessions created from attempts -- βœ… Context snapshots captured - -**Definition of Done:** - -- Trust computed from proofs -- Sessions have trust levels -- Can create session from attempt -- Unit tests passing - ---- - -### Milestone 3: Credentials Complete βœ… - -**Target:** End of Week 4 -**Deliverables:** - -- βœ… Credential lifecycle -- βœ… Status checks during auth - -**Definition of Done:** - -- Credentials can be created/revoked -- Status checked during authentication -- Unit tests passing - ---- - -### Milestone 4: End-to-End Flow βœ… - -**Target:** End of Week 5 -**Deliverables:** - -- βœ… Orchestrator executes flows -- βœ… Multi-step auth works - -**Definition of Done:** - -- Can authenticate with multi-step flow -- Proofs collected correctly -- Session created successfully -- Integration tests passing - ---- - -### Milestone 5: Production Ready βœ… - -**Target:** End of Week 7 -**Deliverables:** - -- βœ… All features implemented -- βœ… Tests passing -- βœ… Documentation updated - -**Definition of Done:** - -- All phases complete -- Integration tests passing -- Documentation updated -- Examples provided - ---- - -## πŸ“Š Progress Tracking - -### Phase 1: Foundation - -- [ ] Task 1.1: Flow Value Objects -- [ ] Task 1.2: Flow Aggregate -- [ ] Task 1.3: Proof System -- [ ] Task 1.4: Attempt Update -- [ ] Task 1.5: Flow Repository Port - -### Phase 2: Trust & Session - -- [ ] Task 2.1: Trust Level VO -- [ ] Task 2.2: Context Snapshot VO -- [ ] Task 2.3: Session Aggregate -- [ ] Task 2.4: Session Repository Port - -### Phase 3: Credentials - -- [ ] Task 3.1: Credential VOs -- [ ] Task 3.2: Credential Entity -- [ ] Task 3.3: Credential Repository Port - -### Phase 4: Orchestration - -- [ ] Task 4.1: Orchestrator Update -- [ ] Task 4.2: Flow Execution Service (Optional) - -### Phase 5: Policies - -- [ ] Task 5.1: Policy Domain Objects -- [ ] Task 5.2: Policy Engine Update - -### Phase 6: Integration - -- [ ] Task 6.1: Domain Exports -- [ ] Task 6.2: Integration Tests -- [ ] Task 6.3: Documentation - ---- - -## 🚦 Status Legend - -- πŸ”΄ **Not Started** - Task not begun -- 🟑 **In Progress** - Actively working -- 🟒 **Complete** - Done and tested -- ⚠️ **Blocked** - Waiting on dependency - ---- - -## πŸ’‘ Quick Reference - -**Start Here:** Task 1.1 (Flow Value Objects) -**Critical Path:** Phase 1 β†’ Phase 4 -**Can Parallelize:** Phase 2 & 3 (after Phase 1) -**Final Step:** Phase 6 (Integration) - ---- - -## πŸ“ Notes - -- Each task should have tests written first (TDD) -- Review code after each phase -- Update documentation as you go -- Don't skip validation logic -- Keep architecture pure (no infra leaks) +See [GAP_ANALYSIS.md](./GAP_ANALYSIS.md) for detailed gap list. diff --git a/packages/authentication/core/RULES.md b/packages/authentication/core/RULES.md index fe68b47..282da9b 100644 --- a/packages/authentication/core/RULES.md +++ b/packages/authentication/core/RULES.md @@ -1,1470 +1,89 @@ -# Step 7 β€” Port & Adapter Map (with Bundle-Size & Modularization First) +# Auth Core β€” Package Topology and Port Map -This step defines **what gets compiled together, what is optional, and what can -be tree-shaken**. +Actual layout as implemented. The `@auth/core-domain` / `@auth/core-ports` split +described in earlier drafts was never built β€” everything lives in +`@rineex/auth-core` with method packages alongside. -If this is wrong, your β€œinstall only what you need” promise is impossible. - ---- - -## 1. Hard Requirement (Restated Clearly) - -Your auth core must satisfy **all** of these: - -- Zero required adapters -- Zero required auth methods -- Zero required storage -- Domain usable alone -- Each auth method installable separately -- Each adapter installable separately -- No transitive bloat -- Tree-shakable -- Side-effect free modules - -If any package pulls more than it needs β†’ fail. - ---- - -## 2. High-Level Package Topology (Non-Negotiable) - -This is the **only sane layout**: - -``` -@auth/core-domain ← pure domain (mandatory) -@auth/core-ports ← domain ports (mandatory) - -@auth/method-password -@auth/method-passwordless -@auth/method-otp -@auth/method-oauth -@auth/method-passkey -... - -@auth/adapter-memory -@auth/adapter-postgres -@auth/adapter-redis -@auth/adapter-http -@auth/adapter-jwt -@auth/adapter-oidc -... -``` - -### Rules - -- `core-domain` depends on **nothing** -- `core-ports` depends only on `core-domain` -- Methods depend on `core-domain + core-ports` -- Adapters depend on `core-ports` -- No reverse dependencies. Ever. - ---- - -## 3. What Lives in `@auth/core-domain` - -**Only meaning and rules.** - -Includes: - -- Aggregates -- Entities -- Value Objects -- Domain Services -- Invariants -- Domain Events - -Explicitly excluded: - -- Interfaces to infra -- Repositories -- Verification logic -- Serialization -- Config loading - -Bundle size: **tiny** This package should never change often. - ---- - -## 4. What Lives in `@auth/core-ports` - -This is the **only dependency surface** for extensions. - -### 4.1 Persistence Ports - -```ts -PrincipalRepository; -CredentialRepository; -AuthenticationAttemptRepository; -AuthenticationSessionRepository; -``` - -Rules: - -- No SQL -- No ORM -- No query builders -- No pagination logic - ---- - -### 4.2 Capability / SPI Ports - -```ts -AuthMethodRegistry; -AuthProofVerifier; -ChallengeIssuer; -RiskEvaluator; -TokenIssuer; -SessionRepresentationFactory; -``` - -Rules: - -- Stateless interfaces -- No default implementations -- No optional methods -- No framework types - ---- - -## 5. Auth Methods as Separately Installable Units - -Each auth method is **one package**. - -Example: - -``` -@auth/method-password -``` - -Contains: - -- AuthMethodDefinition -- Required input schema (domain-level) -- Proof mapping -- Registration function - -Does NOT contain: - -- Hashing logic -- DB access -- HTTP handling -- SDKs - -This guarantees: - -- Installing OTP does not install OAuth -- Installing OAuth does not install JWT -- Bundle size stays minimal - ---- - -## 6. Adapter Packages (Infra Only) - -Adapters implement **one port group only**. - -Bad ❌ - -> one adapter doing DB + HTTP + JWT - -Good βœ… +## Package Topology ``` -@auth/adapter-postgres -@auth/adapter-redis -@auth/adapter-jwt -@auth/adapter-http-express -@auth/adapter-http-hono -``` - -Each adapter: - -- Implements interfaces from `core-ports` -- Knows nothing about domain internals -- Can be swapped freely - ---- - -## 7. Dependency Direction (Strict) - -This diagram must never be violated: - -``` -[ core-domain ] - ↑ -[ core-ports ] - ↑ -[ methods ] [ adapters ] -``` - -Never: - -- Adapter β†’ Method -- Method β†’ Adapter -- Domain β†’ Port implementation -- Domain β†’ Method - ---- - -## 8. Tree-Shaking & Bundle Rules - -To make bundle-size guarantees real: - -- No side effects in module root -- No auto-registration -- Explicit `register()` calls -- Pure ES modules -- No global singletons -- No reflection-based loading - -If a user doesn’t import it β†’ it must not exist in bundle. - ---- - -## 9. Runtime Composition (User Responsibility) - -The **consumer** wires things together: - -- Registers methods -- Provides adapters -- Selects flows -- Configures policies - -Your core: - -- Does not auto-wire -- Does not scan -- Does not assume environment - -This keeps: - -- Core small -- Behavior explicit -- Enterprise-safe - ---- - -## 10. Validation Checklist (Must Pass) - -Before moving forward: - -βœ” Core domain installs alone βœ” No auth method is mandatory βœ” No adapter is -mandatory βœ” Importing one method doesn’t pull others βœ” Infra choices are -replaceable βœ” Bundle analyzer shows only used code - -If any fails β†’ restructure now. - ---- - -## Step 7 Status - -βœ… Port boundaries defined βœ… Modular packaging model defined βœ… Bundle-size -safe βœ… Tree-shakable βœ… Enterprise-usable - ---- - -# Step 9 β€” Minimal End-to-End Walkthrough (Passwordless) - -> Goal: Show a complete authentication cycle using **Passwordless Email**, fully -> abstract, modular, and documented. - ---- - -## 1. Module Setup (Separable & Installable) - -``` -@auth/core-domain -@auth/core-ports -@auth/method-passwordless -@auth/adapter-memory (optional for demonstration) -``` - -Rules: - -- Core-domain + core-ports always installed -- Method installed separately -- Adapter optional -- No infra dependencies required - ---- - -## 2. Example Domain Objects Involved - -```ts -// AuthenticationAttempt aggregate -const attempt = AuthenticationAttempt.create({ - flowId: 'passwordless_flow', - principalId: 'user-123', -}); - -// Principal entity exists -const principal = Principal.create({ id: 'user-123' }); - -// Credential for Passwordless (domain-only) -const credential = Credential.create({ - principalId: principal.id, - methodType: 'passwordless_email', - factor: 'possession', - status: 'Active', -}); -``` - -**Notes:** - -- All objects are **domain-pure**. -- No secrets stored in domain. -- All lifecycle and invariants enforced. - ---- - -## 3. AuthMethod Registration (SPI Usage) - -```ts -const registry: IAuthMethodRegistry = new AuthMethodRegistry(); - -// Passwordless Method (installed separately) -const passwordlessMethod: IAuthMethod<{ email: string }, AuthProof> = - new PasswordlessEmailMethod(); - -registry.register(passwordlessMethod); +packages/authentication/ +β”œβ”€β”€ core/ @rineex/auth-core +β”‚ β”œβ”€β”€ domain/ identity, policy, mfa, oauth, session, token +β”‚ β”œβ”€β”€ application/ flow + MFA services (unexported) +β”‚ β”œβ”€β”€ ports/ inbound + outbound +β”‚ └── types/ registries +└── methods/ + β”œβ”€β”€ otp/ @rineex/authentication-method-otp + └── passwordless/ @rineex/authentication-method-passwordless ``` -**Documentation Notes:** +No `adapters/`, `orchestration/`, or `policies/` packages exist at this level. +Framework adapters are consumer responsibilities. -- Registration explicit, tree-shakable. -- SPI ensures domain does not know implementation. -- Developers can see registry calls in code history for traceability. - ---- - -## 4. Authentication Flow Definition (DSL) - -```ts -const passwordlessFlow = new AuthenticationFlow({ - flowId: 'passwordless_flow', - name: 'Passwordless Email Flow', - steps: [ - { - stepId: 'email_link_step', - authMethodType: 'passwordless_email', - required: true, - onSuccess: { targetStepId: 'terminal_success' }, - onFailure: { targetStepId: 'terminal_failed' }, - }, - ], - entryConditions: [], - exitConditions: [], -}); -``` - -**Documentation Notes:** - -- Flow is declarative and versionable. -- Steps link to AuthMethodType. -- Developers reading code see the exact orchestration chain. -- Policy references are explicit (future step-up integration). - ---- - -## 5. Creating AuthenticationAttempt - -```ts -attempt.startFlow(passwordlessFlow); -``` - -- Creates domain snapshot -- Status = `InProgress` -- Step = first step -- Immutable reference to flow - -**Documentation Notes:** - -- All state transitions logged in code via events -- Chainable domain events can be traced to attempt creation - ---- - -## 6. Issue Challenge (Passwordless Email Example) - -```ts -const challenge = await passwordlessMethod.issueChallenge?.({ - principalId: principal.id, - attemptId: attempt.id, - metadata: { email: 'user@example.com' }, -}); -``` - -- Returns a Challenge object (domain-agnostic) -- No SMTP logic in domain -- Code clearly separates **domain vs infra** -- Challenge metadata traceable in code comments - ---- - -## 7. Verify Challenge / Authenticate Step - -```ts -const proof = await passwordlessMethod.verifyChallenge?.( - { - challengeId: challenge.id, - response: 'user-clicked-link', - }, - { principalId: principal.id, attemptId: attempt.id }, -); - -if (proof) { - attempt.completeStep(proof); -} -``` - -**Documentation Notes:** - -- Proof is immutable and domain-pure -- Chainable: attempt -> proof -> session creation -- Developer reading this sees exact flow progression - ---- - -## 8. Session Creation - -```ts -const session = AuthenticationSession.createFromAttempt(attempt, { - trustLevel: 'Medium', -}); -``` - -- Generates domain session only -- No tokens, cookies, or DB -- TrustLevel reflects factors used -- ContextSnapshot stored for audit - -**Documentation Notes:** - -- Sessions are traceable back to attempt -- Code comments and JSDoc describe domain reasoning -- Any developer reading can follow entire chain - ---- - -## 9. Policy Integration (Optional Hook) - -```ts -const policyResult = policyEvaluator.evaluate({ attempt, session, context }); -if (policyResult.decision === 'RequireStepUp') { - // Step-up flow can be chained -} -``` - -**Notes:** - -- Policies are referenced, not hard-coded -- Fully traceable, chainable in domain events - ---- - -## 10. Summary of Traceable Chain - -``` -Principal -> Credential -> AuthFlow -> AuthenticationAttempt -> Challenge -> AuthProof -> AuthenticationSession -``` - -**All events are documented, chainable, and domain-pure**. Developers reading -code can **trace every step** without touching infra. - ---- - -## 11. Developer Documentation Strategy (Recommended) - -- Each class/interface has **JSDoc** explaining: - - Purpose - - Usage - - Domain invariants - - Chainable events - - Hooks for adapters - -- SPI registration, flows, attempts, proofs, sessions β€” **all documented** -- Link to external docs: - - Passwordless installation guide - - Adapter setup - - Policy DSL examples - -- Optional: `@link` in JSDoc for chainable navigation - ---- - -## Step 9 Status - -βœ… Minimal end-to-end flow defined (Passwordless) βœ… Domain-only, infra-agnostic -βœ… Traceable chain with chainable documentation βœ… Modular, tree-shakable, -extensible - ---- - -# Step 10 β€” Policy DSL Formalization - -> Goal: Enable fully declarative policies for authentication, step-up, risk, and -> multi-factor flows. Policies must be **versionable, chainable, and -> traceable**. - ---- - -## 1. Core Principles - -- Policies are **data**, not logic. -- Evaluated by a domain service (e.g., `AuthenticationPolicyEvaluator`). -- Traceable: each policy evaluation leaves a chainable record. -- Extensible: new conditions, actions, or flows can be added without code - change. -- Enterprise-ready: multi-tenant, step-up, conditional flows supported. - ---- - -## 2. Policy Structure - -```ts -interface AuthenticationPolicy { - /** - * Unique identifier - */ - policyId: string; - - /** - * Human-readable name - */ - name: string; - - /** - * Scope of the policy: global, flow, principal, method - */ - scope: PolicyScope; - - /** - * Ordered rules - */ - rules: PolicyRule[]; - - /** - * Optional metadata for auditing - */ - metadata?: Record; -} -``` - ---- - -## 3. PolicyScope - -```ts -type PolicyScope = - | 'Global' - | 'Principal' - | 'AuthMethod' - | 'AuthFlow' - | 'Resource'; -``` - -- Declarative scope ensures traceability. -- Policies applied to same subject are evaluated in order. - ---- - -## 4. PolicyRule - -```ts -interface PolicyRule { - /** - * Condition to evaluate - */ - condition: ConditionExpression; - - /** - * Action if condition is true - */ - action: PolicyAction; - - /** - * Optional reason for audit trail - */ - reason?: string; -} -``` - -- Rules are **atomic**, traceable, and versionable. -- Reason field ensures **chainable audit logging**. - ---- - -## 5. ConditionExpression - -```ts -interface ConditionExpression { - subject: string; // e.g., 'risk.score', 'device.trusted', 'principal.type' - operator: Operator; // equals, notEquals, greaterThan, lessThan, in, notIn - value: unknown; // domain-agnostic value -} -``` - -- Minimal, domain-agnostic -- Serializable -- Developers can trace which condition triggered which action - -### Operators - -```ts -type Operator = - | 'equals' - | 'notEquals' - | 'greaterThan' - | 'lessThan' - | 'in' - | 'notIn'; -``` - ---- - -## 6. PolicyAction - -```ts -type PolicyAction = - | { type: 'Allow' } - | { type: 'Deny' } - | { type: 'RequireStepUp'; requiredFactors: string[] } - | { type: 'SelectFlow'; flowId: string } - | { type: 'LimitTrustLevel'; level: TrustLevel }; -``` - -- Fully declarative -- Chainable -- Traceable in logs -- Supports step-up, conditional flows, multi-factor, trust downgrades - ---- - -## 7. Policy Evaluation Result - -```ts -interface PolicyEvaluationResult { - decision: 'Allow' | 'Deny' | 'StepUpRequired' | 'FlowSelected'; - triggeredRules: PolicyRule[]; - metadata?: Record; -} -``` - -- All rules evaluated or short-circuited -- `triggeredRules` allows **audit trail chaining** -- Metadata includes reason, timestamp, and evaluation context - ---- - -## 8. Policy Evaluation Flow (Abstract) - -```ts -class AuthenticationPolicyEvaluator { - async evaluate( - context: AuthEvaluationContext, - policies: AuthenticationPolicy[], - ): Promise { - // Abstract: domain only - // Implementations will run the rules, produce decision, and trigger events - } -} -``` - -- `AuthEvaluationContext` includes attempt, session, principal, device, risk, - etc. -- Result is **domain-only**, no infra logic. -- Each evaluation is **traceable, versionable, chainable**. - ---- - -## 9. Example Policy (Passwordless Step-Up) - -```ts -const highRiskStepUpPolicy: AuthenticationPolicy = { - policyId: 'policy-001', - name: 'High Risk Step-Up', - scope: 'Global', - rules: [ - { - condition: { subject: 'risk.score', operator: 'greaterThan', value: 70 }, - action: { type: 'RequireStepUp', requiredFactors: ['otp'] }, - reason: 'High-risk login requires additional verification', - }, - ], -}; -``` - -- Traceable in logs (`policyId`, `reason`) -- Serializable for storage or audit -- Extensible for enterprise tenants - ---- - -## 10. Multi-Rule & Flow Selection Example - -```ts -const enterpriseFlowPolicy: AuthenticationPolicy = { - policyId: 'policy-002', - name: 'Enterprise Flow Selector', - scope: 'Principal', - rules: [ - { - condition: { - subject: 'principal.type', - operator: 'equals', - value: 'Service', - }, - action: { type: 'SelectFlow', flowId: 'm2m_flow' }, - reason: 'Service accounts must use machine-to-machine flow', - }, - ], -}; -``` +## Dependency Rules -- Policies **do not execute flows**, just select them -- Chainable and auditable +| Package | May depend on | +| -------------------------------------------- | ------------------------------------- | +| `@rineex/auth-core` | `@rineex/ddd` | +| `@rineex/authentication-method-otp` | `@rineex/auth-core`, `@rineex/ddd` | +| `@rineex/authentication-method-passwordless` | `@rineex/auth-core`, `@rineex/ddd` | +| Consumer apps | Any of the above + their own adapters | ---- +Method packages must not import each other. -## 11. Documentation & Traceability Rules +## Inbound Ports -- Every policy and rule must have: - - `policyId` - - `reason` - - Optional metadata linking to docs or JSDoc +| Port | Path | Implemented by | +| ---------------------------- | ------------------------------------- | --------------- | +| `AuthMethodPort` | `ports/inbound/auth-method.port.ts` | `OtpAuthMethod` | +| `StartAuthenticationCommand` | `ports/inbound/start-auth.command.ts` | Consumer | -- All evaluations **emit triggeredRules** β†’ chainable audit trail -- Developers can **follow evaluation chain in code** and see exact reasoning +## Outbound Ports (exported) ---- +| Port | Path | +| ------------------------------------- | ---------------------------------------------------------- | +| `AuthenticationAttemptRepositoryPort` | `ports/outbound/authentication-attempt.repository.port.ts` | +| `IdentityRepository` | `ports/outbound/identity.repository.outbound.port.ts` | +| `DomainEventPublisherPort` | `ports/outbound/domain-event-publisher.port.ts` | -## 12. Extensibility Guarantees +## Outbound Ports (in-tree) -- New operators: add to `Operator` enum -- New actions: extend `PolicyAction` union -- New condition subjects: extend `AuthEvaluationContext` -- All changes are **modular, traceable, and versionable** - ---- - -## Step 10 Status - -βœ… Policy DSL formalized βœ… Declarative, auditable, traceable βœ… -Enterprise-ready (step-up, multi-flow, trust-level) βœ… Chainable in code for -developer reference - ---- - -# Step 11 β€” Multi-Tenant / Enterprise Extension Model - -> Goal: Enable the auth core to serve multiple tenants or enterprise clients -> with isolated configurations, policies, flows, and custom auth methods, -> without changing core domain. - ---- - -## 1. Core Principles - -- Each tenant can have **custom flows, policies, and auth methods**. -- Domain objects remain **tenant-agnostic**. -- Configuration is **composable**, versioned, and auditable. -- No core domain changes required for new tenants. -- Traceable: every domain object records **tenant context**. - ---- - -## 2. Tenant Context (Value Object) - -```ts -interface TenantContext { - tenantId: string; - name?: string; - metadata?: Record; -} -``` - -- Attached to **Principal, AuthenticationAttempt, AuthenticationSession, Policy - evaluation**. -- Provides **traceable chain** of which tenant a domain object belongs to. -- Supports **multi-tenant logging and auditing**. - ---- - -## 3. Tenant-Specific Flows - -- Flows can be defined **per tenant**. -- Example: - -```ts -const tenantFlow = new AuthenticationFlow({ - flowId: 'enterprise_passwordless', - tenantId: 'tenant-123', - name: 'Enterprise Passwordless Flow', - steps: [ - { - stepId: 'email_link_step', - authMethodType: 'passwordless_email', - required: true, - }, - ], -}); -``` - -- `tenantId` ensures flows are isolated. -- Developers can **trace flows by tenant**. - ---- - -## 4. Tenant-Specific Policies - -- Policies are scoped per tenant. -- Example: - -```ts -const enterprisePolicy: AuthenticationPolicy = { - policyId: 'policy-tenant-001', - name: 'Tenant 123 MFA Policy', - scope: 'Principal', - rules: [ - { - condition: { subject: 'risk.score', operator: 'greaterThan', value: 50 }, - action: { type: 'RequireStepUp', requiredFactors: ['otp'] }, - reason: 'Tenant requires additional verification for medium-risk logins', - }, - ], - metadata: { tenantId: 'tenant-123' }, -}; -``` - -- Policies are **versioned** and **audit-traceable** per tenant. -- Chainable evaluation ensures **who read/triggered each policy** is recorded. - ---- - -## 5. Tenant-Specific Auth Methods - -- Methods can be **enabled or disabled per tenant**. -- Registration API supports tenant-scoping: - -```ts -tenantRegistry.register('tenant-123', passwordlessMethod); -``` - -- Core domain sees only abstract `IAuthMethod`. -- Tree-shakable: uninstalled methods do not affect bundle size. - ---- - -## 6. Tenant-Specific Credential Configuration - -- Credential lifecycles can be configured per tenant: - - Expiration time - - Rotation rules - - Step-up requirements - -- Credentials remain domain-agnostic; tenant-specific behavior enforced via - policies and adapters. - ---- - -## 7. Tenant Context in AuthenticationAttempt - -- Attach `tenantId` to each attempt: - -```ts -const attempt = AuthenticationAttempt.create({ - flowId: 'enterprise_passwordless', - principalId: 'user-456', - tenantId: 'tenant-123', -}); -``` - -- Ensures **flow, policies, session, and proofs** are evaluated within tenant - scope. -- Supports **audit and traceability** across multi-tenant systems. - ---- - -## 8. Tenant Context in AuthenticationSession - -- Session includes tenant metadata: - -```ts -const session = AuthenticationSession.createFromAttempt(attempt, { - trustLevel: 'Medium', - tenantContext: { tenantId: 'tenant-123' }, -}); -``` - -- Downstream authorization can enforce **tenant isolation**. -- Chainable: developers can trace session β†’ attempt β†’ tenant. - ---- - -## 9. Extensibility & Overrides - -- Tenants can: - - Add custom flows - - Override default policies - - Enable/disable auth methods - - Adjust trust-level rules - -- Core domain is **unchanged**. -- Chainable documentation ensures each override is traceable. - ---- - -## 10. Multi-Tenant Repository Pattern - -```ts -interface TenantAwareRepository { - findByTenant(tenantId: string, id: string): Promise; - saveForTenant(tenantId: string, entity: T): Promise; -} -``` - -- Ensures **tenant isolation at domain level**. -- Infrastructure handles actual storage, domain only sees tenant-scoped - interfaces. - ---- - -## 11. Audit and Traceability - -- Every domain event, policy evaluation, session creation, or flow execution: - - Includes `tenantId` - - Includes `principalId` - - Optional metadata for chainable docs or code references - -- Developers reading code can follow **exact tenant-specific logic**. - ---- - -## 12. Step 11 Status - -βœ… Multi-tenant support defined βœ… Tenant-scoped flows, policies, auth methods, -sessions, attempts βœ… Chainable, traceable for audit βœ… Core domain remains -tenant-agnostic βœ… Enterprise-ready, tree-shakable, modular - ---- - -# Step 12 β€” Additional AuthMethod Implementations (Abstract) - -> Goal: Provide modular contracts and structure for common auth methods beyond -> Passwordless, ensuring traceability, enterprise readiness, and minimal bundle -> size. - ---- - -## 1. Module Layout - -``` -@auth/method-otp -@auth/method-oauth -@auth/method-passkey -``` - -- Each package is **optional**. -- Depends only on: - - `@auth/core-domain` - - `@auth/core-ports` - -- Does **not depend** on other methods or adapters. - ---- - -## 2. OTP Method (Abstract) - -### Purpose - -- One-time codes delivered via email, SMS, or push. -- Supports **step-up, MFA, or primary auth**. - -### SPI Skeleton - -```ts -export interface IOtpMethod extends IAuthMethod< - { destination: string }, - AuthProof -> { - /** - * Generate and issue OTP challenge - */ - issueChallenge( - context: AuthContext, - options?: { length?: number; ttl?: number }, - ): Promise; - - /** - * Verify OTP response - */ - verifyChallenge( - response: ChallengeResponse, - context: AuthContext, - ): Promise; -} -``` - -**Documentation Notes:** - -- `destination` is transport-agnostic. -- Challenge and proof remain domain-pure. -- Developers can trace OTP flow from attempt β†’ challenge β†’ proof β†’ session. - ---- - -## 3. OAuth Method (Abstract) - -### Purpose - -- Delegated authentication via third-party providers (Google, Azure, etc.) -- Supports SSO and enterprise logins. - -### SPI Skeleton - -```ts -export interface IOAuthMethod extends IAuthMethod< - { code: string; provider: string }, - AuthProof -> { - /** - * Initiate OAuth authorization - */ - initiateAuth(context: AuthContext, provider: string): Promise; // URL to redirect - - /** - * Complete OAuth authentication with provider code - */ - completeAuth(code: string, context: AuthContext): Promise; -} -``` - -**Documentation Notes:** - -- Domain sees only `AuthProof`. -- No HTTP or token parsing in domain. -- Chainable: principal β†’ attempt β†’ proof β†’ session β†’ policy evaluation. - ---- - -## 4. Passkey / WebAuthn Method (Abstract) - -### Purpose - -- Hardware-backed or platform credentials (biometrics, security keys) -- Supports phishing-resistant enterprise login - -### SPI Skeleton - -```ts -export interface IPasskeyMethod extends IAuthMethod< - { clientData: unknown }, - AuthProof -> { - /** - * Initiate passkey registration or authentication - */ - initiate( - context: AuthContext, - options?: { type: 'register' | 'authenticate' }, - ): Promise; - - /** - * Verify passkey response - */ - verify(response: ChallengeResponse, context: AuthContext): Promise; -} -``` - -**Documentation Notes:** - -- `clientData` is abstracted; adapter handles platform specifics. -- Challenge/proof remain domain-pure. -- Developers can trace registration/authentication per attempt. - ---- - -## 5. Common Principles Across Methods - -- Tree-shakable: install only the methods you need. -- Domain-agnostic: all proofs and challenges are abstractions. -- Chainable: attempts, proofs, sessions, and policies are auditable. -- Extensible: adding new methods requires only a new package implementing - `IAuthMethod`. - ---- - -## 6. Method Registration (Example) - -```ts -const registry = new AuthMethodRegistry(); -registry.register(otpMethod); // @auth/method-otp -registry.register(oauthMethod); // @auth/method-oauth -registry.register(passkeyMethod); // @auth/method-passkey -``` - -- Explicit registration ensures **tree-shakability**. -- Developers can trace **which methods are enabled per tenant or flow**. - ---- - -## 7. Step 12 Status - -βœ… Additional auth methods abstracted βœ… OTP, OAuth, Passkey supported βœ… -Modular, optional, installable separately βœ… Traceable chain from attempt β†’ -proof β†’ session β†’ policy βœ… Enterprise-ready, extensible, tree-shakable - ---- - -# Step 13 β€” Adapter Implementation Guides - -> Goal: Provide a **modular, optional, and tree-shakable** adapter structure for -> storage, token management, and transport layers. - ---- - -## 1. Adapter Principles - -- Implements **ports defined in `@auth/core-ports`**. -- Tree-shakable: install only what you need. -- Fully replaceable: e.g., Postgres ↔ Redis ↔ Memory. -- Modular: one adapter per concern. -- Traceable: every adapter operation can emit domain events or logs for - auditing. - ---- - -## 2. Adapter Package Layout - -``` -@auth/adapter-memory ← in-memory demo & tests -@auth/adapter-postgres ← persistence -@auth/adapter-redis ← caching / OTP / session storage -@auth/adapter-jwt ← token issuance / verification -@auth/adapter-oidc ← external SSO / OIDC provider -@auth/adapter-http-express ← HTTP integration -@auth/adapter-http-hono ← alternative HTTP framework -``` - -- Each package is optional. -- Depends only on `@auth/core-ports`. -- No cross-dependencies. - ---- - -## 3. Persistence Adapters - -### 3.1 Credential Repository Adapter - -```ts -export interface CredentialRepositoryAdapter extends CredentialRepository { - // Adapter-specific configuration (Postgres table, Redis hash, etc.) -} -``` - -**Documentation Notes:** - -- Must implement domain interface `CredentialRepository`. -- Domain only sees abstract operations: - - save() - - findById() - - revoke() - -- Storage details hidden in adapter. -- Traceable via adapter logs or emitted events. - ---- - -### 3.2 Principal / Session / Attempt Repositories - -- Same pattern: implement `PrincipalRepository`, - `AuthenticationSessionRepository`, `AuthenticationAttemptRepository`. -- Example adapter: `PostgresPrincipalRepositoryAdapter`. -- Optional: emit domain events for auditing. - ---- - -## 4. Token Adapters - -### 4.1 JWT Adapter - -```ts -export interface JwtAdapter extends SessionRepresentationFactory { - issue(session: AuthenticationSession, options?: JwtOptions): Promise; - verify(token: string): Promise; -} -``` - -- Purely optional. -- Domain never depends on JWT format. -- Traceable: JWT issuance linked to sessionId β†’ tenantId. - ---- - -### 4.2 OIDC / OAuth Adapter - -```ts -export interface OidcAdapter extends IOAuthMethod { - initiateAuth(context: AuthContext, provider: string): Promise; - completeAuth(code: string, context: AuthContext): Promise; -} -``` - -- Adapter handles HTTP redirect, code exchange. -- Domain sees only `AuthProof`. -- Traceable through context and tenant metadata. - ---- - -## 5. Caching / Temporary Storage Adapters - -- OTP codes, challenge states, rate limiting. -- Interface: implement `ChallengeStore` or `CredentialMaterialStore`. -- Example: `RedisOtpAdapter`, `MemoryOtpAdapter`. -- Modular: can swap infra without affecting domain. - ---- - -## 6. HTTP Adapters - -- Optional transport layer integration: Express, Hono, Fastify. -- Implement endpoint wiring: - - Receive request - - Call registered auth method - - Return proof/session - -- Domain remains **transport-agnostic**. - ---- - -## 7. Adapter Best Practices - -1. **Install separately** β€” don’t bundle with core. -2. **No domain logic** β€” only map ports β†’ infra. -3. **Emit events** for auditing / tracing. -4. **Configurable per tenant** β€” adapter can read tenant-specific metadata. -5. **Tree-shakable** β€” unused adapters never included in bundle. -6. **Traceable code links** β€” each adapter operation references domain events or - JSDoc for auditing. - ---- - -## 8. Adapter Documentation Strategy - -- Each adapter includes: - - JSDoc explaining mapping to ports - - Tenant-aware behavior - - Optional emitted events for traceability - - Usage example (tree-shakable) - -- Chainable: core-domain β†’ port β†’ adapter β†’ infra action -- Developers can trace **who read / executed** each operation. - ---- - -## Step 13 Status - -βœ… Adapter types and patterns defined βœ… Storage, caching, tokens, HTTP, OIDC, -JWT covered βœ… Modular, tree-shakable, optional βœ… Traceable chain from domain β†’ -port β†’ adapter βœ… Enterprise-ready and multi-tenant safe - ---- - -# Step 14 β€” End-to-End Modular Integration Examples - -> Goal: Show how to wire **core-domain, core-ports, methods, adapters, policies, -> and multi-tenant context** together in a modular, traceable, and -> enterprise-ready way. - ---- - -## 1. Installable Modules - -``` -@auth/core-domain -@auth/core-ports -@auth/method-passwordless -@auth/method-otp -@auth/method-oauth -@auth/adapter-memory -@auth/adapter-jwt -``` - -- Each module optional -- Installed only if needed β†’ tree-shakable - ---- - -## 2. Tenant Setup - -```ts -const tenant: TenantContext = { tenantId: 'tenant-123', name: 'Acme Corp' }; -``` - -- All subsequent flows, policies, and sessions are **scoped to tenant** -- Chainable: attempt β†’ session β†’ policy β†’ tenant - ---- - -## 3. AuthMethod Registration per Tenant - -```ts -const registry = new AuthMethodRegistry(); - -registry.registerForTenant(tenant.tenantId, passwordlessMethod); -registry.registerForTenant(tenant.tenantId, otpMethod); -registry.registerForTenant(tenant.tenantId, oauthMethod); -``` - -- Tree-shakable: unused methods not included -- Traceable: registry logs tenant + methodType - ---- - -## 4. Authentication Flow Setup - -```ts -const flow = new AuthenticationFlow({ - flowId: 'enterprise_flow', - tenantId: tenant.tenantId, - name: 'Enterprise Multi-Method Flow', - steps: [ - { - stepId: 'passwordless_step', - authMethodType: 'passwordless_email', - required: true, - }, - { stepId: 'otp_step', authMethodType: 'otp', required: false }, - ], -}); -``` - -- Steps refer to registered methods -- Flow scoped per tenant -- Chainable: each step β†’ proof β†’ policy evaluation - ---- - -## 5. Policy Setup per Tenant - -```ts -const policy: AuthenticationPolicy = { - policyId: 'policy-tenant-001', - name: 'Step-Up for High-Risk Logins', - scope: 'Principal', - rules: [ - { - condition: { subject: 'risk.score', operator: 'greaterThan', value: 70 }, - action: { type: 'RequireStepUp', requiredFactors: ['otp'] }, - reason: 'High-risk logins require OTP', - }, - ], - metadata: { tenantId: tenant.tenantId }, -}; -``` - -- Policies applied **before or after each step** -- Chainable evaluation logged for traceability - ---- - -## 6. Authentication Attempt & Execution - -```ts -const principal = Principal.create({ - id: 'user-001', - tenantId: tenant.tenantId, -}); -const attempt = AuthenticationAttempt.create({ - flowId: flow.flowId, - principalId: principal.id, - tenantId: tenant.tenantId, -}); - -// Execute passwordless step -const passwordlessProof = await passwordlessMethod.authenticate( - { email: 'user@example.com' }, - { principalId: principal.id, attemptId: attempt.id }, -); - -// Update attempt -attempt.completeStep(passwordlessProof); - -// Evaluate policy after step -const policyResult = await policyEvaluator.evaluate( - { attempt, session: null, context: { tenantId: tenant.tenantId } }, - [policy], -); -``` - -- Domain-only, infra-agnostic -- Proof β†’ session β†’ policy evaluation chain traceable -- Developers can see **full execution path** for audit - ---- - -## 7. Session Creation - -```ts -const session = AuthenticationSession.createFromAttempt(attempt, { - trustLevel: 'Medium', - tenantContext: tenant, -}); -``` - -- Captures **tenant, principal, flow, steps, proofs** -- Immutable ContextSnapshot stored -- Domain-pure, transport-agnostic - ---- - -## 8. Adapter Wiring Example - -```ts -const credentialRepo = new MemoryCredentialRepositoryAdapter(); -const sessionRepo = new MemoryAuthenticationSessionRepositoryAdapter(); -const jwtAdapter = new JwtAdapter({ secret: 'dummy' }); - -// Connect adapters to ports -// Domain only interacts with repositories via core-ports interfaces -``` - -- Tree-shakable: only installed adapters included -- Modular: can swap Postgres / Redis / JWT -- Traceable: all actions reference tenant + attempt + session - ---- - -## 9. Chainable Audit Trail - -``` -Tenant -> Principal -> AuthenticationAttempt -> AuthMethod Step -> Proof -> Session -> Policy Evaluation -> Adapter Event -``` +| Port | Path | +| ------------------------ | ------------------------------------------------- | +| `SessionRepositoryPort` | `ports/outbound/session.repository.port.ts` | +| `TokenRepository` | `ports/repositories/token.repository.ts` | +| `MfaSessionRepository` | `ports/mfa/mfa-session-repository.port.ts` | +| `MfaSessionIdGenerator` | `ports/mfa/mfa-session-id-generator.port.ts` | +| `MfaClock` | `ports/mfa/mfa-clock.port.ts` | +| `LoggerPort` | `ports/log/log.port.ts` | +| `ObservabilityEventPort` | `ports/observability/observability-event.port.ts` | -- Every step **documented with JSDoc** -- Events or logs link **who executed / evaluated** -- Developers can trace **full end-to-end path** without touching infra +## Passwordless Ports (passwordless package) ---- +| Port | Path | +| --------------------------------- | ---------------------------------------------- | +| `PasswordlessChallengeRepository` | `methods/passwordless/src/ports/repositories/` | +| `PasswordlessChannelPort` | `methods/passwordless/src/ports/channels/` | +| `PasswordlessIdGeneratorPort` | `methods/passwordless/src/ports/` | -## 10. Developer Documentation Strategy +## OTP Ports -- Use **JSDoc** for: - - Modules - - SPI methods - - Flows and steps - - Policy evaluation - - Tenant context - - Adapter mapping +| Port | Package | +| ---------------- | ---------------------------------------------- | +| `OtpChannelPort` | `@rineex/authentication-method-otp` (exported) | -- Include `@link` to related domain objects -- Chainable audit references included for every step +## Layer Rules ---- +1. Domain must not import application or infrastructure +2. Application orchestrates domain + ports +3. Ports are interfaces only β€” no implementations in core +4. Method packages implement inbound ports; consumers implement outbound ports +5. Domain throws `DomainError`; application services return `Result` (v5+ API) -## Step 14 Status +## Export Policy -βœ… Full modular integration example complete βœ… Multi-tenant, multi-method, -policy-driven βœ… Tree-shakable and modular βœ… Domain-only traceable chain βœ… -Enterprise-ready +Only symbols re-exported from `core/src/index.ts` are public API. In-tree +domains (MFA, OAuth, session, token) are internal until explicitly exported. ---- +See [Architecture.md](./Architecture.md) for the full exported vs in-tree list. diff --git a/packages/authentication/core/TASK_BREAKDOWN.md b/packages/authentication/core/TASK_BREAKDOWN.md index 79464b9..098b7f1 100644 --- a/packages/authentication/core/TASK_BREAKDOWN.md +++ b/packages/authentication/core/TASK_BREAKDOWN.md @@ -1,980 +1,42 @@ -# Task Breakdown: Detailed Implementation Tasks +# Auth Core β€” Task Breakdown -This document provides granular, actionable tasks that can be converted to -GitHub issues or project management tickets. +Granular tasks derived from [IMPLEMENTATION_PLAN.md](./IMPLEMENTATION_PLAN.md). ---- +## Passwordless integration -## πŸ“¦ Phase 1: Foundation - Flow DSL & Proof System +- [ ] Export passwordless services, aggregate, and ports from `index.ts` +- [ ] Add `PasswordlessAuthMethod` class implementing `AuthMethodPort` +- [ ] Wire `PasswordlessAuthMethod` into `AuthenticationMethodResolver` +- [ ] Integration test: start attempt β†’ passwordless start β†’ verify β†’ succeed -### Task 1.1.1: Create FlowId Value Object +## Export hygiene -**Type:** Value Object -**Priority:** Critical -**Estimated Time:** 2 hours -**Dependencies:** None +- [ ] Add export tier table to README +- [ ] Decide MFA subpath export vs main entry promotion +- [ ] Export `AuthCoreErrorRegistry` from package entry +- [ ] Resolve Identity entity vs aggregate naming collision -**Description:** Create a `FlowId` value object following the pattern of -`AuthAttemptId`. +## OAuth -**Acceptance Criteria:** +- [ ] Implement `OAuthAuthorizeService` with repository dependency +- [ ] Export OAuth authorization aggregate and value objects +- [ ] Add OAuth flow integration test (domain level) -- [ ] File: `src/domain/identity/value-objects/flow-id.vo.ts` -- [ ] Extends `PrimitiveValueObject` or uses UUID -- [ ] Has `create()` factory method -- [ ] Validates non-empty value -- [ ] Has `toString()` method -- [ ] Unit tests with 100% coverage +## Error consistency -**Implementation Notes:** +- [ ] Replace plain `Error` in `Token` aggregate with `EntityValidationError` +- [ ] Add domain error for invalid `OtpCode` in core VO +- [ ] Verify all error classes in architecture spec -- Follow existing value object patterns in codebase -- Use UUID format for uniqueness -- Add to `src/domain/identity/value-objects/index.ts` +## Channels ---- +- [ ] Implement or remove `EmailChannel` stub +- [ ] Implement or remove `SMSChannelImp` stub +- [ ] Document channel adapter contract in passwordless README -### Task 1.1.2: Create TerminalState Value Object +## Documentation (completed in sync) -**Type:** Value Object -**Priority:** Critical -**Estimated Time:** 1 hour -**Dependencies:** None - -**Description:** Create a `TerminalState` value object representing flow -terminal states. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/value-objects/terminal-state.vo.ts` -- [ ] Values: `AUTHENTICATED`, `FAILED`, `CHALLENGED` -- [ ] Immutable enum-like value object -- [ ] Has `is()` and `isNot()` methods -- [ ] Unit tests - -**Implementation Notes:** - -- Can be enum or union type wrapped in value object -- Follow `AuthStatus` pattern - ---- - -### Task 1.1.3: Create Transition Value Object - -**Type:** Value Object -**Priority:** Critical -**Estimated Time:** 3 hours -**Dependencies:** Task 1.1.2 - -**Description:** Create a `Transition` value object that represents flow step -transitions. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/value-objects/transition.vo.ts` -- [ ] Properties: `targetStepId?: string`, `terminalState?: TerminalState` -- [ ] Exactly one of `targetStepId` or `terminalState` must be set -- [ ] Optional `condition?: PolicyExpression` (can be string reference for now) -- [ ] Validation ensures mutual exclusivity -- [ ] Unit tests - -**Implementation Notes:** - -- Use discriminated union or validation -- PolicyExpression can be a string reference initially - ---- - -### Task 1.1.4: Create AuthFlowStep Value Object - -**Type:** Value Object -**Priority:** Critical -**Estimated Time:** 4 hours -**Dependencies:** Task 1.1.3 - -**Description:** Create an `AuthFlowStep` value object representing a single -step in a flow. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/value-objects/auth-flow-step.vo.ts` -- [ ] Properties: - - `stepId: string` - - `authMethodType: AuthMethodName` - - `required: boolean` - - `onSuccess: Transition` - - `onFailure: Transition` - - `onChallenge?: Transition` -- [ ] Immutable -- [ ] Validation ensures stepId is non-empty -- [ ] Unit tests - -**Implementation Notes:** - -- Use readonly properties -- Validate transitions are valid - ---- - -### Task 1.2.1: Create AuthenticationFlow Aggregate - -**Type:** Aggregate Root -**Priority:** Critical -**Estimated Time:** 8 hours -**Dependencies:** Task 1.1.4 - -**Description:** Create the `AuthenticationFlow` aggregate root that defines -authentication flows. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/aggregates/authentication-flow.aggregate.ts` -- [ ] Properties: - - `flowId: FlowId` - - `name: string` - - `steps: AuthFlowStep[]` - - `entryConditions?: PolicyExpression[]` - - `exitConditions?: PolicyExpression[]` -- [ ] Factory method: `create(flowId, name, steps, ...)` -- [ ] Method: `getStep(stepId): AuthFlowStep | null` -- [ ] Method: `getFirstStep(): AuthFlowStep` -- [ ] Method: `isValid(): boolean` -- [ ] Validation: - - At least one step - - First step exists - - Terminal states reachable - - No circular dependencies -- [ ] Domain events (if needed) -- [ ] Unit tests with edge cases - -**Implementation Notes:** - -- Follow `AuthenticationAttempt` aggregate pattern -- Use `AggregateRoot` from `@rineex/ddd` -- Validation is critical for security - ---- - -### Task 1.2.2: Add Flow Domain Events - -**Type:** Domain Event -**Priority:** Low -**Estimated Time:** 2 hours -**Dependencies:** Task 1.2.1 - -**Description:** Create domain events for flow lifecycle (if needed). - -**Acceptance Criteria:** - -- [ ] Events: `FlowCreatedEvent` (if needed) -- [ ] Follow existing event patterns -- [ ] Unit tests - -**Implementation Notes:** - -- May not be needed if flows are configuration -- Check if events add value - ---- - -### Task 1.3.1: Create AuthProofType Value Object - -**Type:** Value Object -**Priority:** Critical -**Estimated Time:** 2 hours -**Dependencies:** None - -**Description:** Create `AuthProofType` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/value-objects/auth-proof-type.vo.ts` -- [ ] Values: `password_proof`, `otp_proof`, `oauth_proof`, `assertion_proof`, - `challenge_proof` -- [ ] Immutable enum-like -- [ ] Unit tests - ---- - -### Task 1.3.2: Create AuthProof Value Object - -**Type:** Value Object -**Priority:** Critical -**Estimated Time:** 4 hours -**Dependencies:** Task 1.3.1 - -**Description:** Create `AuthProof` value object representing authentication -proof. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/value-objects/auth-proof.vo.ts` -- [ ] Properties: - - `proofId: string` (UUID) - - `proofType: AuthProofType` - - `methodType: AuthMethodName` - - `issuedAt: Date` - - `metadata: Record` -- [ ] Factory method: `create(...)` -- [ ] Method: `toObject()` for serialization -- [ ] Validation: - - proofId is UUID - - issuedAt is valid date - - metadata is serializable -- [ ] Unit tests - -**Implementation Notes:** - -- Proofs are immutable -- Metadata should be validated for serialization - ---- - -### Task 1.4.1: Update AuthStatus Value Object - -**Type:** Value Object -**Priority:** Critical -**Estimated Time:** 2 hours -**Dependencies:** None - -**Description:** Add new states to `AuthStatus` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/value-objects/auth-status.vo.ts` -- [ ] Add states: `INITIALIZED`, `IN_PROGRESS`, `AWAITING_CHALLENGE` -- [ ] Keep existing: `SUCCEEDED`, `FAILED` -- [ ] Update validation -- [ ] Update tests -- [ ] Ensure backward compatibility if possible - -**Implementation Notes:** - -- May need to deprecate `PENDING` or map it to `INITIALIZED` -- Check existing usages - ---- - -### Task 1.4.2: Update AuthenticationAttempt Props - -**Type:** Aggregate -**Priority:** Critical -**Estimated Time:** 3 hours -**Dependencies:** Task 1.1.1, Task 1.3.2 - -**Description:** Add flow-related properties to `AuthenticationAttempt`. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/aggregates/authentication-attempt.aggregate.ts` -- [ ] Add to props: - - `flowId: FlowId` - - `currentStepId?: string` - - `collectedProofs: AuthProof[]` -- [ ] Update factory method signature -- [ ] Ensure backward compatibility -- [ ] Update tests - -**Implementation Notes:** - -- May need migration strategy for existing attempts -- Keep `method` for backward compat initially - ---- - -### Task 1.4.3: Add Flow Methods to AuthenticationAttempt - -**Type:** Aggregate -**Priority:** Critical -**Estimated Time:** 8 hours -**Dependencies:** Task 1.4.2, Task 1.2.1 - -**Description:** Add methods to `AuthenticationAttempt` for flow execution. - -**Acceptance Criteria:** - -- [ ] Method: `startFlow(flow: AuthenticationFlow): void` - - Transition: `INITIALIZED` β†’ `IN_PROGRESS` - - Set `currentStepId` to first step - - Emit event -- [ ] Method: `completeStep(proof: AuthProof): void` - - Validate proof matches current step method - - Add proof to `collectedProofs` - - Transition based on flow step's `onSuccess` -- [ ] Method: `failStep(reason: string): void` - - Transition based on flow step's `onFailure` -- [ ] Method: `awaitChallenge(): void` - - Transition to `AWAITING_CHALLENGE` -- [ ] Method: `resumeFromChallenge(): void` - - Transition back to `IN_PROGRESS` -- [ ] Method: `getCurrentStep(flow: AuthenticationFlow): AuthFlowStep | null` -- [ ] Method: `advanceToNextStep(transition: Transition): void` -- [ ] Update `succeed()` and `fail()` to validate terminal states -- [ ] Unit tests for all methods -- [ ] Integration tests for flow execution - -**Implementation Notes:** - -- Methods should enforce invariants -- State transitions must be validated -- Proofs are append-only - ---- - -### Task 1.5.1: Create Flow Repository Port - -**Type:** Port -**Priority:** High -**Estimated Time:** 1 hour -**Dependencies:** Task 1.2.1 - -**Description:** Create repository port for `AuthenticationFlow`. - -**Acceptance Criteria:** - -- [ ] File: `src/ports/outbound/authentication-flow-repository.port.ts` -- [ ] Interface: - ```typescript - AuthenticationFlowRepositoryPort { - findById(flowId: FlowId): Promise - findAll(): Promise - save(flow: AuthenticationFlow): Promise - } - ``` -- [ ] Add to `src/ports/outbound/index.ts` - -**Implementation Notes:** - -- No implementation (infrastructure concern) -- Follow existing port patterns - ---- - -## πŸ“¦ Phase 2: Trust & Session System - -### Task 2.1.1: Create TrustLevel Value Object - -**Type:** Value Object -**Priority:** High -**Estimated Time:** 4 hours -**Dependencies:** None - -**Description:** Create `TrustLevel` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/session/value-objects/trust-level.vo.ts` -- [ ] Values: `ANONYMOUS`, `LOW`, `MEDIUM`, `HIGH` -- [ ] Method: `compare(other: TrustLevel): number` -- [ ] Method: `isAtLeast(level: TrustLevel): boolean` -- [ ] Method: `downgradeTo(level: TrustLevel): TrustLevel` - - Only allows downgrade - - Throws if upgrade attempted -- [ ] Unit tests - -**Implementation Notes:** - -- Trust levels are ordered -- Cannot upgrade in-place - ---- - -### Task 2.2.1: Create ContextSnapshot Value Object - -**Type:** Value Object -**Priority:** High -**Estimated Time:** 4 hours -**Dependencies:** None - -**Description:** Create `ContextSnapshot` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/session/value-objects/context-snapshot.vo.ts` -- [ ] Properties: - - `deviceTrusted?: boolean` - - `location?: { country?: string, ipAddress?: string }` - - `riskScore?: number` - - `authMethodsUsed: AuthMethodName[]` - - `policiesApplied: string[]` - - `capturedAt: Date` -- [ ] Factory: `capture(context: AuthContext, ...): ContextSnapshot` -- [ ] Method: `toObject()` for serialization -- [ ] Unit tests - -**Implementation Notes:** - -- Snapshot is immutable -- Captured at session creation time - ---- - -### Task 2.3.1: Create SessionStatus Value Object - -**Type:** Value Object -**Priority:** High -**Estimated Time:** 1 hour -**Dependencies:** None - -**Description:** Create `SessionStatus` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/session/value-objects/session-status.vo.ts` -- [ ] Values: `ACTIVE`, `EXPIRED`, `REVOKED` -- [ ] Follow `AuthStatus` pattern -- [ ] Unit tests - ---- - -### Task 2.3.2: Create AuthenticationSession Aggregate - -**Type:** Aggregate Root -**Priority:** High -**Estimated Time:** 12 hours -**Dependencies:** Task 2.1.1, Task 2.2.1, Task 2.3.1, Task 1.3.2 - -**Description:** Create `AuthenticationSession` aggregate root. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/session/aggregates/authentication-session.aggregate.ts` -- [ ] Properties: - - `sessionId: SessionId` - - `principalId: IdentityId` - - `flowId: FlowId` - - `attemptId: AuthAttemptId` - - `issuedAt: Date` - - `expiresAt: Date` - - `trustLevel: TrustLevel` - - `authFactorsUsed: AuthFactor[]` - - `contextSnapshot: ContextSnapshot` - - `status: SessionStatus` -- [ ] Factory: - `createFromAttempt(attempt: AuthenticationAttempt, ...): AuthenticationSession` -- [ ] Method: - `computeTrustLevel(proofs: AuthProof[], policies: Policy[]): TrustLevel` -- [ ] Method: `revoke(at: Date): void` -- [ ] Method: `isExpired(now: Date): boolean` -- [ ] Method: `downgradeTrust(level: TrustLevel): void` -- [ ] Method: `extractAuthFactors(proofs: AuthProof[]): AuthFactor[]` -- [ ] Domain events: `SessionCreatedEvent`, `SessionRevokedEvent` -- [ ] Unit tests -- [ ] Integration tests - -**Implementation Notes:** - -- Trust computation logic is critical -- Follow existing aggregate patterns - ---- - -### Task 2.4.1: Create Session Repository Port - -**Type:** Port -**Priority:** High -**Estimated Time:** 1 hour -**Dependencies:** Task 2.3.2 - -**Description:** Create repository port for `AuthenticationSession`. - -**Acceptance Criteria:** - -- [ ] File: `src/ports/outbound/authentication-session-repository.port.ts` -- [ ] Interface: - ```typescript - AuthenticationSessionRepositoryPort { - save(session: AuthenticationSession): Promise - findById(sessionId: SessionId): Promise - findByPrincipalId(principalId: IdentityId): Promise - revoke(sessionId: SessionId): Promise - } - ``` - ---- - -## πŸ“¦ Phase 3: Credential Lifecycle - -### Task 3.1.1: Create CredentialId Value Object - -**Type:** Value Object -**Priority:** Medium -**Estimated Time:** 2 hours -**Dependencies:** None - -**Description:** Create `CredentialId` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/value-objects/credential-id.vo.ts` -- [ ] UUID-based -- [ ] Follow existing ID patterns -- [ ] Unit tests - ---- - -### Task 3.1.2: Create CredentialStatus Value Object - -**Type:** Value Object -**Priority:** Medium -**Estimated Time:** 3 hours -**Dependencies:** None - -**Description:** Create `CredentialStatus` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/value-objects/credential-status.vo.ts` -- [ ] Values: `ACTIVE`, `SUSPENDED`, `REVOKED`, `EXPIRED`, `COMPROMISED` -- [ ] Validation for status transitions -- [ ] Method: `canTransitionTo(newStatus: CredentialStatus): boolean` -- [ ] Unit tests - -**Implementation Notes:** - -- REVOKED and COMPROMISED are terminal -- EXPIRED is time-based -- SUSPENDED is reversible - ---- - -### Task 3.2.1: Create Credential Entity - -**Type:** Entity -**Priority:** Medium -**Estimated Time:** 10 hours -**Dependencies:** Task 3.1.1, Task 3.1.2 - -**Description:** Create `Credential` entity. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/identity/entities/credential.entity.ts` -- [ ] Properties: - - `credentialId: CredentialId` - - `principalId: IdentityId` - - `authMethodType: AuthMethodName` - - `authFactorType: AuthFactorType` - - `status: CredentialStatus` - - `issuedAt: Date` - - `expiresAt?: Date` - - `lastUsedAt?: Date` - - `metadata: Record` -- [ ] Factory: `create(...): Credential` -- [ ] Method: `suspend(): void` -- [ ] Method: `revoke(reason: string): void` -- [ ] Method: `markAsCompromised(): void` -- [ ] Method: `recordUsage(at: Date): void` -- [ ] Method: `checkExpiration(now: Date): void` -- [ ] Method: `isUsable(): boolean` -- [ ] Domain events: `CredentialCreatedEvent`, `CredentialRevokedEvent`, etc. -- [ ] Unit tests - -**Implementation Notes:** - -- No secrets stored (infrastructure concern) -- Lifecycle must be enforced - ---- - -### Task 3.3.1: Create Credential Repository Port - -**Type:** Port -**Priority:** Medium -**Estimated Time:** 1 hour -**Dependencies:** Task 3.2.1 - -**Description:** Create repository port for `Credential`. - -**Acceptance Criteria:** - -- [ ] File: `src/ports/outbound/credential-repository.port.ts` -- [ ] Interface with methods for CRUD operations -- [ ] Method: `findByPrincipalAndMethod(...)` - ---- - -## πŸ“¦ Phase 4: Enhanced Orchestration - -### Task 4.1.1: Update AuthOrchestratorService Constructor - -**Type:** Application Service -**Priority:** Critical -**Estimated Time:** 2 hours -**Dependencies:** Phase 1, Phase 2 - -**Description:** Update orchestrator to accept new dependencies. - -**Acceptance Criteria:** - -- [ ] Add `flowRepository: AuthenticationFlowRepositoryPort` -- [ ] Add `sessionRepository: AuthenticationSessionRepositoryPort` -- [ ] Update constructor signature -- [ ] Update tests - ---- - -### Task 4.1.2: Implement Flow Selection Logic - -**Type:** Application Service -**Priority:** Critical -**Estimated Time:** 4 hours -**Dependencies:** Task 4.1.1 - -**Description:** Add method to select flow from command or policy. - -**Acceptance Criteria:** - -- [ ] Method: - `selectFlow(command: StartAuthenticationCommand): Promise` -- [ ] Check if command specifies flow -- [ ] Otherwise use policy to select -- [ ] Default to single-step flow for method -- [ ] Unit tests - ---- - -### Task 4.1.3: Implement Step Execution Logic - -**Type:** Application Service -**Priority:** Critical -**Estimated Time:** 6 hours -**Dependencies:** Task 4.1.2 - -**Description:** Add method to execute a flow step. - -**Acceptance Criteria:** - -- [ ] Method: - `executeStep(attempt: AuthenticationAttempt, flow: AuthenticationFlow): Promise` -- [ ] Get current step from flow -- [ ] Resolve auth method for step -- [ ] Call method's authenticate() -- [ ] Handle result (success/failure/challenge) -- [ ] Unit tests - ---- - -### Task 4.1.4: Implement Step Result Handling - -**Type:** Application Service -**Priority:** Critical -**Estimated Time:** 6 hours -**Dependencies:** Task 4.1.3 - -**Description:** Add method to handle step results and advance flow. - -**Acceptance Criteria:** - -- [ ] Method: - `handleStepResult(attempt: AuthenticationAttempt, proof: AuthProof, flow: AuthenticationFlow): Promise` -- [ ] Add proof to attempt -- [ ] Get transition from current step -- [ ] Evaluate transition condition (if any) -- [ ] Advance to next step or terminal state -- [ ] Unit tests - ---- - -### Task 4.1.5: Implement Attempt Finalization - -**Type:** Application Service -**Priority:** Critical -**Estimated Time:** 4 hours -**Dependencies:** Task 4.1.4 - -**Description:** Add method to finalize attempt and create session. - -**Acceptance Criteria:** - -- [ ] Method: - `finalizeAttempt(attempt: AuthenticationAttempt): Promise` -- [ ] Only if attempt succeeded -- [ ] Create session from attempt -- [ ] Save session -- [ ] Return session -- [ ] Unit tests - ---- - -### Task 4.1.6: Update Orchestrator Execute Method - -**Type:** Application Service -**Priority:** Critical -**Estimated Time:** 6 hours -**Dependencies:** All Task 4.1.x - -**Description:** Refactor main execute method to use flows. - -**Acceptance Criteria:** - -- [ ] Update `execute()` to use flow system -- [ ] Call `selectFlow()` -- [ ] Create attempt with flow -- [ ] Call `startFlow()` -- [ ] Execute steps sequentially -- [ ] Handle challenges -- [ ] Finalize attempt -- [ ] Integration tests - ---- - -## πŸ“¦ Phase 5: Policy Domain Objects - -### Task 5.1.1: Create PolicyScope Value Object - -**Type:** Value Object -**Priority:** Medium -**Estimated Time:** 2 hours -**Dependencies:** None - -**Description:** Create `PolicyScope` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/policy/value-objects/policy-scope.vo.ts` -- [ ] Values: `GLOBAL`, `PRINCIPAL`, `AUTH_METHOD`, `AUTH_FLOW`, `RESOURCE` -- [ ] Unit tests - ---- - -### Task 5.1.2: Create ConditionExpression Value Object - -**Type:** Value Object -**Priority:** Medium -**Estimated Time:** 4 hours -**Dependencies:** None - -**Description:** Create `ConditionExpression` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/policy/value-objects/condition-expression.vo.ts` -- [ ] Properties: `subject: string`, `operator: Operator`, `value: unknown` -- [ ] Operator enum: `equals`, `notEquals`, `greaterThan`, `lessThan`, `in`, - `notIn` -- [ ] Validation -- [ ] Unit tests - ---- - -### Task 5.1.3: Create PolicyAction Value Object - -**Type:** Value Object -**Priority:** Medium -**Estimated Time:** 4 hours -**Dependencies:** Task 2.1.1 - -**Description:** Create `PolicyAction` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/policy/value-objects/policy-action.vo.ts` -- [ ] Discriminated union: - - `{ type: 'ALLOW' }` - - `{ type: 'DENY'; reason: string }` - - `{ type: 'REQUIRE_STEP_UP'; requiredFactors: string[] }` - - `{ type: 'SELECT_FLOW'; flowId: FlowId }` - - `{ type: 'LIMIT_TRUST_LEVEL'; level: TrustLevel }` -- [ ] Unit tests - ---- - -### Task 5.1.4: Create PolicyRule Value Object - -**Type:** Value Object -**Priority:** Medium -**Estimated Time:** 3 hours -**Dependencies:** Task 5.1.2, Task 5.1.3 - -**Description:** Create `PolicyRule` value object. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/policy/value-objects/policy-rule.vo.ts` -- [ ] Properties: `condition: ConditionExpression`, `action: PolicyAction`, - `reason?: string` -- [ ] Unit tests - ---- - -### Task 5.1.5: Create AuthenticationPolicy Aggregate - -**Type:** Aggregate Root -**Priority:** Medium -**Estimated Time:** 8 hours -**Dependencies:** Task 5.1.4, Task 5.1.1 - -**Description:** Create `AuthenticationPolicy` aggregate. - -**Acceptance Criteria:** - -- [ ] File: `src/domain/policy/aggregates/authentication-policy.aggregate.ts` -- [ ] Properties: - - `policyId: string` - - `name: string` - - `scope: PolicyScope` - - `rules: PolicyRule[]` - - `version: number` - - `metadata?: Record` -- [ ] Factory: `create(...)` -- [ ] Method: `evaluate(context: AuthPolicyContext): PolicyEvaluationResult` -- [ ] Validation: at least one rule -- [ ] Serializable -- [ ] Unit tests - ---- - -### Task 5.2.1: Update Policy Engine - -**Type:** Domain Service -**Priority:** Medium -**Estimated Time:** 3 hours -**Dependencies:** Task 5.1.5 - -**Description:** Update policy engine to use domain objects. - -**Acceptance Criteria:** - -- [ ] Update `AuthPolicyEngine` to accept `AuthenticationPolicy[]` -- [ ] Call `policy.evaluate()` on each -- [ ] Aggregate results -- [ ] Maintain backward compatibility if needed -- [ ] Unit tests - ---- - -## πŸ“¦ Phase 6: Integration & Testing - -### Task 6.1.1: Update Domain Exports - -**Type:** Infrastructure -**Priority:** High -**Estimated Time:** 2 hours -**Dependencies:** All phases - -**Description:** Export all new domain objects. - -**Acceptance Criteria:** - -- [ ] Update `src/domain/index.ts` -- [ ] Update `src/domain/identity/index.ts` -- [ ] Update `src/domain/session/index.ts` -- [ ] Update `src/domain/policy/index.ts` -- [ ] Ensure proper module boundaries - ---- - -### Task 6.2.1: Create Flow Execution Integration Tests - -**Type:** Test -**Priority:** High -**Estimated Time:** 8 hours -**Dependencies:** Phase 4 - -**Description:** Create integration tests for flow execution. - -**Acceptance Criteria:** - -- [ ] File: `src/__tests__/integration/flow-execution.test.ts` -- [ ] Test: Single-step password flow -- [ ] Test: Multi-step password + OTP flow -- [ ] Test: Challenge handling -- [ ] Test: Flow transitions -- [ ] All tests passing - ---- - -### Task 6.2.2: Create Trust Level Integration Tests - -**Type:** Test -**Priority:** High -**Estimated Time:** 4 hours -**Dependencies:** Phase 2 - -**Description:** Create integration tests for trust level computation. - -**Acceptance Criteria:** - -- [ ] File: `src/__tests__/integration/trust-level-computation.test.ts` -- [ ] Test: Trust from single factor -- [ ] Test: Trust from MFA -- [ ] Test: Trust downgrade -- [ ] All tests passing - ---- - -### Task 6.2.3: Create Session Creation Integration Tests - -**Type:** Test -**Priority:** High -**Estimated Time:** 6 hours -**Dependencies:** Phase 2, Phase 4 - -**Description:** Create integration tests for session creation. - -**Acceptance Criteria:** - -- [ ] File: `src/__tests__/integration/session-creation.test.ts` -- [ ] Test: Session from successful attempt -- [ ] Test: Trust level in session -- [ ] Test: Context snapshot -- [ ] All tests passing - ---- - -### Task 6.3.1: Update README - -**Type:** Documentation -**Priority:** Medium -**Estimated Time:** 4 hours -**Dependencies:** All phases - -**Description:** Update README with new features and examples. - -**Acceptance Criteria:** - -- [ ] Document flow DSL -- [ ] Document trust levels -- [ ] Add usage examples -- [ ] Update feature list - ---- - -### Task 6.3.2: Create Flow Examples - -**Type:** Documentation -**Priority:** Medium -**Estimated Time:** 4 hours -**Dependencies:** Phase 1 - -**Description:** Create example flow definitions. - -**Acceptance Criteria:** - -- [ ] Example: Password flow -- [ ] Example: Password + OTP flow -- [ ] Example: Risk-based step-up flow -- [ ] Example: OAuth flow -- [ ] Documented in README or examples/ - ---- - -## πŸ“Š Summary - -**Total Tasks:** 50+ -**Estimated Total Time:** ~200-250 hours -**Critical Path:** Phase 1 β†’ Phase 4 -**Can Parallelize:** Phase 2 & 3 (after Phase 1) - ---- - -## 🎯 Next Steps - -1. Review this task breakdown -2. Create GitHub issues/projects from tasks -3. Start with Task 1.1.1 (FlowId Value Object) -4. Work incrementally, test as you go -5. Review after each phase - -Good luck! πŸš€ +- [x] Rewrite Architecture.md, Definition.md, RULES.md +- [x] Update domain errors and value objects READMEs +- [x] Sync Result/DomainError patterns across packages +- [x] Preserve aspirational spec in FUTURE.md diff --git a/packages/authentication/core/src/domain/errors/README.md b/packages/authentication/core/src/domain/errors/README.md index c8d6430..1e2f402 100644 --- a/packages/authentication/core/src/domain/errors/README.md +++ b/packages/authentication/core/src/domain/errors/README.md @@ -1,322 +1,118 @@ # Authentication Domain Errors -## Overview - -This document describes the domain error classes used in the authentication core -package. All errors extend `DomainError` from `@rineex/ddd` and follow a -consistent pattern for error handling and reporting. +Domain error classes for `@rineex/auth-core`. All extend `DomainError` from +`@rineex/ddd` using the v6 registry-backed, code-first API. ## Error Structure -All domain errors follow this structure: - -- **code**: Machine-readable error code in format `NAMESPACE.ERROR_NAME` -- **message**: Human-readable error message -- **type**: Error category (`DOMAIN.INVALID_STATE` or `DOMAIN.INVALID_VALUE`) -- **metadata**: Optional structured context (primitive values only) - -## Core Domain Errors - -### InvalidAuthTokenError - -Raised when an authentication token violates domain invariants. - -**Code**: `AUTH_CORE_TOKEN.INVALID` -**Type**: `DOMAIN.INVALID_VALUE` - -**Metadata**: - -- `actualLength`: number - Actual token length -- `minLength`: number - Minimum required length (32) - -**Example**: - -```typescript -throw InvalidAuthTokenError.create('Authentication token is invalid', { - actualLength: 20, - minLength: 32, -}); -``` - -### InvalidSessionError - -Raised when a session invariant is violated. - -**Code**: `AUTH_CORE_SESSION.INVALID` -**Type**: `DOMAIN.INVALID_STATE` - -**Example**: - -```typescript -throw InvalidSessionError.create(); -``` - -### InvalidScopeError - -Raised when a scope format is invalid. - -**Code**: `AUTH_CORE_SCOPE.INVALID` -**Type**: `DOMAIN.INVALID_VALUE` - -**Metadata**: - -- `scope`: string - The invalid scope value - -**Example**: - -```typescript -throw InvalidScopeError.create('Scope format is invalid', { - scope: 'invalid scope format', -}); -``` - -## MFA Errors +Every domain error has: -### MfaChallengeExpiredError +- **code**: Machine-readable `NAMESPACE.ERROR_NAME` literal (e.g. + `AUTH_CORE_TOKEN.INVALID`) +- **message**: Human-readable description +- **metadata**: Optional primitive context (`Metadata` from `@rineex/ddd`) -Raised when an MFA challenge has expired. +There is **no `type` field** β€” removed in `@rineex/ddd` v6. Use `code` for +classification. -**Code**: `AUTH_CORE_MFA.CHALLENGE_EXPIRED` -**Type**: `DOMAIN.INVALID_STATE` - -**Example**: +## Registry Pattern ```typescript -if (challenge.isExpired(now)) { - throw MfaChallengeExpiredError.create(); -} -``` - -### MfaExpiredError - -Raised when an MFA challenge or session has expired. +import { InferErrorCodes } from '@rineex/ddd'; -**Code**: `AUTH_CORE_MFA.EXPIRED` -**Type**: `DOMAIN.INVALID_STATE` +export const AuthCoreErrorRegistry = { + AUTH_CORE_TOKEN: ['INVALID'], + // ... other namespaces +} as const; -**Example**: - -```typescript -if (mfaSession.isExpired(now)) { - throw MfaExpiredError.create(); -} +export type AuthCoreDomainErrorCode = InferErrorCodes< + typeof AuthCoreErrorRegistry +>; ``` -### MfaAlreadyVerifiedError +Architecture tests in `__tests__/auth-core-error.architecture.spec.ts` verify +every error class code is registered. -Raised when an MFA session is already verified. +## Namespaces -**Code**: `AUTH_CORE_MFA.ALREADY_VERIFIED` -**Type**: `DOMAIN.INVALID_STATE` +### AUTH_CORE_TOKEN -**Example**: +| Code | Class | Metadata | +| --------- | ----------------------- | --------------------------- | +| `INVALID` | `InvalidAuthTokenError` | `actualLength`, `minLength` | -```typescript -if (mfaSession.isVerified()) { - throw MfaAlreadyVerifiedError.create(); -} -``` +### AUTH_CORE_SESSION -### MfaActiveChallengeExistsError +| Code | Class | +| --------- | --------------------- | +| `INVALID` | `InvalidSessionError` | -Raised when attempting to create a new MFA challenge while an active one already -exists. +### AUTH_CORE_SCOPE -**Code**: `AUTH_CORE_MFA.ACTIVE_CHALLENGE_EXISTS` -**Type**: `DOMAIN.INVALID_STATE` +| Code | Class | +| --------- | ------------------- | +| `INVALID` | `InvalidScopeError` | -**Example**: +### AUTH_CORE_IDENTITY -```typescript -if (await hasActiveChallenge(identityId)) { - throw MfaActiveChallengeExistsError.create(); -} -``` +| Code | Class | +| -------------------- | -------------------------------------- | +| `DISABLED_ERROR` | `IdentityDisabledError` | +| `INVALID_TRANSITION` | `InvalidAuthenticationTransitionError` | -### MfaAttemptsExceededError +### AUTH_CORE_ATTEMPT -Raised when the maximum number of MFA verification attempts has been exceeded. +| Code | Class | +| ----------------------- | ------------------------------- | +| `AUTHENTICATION_FAILED` | `AuthenticationAttemptError` | +| `NOT_FOUND` | `AuthenticationAttemptNotFound` | -**Code**: `AUTH_CORE_MFA.ATTEMPTS_EXCEEDED` -**Type**: `DOMAIN.INVALID_STATE` +### AUTH_CORE_MFA -**Metadata**: +| Code | Class | +| -------------------------- | ------------------------------- | +| `CHALLENGE_ID_INVALID` | MFA challenge ID errors | +| `CHALLENGE_STATUS_INVALID` | Invalid MFA challenge status | +| `CHALLENGE_EXPIRED` | `MfaChallengeExpiredError` | +| `EXPIRED` | `MfaExpiredError` | +| `ALREADY_VERIFIED` | `MfaAlreadyVerifiedError` | +| `ACTIVE_CHALLENGE_EXISTS` | `MfaActiveChallengeExistsError` | +| `ATTEMPTS_EXCEEDED` | `MfaAttemptsExceededError` | +| `SESSION_ID_INVALID` | MFA session ID errors | -- `attemptsUsed`: number - Number of attempts that were used -- `maxAttempts`: number - Maximum number of allowed attempts +### AUTH_CORE_OAUTH -**Example**: +| Code | Class | +| ---------------------------- | ------------------------------- | +| `INVALID_PROVIDER` | `InvalidOauthProviderError` | +| `INVALID_REDIRECT_URI` | `InvalidRedirectUriError` | +| `AUTHORIZATION_ALREADY_USED` | `AuthorizationAlreadyUsedError` | +| `INVALID_PKCE` | `InvalidPkceError` | +| `INVALID_AUTHORIZATION_CODE` | `InvalidAuthorizationCodeError` | +| `CONSENT_REQUIRED` | `ConsentRequiredError` | +| `AUTHORIZATION_EXPIRED` | `AuthorizationExpiredError` | -```typescript -if (attempts >= maxAttempts) { - throw MfaAttemptsExceededError.create(attempts, maxAttempts); -} -``` - -## OAuth Errors - -### InvalidOAuthProviderError - -Raised when an OAuth provider identifier is invalid. - -**Code**: `AUTH_CORE_OAUTH.INVALID_PROVIDER` -**Type**: `DOMAIN.INVALID_VALUE` - -**Metadata**: - -- `value`: string - The invalid provider identifier - -**Example**: +## Usage ```typescript -throw InvalidOAuthProviderError.create({ value: 'invalid_provider' }); -``` - -### InvalidRedirectUriError - -Raised when a redirect URI is invalid or insecure. - -**Code**: `AUTH_CORE_OAUTH.INVALID_REDIRECT_URI` -**Type**: `DOMAIN.INVALID_VALUE` - -**Metadata**: - -- `redirectUri`: string - The invalid redirect URI - -**Example**: - -```typescript -throw InvalidRedirectUriError.create({ redirectUri: 'http://insecure.com' }); -``` - -### AuthorizationAlreadyUsedError - -Raised when an OAuth authorization has already been used. - -**Code**: `AUTH_CORE_OAUTH.AUTHORIZATION_ALREADY_USED` -**Type**: `DOMAIN.INVALID_STATE` - -**Example**: - -```typescript -if (authorization.isUsed()) { - throw AuthorizationAlreadyUsedError.create(); -} -``` - -### InvalidPkceError - -Raised when PKCE (Proof Key for Code Exchange) parameters are invalid. - -**Code**: `AUTH_CORE_OAUTH.INVALID_PKCE` -**Type**: `DOMAIN.INVALID_VALUE` - -**Example**: - -```typescript -throw InvalidPkceError.create({ reason: 'code_verifier_mismatch' }); -``` - -### InvalidAuthorizationCodeError - -Raised when an authorization code is invalid. - -**Code**: `AUTH_CORE_OAUTH.INVALID_AUTHORIZATION_CODE` -**Type**: `DOMAIN.INVALID_VALUE` - -**Example**: - -```typescript -throw InvalidAuthorizationCodeError.create({ code: 'invalid_code' }); -``` - -### ConsentRequiredError - -Raised when user consent is required for an OAuth authorization. - -**Code**: `AUTH_CORE_OAUTH.CONSENT_REQUIRED` -**Type**: `DOMAIN.INVALID_STATE` - -**Example**: - -```typescript -if (!hasConsent(clientId, userId, scopes)) { - throw ConsentRequiredError.create(); -} -``` - -### AuthorizationExpiredError - -Raised when an OAuth authorization has expired. - -**Code**: `AUTH_CORE_OAUTH.AUTHORIZATION_EXPIRED` -**Type**: `DOMAIN.INVALID_STATE` - -**Example**: - -```typescript -if (authorization.isExpired(now)) { - throw AuthorizationExpiredError.create(); -} -``` - -## OTP Errors - -### OtpAuthenticationError - -Raised when OTP authentication fails. - -**Code**: `AUTH_OTP.AUTHENTICATION_FAILED` -**Type**: `DOMAIN.INVALID_STATE` - -**Metadata**: - -- `attemptsUsed?`: number - Number of attempts used -- `reason?`: string - Reason for failure - -**Example**: - -```typescript -throw OtpAuthenticationError.create('Invalid or expired OTP code', { - attemptsUsed: 3, - reason: 'code_mismatch', -}); -``` - -## Usage Guidelines - -1. **Error Creation**: Always use the static `create()` method when available -2. **Error Handling**: Catch domain errors at the application or adapter layer -3. **Error Messages**: Keep messages user-friendly but informative -4. **Metadata**: Include relevant context in metadata for debugging -5. **Error Types**: Use `DOMAIN.INVALID_STATE` for state violations, - `DOMAIN.INVALID_VALUE` for value violations - -## Serialization - -All errors can be serialized using `toObject()`: - -```typescript -const error = InvalidAuthTokenError.create('Token invalid', { +throw InvalidAuthTokenError.create('Authentication token is invalid', { actualLength: 20, minLength: 32, }); -const serialized = error.toObject(); -// { -// code: 'AUTH_CORE_TOKEN.INVALID', -// message: 'Token invalid', -// type: 'DOMAIN.INVALID_VALUE', -// metadata: { actualLength: 20, minLength: 32 } -// } +throw InvalidSessionError.create(); ``` -## String Representation +## Serialization -Errors can be converted to strings using `toString()`: +`toObject()` returns `{ code, message, metadata }` β€” no `type` field. -```typescript -const error = InvalidSessionError.create(); -console.log(error.toString()); -// Output: [AUTH_CORE_SESSION.INVALID] Session state is invalid -``` +## Related + +- Registry: `auth-core-error.registry.ts` +- Base class: `auth-domain.error.ts` (optional base; most errors extend + `DomainError` directly) +- Passwordless errors: separate `PasswordlessErrorRegistry` in passwordless + package +- OTP errors: `OtpErrorRegistry` in OTP package + (`AUTH_OTP.AUTHENTICATION_FAILED`) diff --git a/packages/authentication/core/src/domain/value-objects/README.md b/packages/authentication/core/src/domain/value-objects/README.md index c40c92c..487366a 100644 --- a/packages/authentication/core/src/domain/value-objects/README.md +++ b/packages/authentication/core/src/domain/value-objects/README.md @@ -1,221 +1,101 @@ # Authentication Domain Value Objects -## Overview +Value objects in `@rineex/auth-core`. Immutable, validated domain concepts. -This document describes the value objects used in the authentication core -package. Value objects are immutable domain concepts that represent values -rather than entities with identity. +## Exported (public API) -## MFA Value Objects +Available from `@rineex/auth-core` via `src/index.ts`: -### MfaChallengeId +| VO | Description | +| ------------------ | -------------------------------------------------- | +| `AuthAttemptId` | UUID identifier for authentication attempts | +| `AuthFactor` | Auth factor name (registry: `password`) | +| `AuthMethod` | Auth method name (registry: `passwordless`, `otp`) | +| `AuthPolicy` | Policy name (registry: `base`) | +| `AuthStatus` | Attempt status: `pending`, `succeed`, `failed` | +| `IdentityId` | UUID identifier for identities | +| `IdentityProvider` | Provider name (registry currently empty) | +| `RiskSignal` | Risk signal name (registry currently empty) | -Represents the unique identifier for an MFA challenge. - -**Extends**: `DomainID` from `@rineex/ddd` - -**Methods**: - -- `static generate()`: Creates a new UUID-based challenge ID -- `static fromString(value: string)`: Creates from a UUID string -- `toString()`: Returns the UUID string representation - -**Example**: +### Examples ```typescript -// Generate a new challenge ID -const challengeId = MfaChallengeId.generate(); +import { AuthAttemptId, AuthStatus, IdentityId } from '@rineex/auth-core'; -// Create from string -const challengeId = MfaChallengeId.fromString( +const attemptId = AuthAttemptId.generate(); +const status = AuthStatus.pending(); +const identityId = IdentityId.fromString( '550e8400-e29b-41d4-a716-446655440000', ); ``` -### MfaSessionId +## In-tree (not exported) -Represents the unique identifier for an MFA session. +### Identity -**Extends**: `DomainID` from `@rineex/ddd` +- `IdentityStatus` β€” used by unexported Identity aggregate -**Methods**: - -- `static generate()`: Creates a new UUID-based session ID -- `static fromString(value: string)`: Creates from a UUID string -- `toString()`: Returns the UUID string representation - -**Example**: - -```typescript -// Generate a new session ID -const sessionId = MfaSessionId.generate(); +### MFA -// Create from string -const sessionId = MfaSessionId.fromString( - '550e8400-e29b-41d4-a716-446655440000', -); -``` +| VO | Description | +| -------------------- | ------------------------------------------ | +| `MfaChallengeId` | MFA challenge UUID | +| `MfaSessionId` | MFA session UUID | +| `MfaChallengeStatus` | `pending`, `verified`, `expired`, `failed` | -### MfaChallengeStatus +### OAuth -Represents the status of an MFA challenge. +| VO | Description | +| --------------------------------------- | ------------------------ | +| `OauthAuthorizationId` | OAuth authorization UUID | +| `AuthorizationCodeId` | Authorization code ID | +| `AuthorizationCode` | OAuth authorization code | +| `ClientId` | OAuth client identifier | +| `CodeChallenge` / `CodeChallengeMethod` | PKCE | +| `Pkce` | PKCE bundle | +| `OauthProvider` | Provider name | +| `RedirectUri` | Redirect URI | +| `Scope` / `ScopeSet` | OAuth scopes | -**Extends**: `PrimitiveValueObject` from `@rineex/ddd` +### Session -**Valid Values**: +| VO | Description | +| ----------- | ------------ | +| `SessionId` | Session UUID | -- `'pending'`: Challenge has been issued but not yet verified -- `'verified'`: Challenge has been successfully verified -- `'expired'`: Challenge has expired without being verified -- `'failed'`: Challenge verification failed +### Token -**Methods**: +| VO | Description | +| -------------- | ----------------------------------------------- | +| `AuthToken` | Abstract cryptographic token (not JWT-specific) | +| `SessionToken` | Session-scoped token | -- `static create(value: MfaChallengeStatusValue)`: Creates a status from a - string value -- `static pending()`: Creates a pending status -- `static verified()`: Creates a verified status -- `toString()`: Returns the status string +## MFA Value Object Details -**Example**: +### MfaChallengeStatus ```typescript -// Create a pending status const status = MfaChallengeStatus.pending(); - -// Create from string -const status = MfaChallengeStatus.create('verified'); - -// Check value -if (status.value === 'pending') { - // Handle pending challenge -} +const verified = MfaChallengeStatus.create('verified'); ``` -**Validation**: Throws `InvalidMfaChallengeStatusError` if value is not in the -allowed list. - -## Token Value Objects +Throws `InvalidMfaChallengeStatusError` for invalid values. ### AuthToken -Abstract base class representing a cryptographic authentication token. - -**Extends**: `PrimitiveValueObject` from `@rineex/ddd` - -**Important Notes**: - -- This is NOT a JWT -- This is NOT a bearer string -- This is a domain-level proof of authentication -- Concrete formats (JWT, opaque tokens, etc.) live in adapters - -**Abstract Properties**: - -- `readonly type: string`: Token type identifier (e.g., 'JWT', 'OPAQUE') - -**Validation**: - -- Minimum length: 32 characters -- Throws `InvalidAuthTokenError` if validation fails - -**Example**: - -```typescript -class JwtAuthToken extends AuthToken { - readonly type = 'JWT'; - - static fromString(value: string): JwtAuthToken { - return new JwtAuthToken(value); - } -} - -const token = JwtAuthToken.fromString('a'.repeat(32)); -``` - -## OTP Value Objects - -### OtpCode - -Represents a One-Time Password (OTP) code. +Abstract base β€” concrete formats (JWT, opaque) live in consumer adapters. +Minimum length: 32 characters. Throws `InvalidAuthTokenError`. -**Extends**: `PrimitiveValueObject` from `@rineex/ddd` - -**Format**: 6-digit numeric string (e.g., '123456') - -**Methods**: - -- `static create(value: string)`: Creates an OTP code from a string -- `toString()`: Returns the OTP code string - -**Example**: - -```typescript -// Create an OTP code -const code = OtpCode.create('123456'); - -// Access the value -const value = code.value; // '123456' -``` - -**Validation**: - -- Must be exactly 6 digits -- Must match pattern `/^\d{6}$/` -- Throws `Error` if validation fails - -## Usage Guidelines - -1. **Immutability**: Value objects are immutable - never modify their values -2. **Equality**: Use `equals()` method for comparison, not `===` -3. **Validation**: Validation happens during construction -4. **Serialization**: Use `toString()` or `value` property for serialization -5. **Type Safety**: Use TypeScript types to ensure correct usage - -## Common Patterns - -### Creating Value Objects - -```typescript -// From string -const id = MfaChallengeId.fromString('uuid-string'); - -// Generate new -const id = MfaChallengeId.generate(); - -// From primitive -const status = MfaChallengeStatus.create('pending'); -``` - -### Comparing Value Objects - -```typescript -const id1 = MfaChallengeId.fromString('uuid-1'); -const id2 = MfaChallengeId.fromString('uuid-2'); - -// Use equals() method -if (id1.equals(id2)) { - // IDs are equal -} -``` - -### Serialization - -```typescript -const status = MfaChallengeStatus.pending(); - -// To string -const str = status.toString(); // 'pending' - -// To primitive value -const value = status.value; // 'pending' -``` +## Guidelines -## Error Handling +1. **Immutability** β€” never mutate VO values after construction +2. **Equality** β€” use `equals()`, not `===` (except primitives inside) +3. **Validation** β€” happens at construction; throws `DomainError` or + `InvalidValueObjectError` +4. **Serialization** β€” use `.value` or `toString()` -All value objects validate their values during construction and throw domain -errors when validation fails: +## Known gaps -- `MfaChallengeStatus`: Throws `InvalidMfaChallengeStatusError` -- `AuthToken`: Throws `InvalidAuthTokenError` -- `OtpCode`: Throws generic `Error` (consider migrating to domain error) +- `OtpCode` in core throws generic `Error` β€” migrate to domain error (see + GAP_ANALYSIS.md) +- `IdentityStatus` not exported despite use in Identity aggregate diff --git a/packages/authentication/methods/otp/README.md b/packages/authentication/methods/otp/README.md new file mode 100644 index 0000000..03e218f --- /dev/null +++ b/packages/authentication/methods/otp/README.md @@ -0,0 +1,89 @@ +# @rineex/authentication-method-otp + +OTP authentication method for the Rineex auth system. Implements +`AuthMethodPort` from `@rineex/auth-core` and delegates delivery/verification to +`OtpChannelPort`. + +## Installation + +```bash +pnpm add @rineex/authentication-method-otp @rineex/auth-core @rineex/ddd +``` + +## Public API + +| Export | Description | +| ---------------- | ------------------------------- | +| `OtpAuthMethod` | `AuthMethodPort` implementation | +| `OtpChannelPort` | Delivery and verification seam | + +## Usage + +```typescript +import { + OtpAuthMethod, + OtpChannelPort, +} from '@rineex/authentication-method-otp'; +import { OtpCode } from './otp-code'; // internal VO; provide generator + +const channel: OtpChannelPort = { + async sendOtp(identityId, otp) { + // deliver via SMS, email, etc. + }, + async verifyOtp(identityId, otp) { + return true; + }, +}; + +const otpMethod = new OtpAuthMethod(channel, () => OtpCode.generate()); + +// Register with flow orchestration at composition root +const outcome = await otpMethod.start({ + authAttemptId, + ctx: { identityId: identityId.value }, +}); +``` + +## AuthMethodPort contract + +- `start` β€” generates OTP via `otpGenerator`, sends via `OtpChannelPort` +- `verify` β€” parses payload, verifies via `OtpChannelPort` + +Returns `AuthMethodOutcome`: `{ ok: true }` or `{ ok: false, violation }`. + +This is **not** `Result` β€” the application layer wraps outcomes as needed. + +## Error registry + +```typescript +export const OtpErrorRegistry = { + AUTH_OTP: ['AUTHENTICATION_FAILED'], +} as const; +``` + +`OtpAuthenticationError` uses code `AUTH_OTP.AUTHENTICATION_FAILED`. + +## Integration with auth-core + +1. Implement `OtpChannelPort` in your infrastructure layer +2. Instantiate `OtpAuthMethod` with channel + generator +3. Register with `AuthenticationMethodResolver` (consumer wiring) +4. Wire `AuthenticationAttemptRepositoryPort` and flow services + +## Development + +```bash +cd packages/authentication/methods/otp +pnpm test +pnpm lint +pnpm check-types +``` + +## Related + +- [@rineex/auth-core](../../core/README.md) +- [Passwordless method](../passwordless/README.md) β€” standalone challenge flow + +## License + +Apache-2.0 diff --git a/packages/authentication/methods/passwordless/DOCS.md b/packages/authentication/methods/passwordless/DOCS.md index 2771d02..f8e4be8 100644 --- a/packages/authentication/methods/passwordless/DOCS.md +++ b/packages/authentication/methods/passwordless/DOCS.md @@ -1,198 +1,5 @@ -# Passwordless Authentication Method β€” Production Documentation +# Passwordless β€” Documentation -## 1. Overview +See [README.md](./README.md) for the complete guide. -### What this component does - -- Manages the lifecycle of passwordless authentication challenges (OTP, magic - links, etc.) via a challenge–response flow. -- Exposes two application services: **issue** (create and persist a challenge) - and **verify** (load, verify secret, persist state). -- Uses a channel-agnostic design: delivery (email, SMS, push, authenticator app) - is implemented by adapters against the channel port. -- Emits domain events on challenge issuance and verification; uses timing-safe - secret comparison. - -### When to use - -- Passwordless login (magic links, OTP). -- OTP-based MFA verification. -- Any flow that needs short-lived, single-use challenge verification without - passwords. - -### When not to use - -- Password-based auth (use a dedicated auth method). -- Session or token lifecycle (this package only handles challenge issue/verify). -- Without implementing the required ports (repository, ID generator, clock for - issue; repository for verify). - ---- - -## 2. Public API - -### Application services - -| Service | Constructor | Purpose | -| ------------------------------------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -| `IssuePasswordlessChallengeService` | `(repository, idGenerator, clock)` | Creates a new challenge, persists it, returns `Result`. | -| `VerifyPasswordlessChallengeService` | `(repository)` | Loads challenge by ID, checks existence/expiry/secret, calls aggregate `verify`, saves, returns `Result`. | - -#### IssuePasswordlessChallengeService.execute - -- **Parameters:** `{ channel, destination, secret, ttlSeconds? }` - - `channel`: `PasswordlessChannel` (e.g. `email`, `sms`, `authenticator_app`, - `push`). - - `destination`: `ChallengeDestination` (min 3 chars). - - `secret`: `ChallengeSecret` (min 4 chars). - - `ttlSeconds`: optional, `ms`-compatible string; default `'300s'`. -- **Returns:** `Promise>`. -- **Errors:** Any exception from aggregate creation or repository save is caught - and returned as `Result.fail(error)`. - -#### VerifyPasswordlessChallengeService.execute - -- **Parameters:** `{ id: PasswordlessChallengeId, secret: ChallengeSecret }`. -- **Returns:** `Promise>`. -- **Success path:** Challenge found, not expired, secret matches β†’ aggregate - verified and saved β†’ `Result.ok(challenge)`. -- **Failure modes (Result.fail):** - - `PasswordlessChallengeNotFoundError` β€” `findById` returned `null`. - - `PasswordlessChallengeExpiredError` β€” `challenge.isExpired()` is true. - - `PasswordlessChallengeSecretMismatchError` β€” - `challenge.matchesSecret(secret.value)` is false. - - Any error thrown inside the `try` (e.g. - `PasswordlessChallengeAlreadyUsedError` from `verify()`, or repository - rejections) is returned as `Result.fail(error)`. - -### Domain aggregate - -- **PasswordlessChallengeAggregate** - - **Static:** `issue({ id, createdAt?, props })` β€” creates aggregate, emits - `PasswordlessChallengeIssuedEvent`. - - **Instance:** `verify(secret, now?)`, `isExpired(now?)`, - `matchesSecret(input)`, `toObject()`, `validate()`. - - **verify()** throws: `PasswordlessChallengeExpiredError`, - `PasswordlessChallengeAlreadyUsedError`, - `PasswordlessChallengeSecretMismatchError`. - -### Ports (implement these) - -| Port | Contract | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `PasswordlessChallengeRepository` | `save(challenge): Promise`; `findById(id: string): Promise`. | -| `PasswordlessIdGeneratorPort` | `generate(): PasswordlessChallengeId`. | -| `ClockPort` | `now(): Date`. | -| `PasswordlessChannelPort` | `channelName: PasswordlessChannel`; `deliver(destination, secret): Promise`. | - -### Registry - -- **PasswordlessChannelRegistry** - - **Static:** `init(channels: readonly PasswordlessChannelPort[])` β€” builds - registry; throws if duplicate channel name. - - **Instance:** `resolve(key: string): PasswordlessChannelPort` (throws if - unknown); `supports(channel: PasswordlessChannel): boolean`. - -### Value objects (factory / validation) - -- `PasswordlessChannel.create(value)` β€” - `'email' | 'sms' | 'authenticator_app' | 'push'`. -- `ChallengeDestination.create(value)` β€” min 3 chars. -- `ChallengeSecret.create(value)` β€” min 4 chars. -- `OtpCode.create(value)` β€” exactly 6 digits. -- `PasswordlessChallengeId.fromString(id)`. -- `PasswordlessChallengeStatus`: `issued()`, `verified()`, `expired()`. - -### Domain events - -- `PasswordlessChallengeIssuedEvent` β€” name - `auth.passwordless.challenge_created`; payload includes `channel`, - `destination`, `expiresAt`. -- `PasswordlessChallengeVerifiedEvent` β€” name - `auth.passwordless.challenge_verified`; payload includes `channel`, - `destination`, `verifiedAt`. - ---- - -## 3. Usage examples - -### Issue then verify (recommended: use services) - -```typescript -const issueService = new IssuePasswordlessChallengeService( - challengeRepository, - idGenerator, - clock, -); - -const issueResult = await issueService.execute({ - channel: PasswordlessChannel.create('email'), - destination: ChallengeDestination.create('user@example.com'), - secret: ChallengeSecret.create('123456'), - ttlSeconds: '5m', -}); - -if (issueResult.isFailure) return handle(issueResult.getError()); -const challenge = issueResult.getValue(); -// Deliver challenge.props.secret via channel; then later: - -const verifyService = new VerifyPasswordlessChallengeService( - challengeRepository, -); -const verifyResult = await verifyService.execute({ - id: challenge.id, - secret: ChallengeSecret.create(userInputCode), -}); - -if (verifyResult.isSuccess) { - const verified = verifyResult.getValue(); - // Create session or token from verified challenge. -} -``` - -### Repository contract - -```typescript -const repository: PasswordlessChallengeRepository = { - findById: id => db.findChallengeById(id), - save: challenge => db.saveChallenge(challenge), -}; -``` - -### Channel registry - -```typescript -const registry = PasswordlessChannelRegistry.init([emailChannel, smsChannel]); -const channel = registry.resolve('email'); -if (registry.supports(PasswordlessChannel.create('sms'))) { - // use SMS -} -``` - ---- - -## 4. Behavior and guarantees - -- **Invariants:** Expiration time is after issuance; challenge is single-use - (verified or expired is final); secret comparison is timing-safe (e.g. - SHA-256 + `crypto.timingSafeEqual`). -- **Idempotency:** Verify is not idempotent β€” second verify of the same - challenge throws / returns error (already used or expired). -- **Ordering:** Issue before verify; persist after verify for state to be - durable. -- **Concurrency:** No in-process locking; uniqueness and overwrites are the - responsibility of the repository implementation. - ---- - -## 5. Operational notes - -- **Configuration:** Default TTL is `'300s'`; accept `ms`-style strings (e.g. - `'5m'`, `'300s'`). -- **Observability:** Domain events are the hook for logging/metrics; no built-in - logging in the services. -- **Pitfalls:** - - Always persist after verify (service does this). - - Do not reuse challenges; issue a new one per attempt. - - Register channel implementations before use; use a single clock source to - avoid skew and premature expiration. +This file is kept as a pointer to avoid maintaining duplicate documentation. diff --git a/packages/authentication/methods/passwordless/High-level-Architrcture.md b/packages/authentication/methods/passwordless/High-level-Architrcture.md index b20230c..f8b3f64 100644 --- a/packages/authentication/methods/passwordless/High-level-Architrcture.md +++ b/packages/authentication/methods/passwordless/High-level-Architrcture.md @@ -1,17 +1,45 @@ -+--------------------------+ | Application Service Layer| -+--------------------------+ | IssuePasswordlessService | | ------------------------- | | Responsibilities: | | - Orchestrates aggregate | -| - Uses ports | | - Returns Result | +-----------+--------------+ | v -+-------------------------------+ | PasswordlessChallengeAggregate| -+-------------------------------+ | Properties: | | - id | | - channel | | - -destination | | - secret | | - status | | - issuedAt / expiresAt | | Methods: | -| - issue() | | - verify() | | - isExpired() | +-----------+-------------------+ -| v +-------------------+ +--------------------+ | Ports: | | Value Objects | -+-------------------+ +--------------------+ | - -PasswordlessChallengeRepositoryPort | - PasswordlessChallengeId | | - -PasswordlessIdGeneratorPort | - PasswordlessChannel | | - PasswordlessClockPort -| - ChallengeDestination | | - PasswordlessChannelPort (channel) | - -ChallengeSecret | | | - PasswordlessChallengeStatus | +-------------------+ -+--------------------+ | v +----------------------+ | Infrastructure Layer | -+----------------------+ | - DB Repository | | - Clock Adapter | | - Channel -Adapter | | (Email, SMS, Push, Auth App) | +----------------------+ +# Passwordless β€” High-Level Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Application Service Layer β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ IssuePasswordlessChallengeService β”‚ +β”‚ VerifyPasswordlessChallengeService β”‚ +β”‚ β€’ Orchestrate aggregate β”‚ +β”‚ β€’ Coordinate ports β”‚ +β”‚ β€’ Return Result (v5 API: Result.err, not Result.fail) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PasswordlessChallengeAggregate β”‚ +β”‚ issue() Β· verify() Β· isExpired() Β· matchesSecret() β”‚ +β”‚ Events: Issued Β· Verified β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Ports β”‚ β”‚ Value Objects β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ PasswordlessChallengeβ”‚ β”‚ PasswordlessChallengeIdβ”‚ +β”‚ Repository β”‚ β”‚ PasswordlessChannel β”‚ +β”‚ PasswordlessId β”‚ β”‚ ChallengeDestination β”‚ +β”‚ GeneratorPort β”‚ β”‚ ChallengeSecret β”‚ +β”‚ PasswordlessChannel β”‚ β”‚ PasswordlessChallenge β”‚ +β”‚ Port β”‚ β”‚ Status β”‚ +β”‚ ClockPort (@rineex/ β”‚ β”‚ OtpCode β”‚ +β”‚ ddd) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Infrastructure (consumer-provided) β”‚ +β”‚ DB repository Β· Clock adapter Β· Email/SMS/Push channels β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Integration note + +Passwordless is **not** on `AuthMethodPort` yet. OTP uses the port; passwordless +uses standalone services. See `@rineex/auth-core` GAP_ANALYSIS.md. diff --git a/packages/authentication/methods/passwordless/README.md b/packages/authentication/methods/passwordless/README.md index cac5a43..758f30f 100644 --- a/packages/authentication/methods/passwordless/README.md +++ b/packages/authentication/methods/passwordless/README.md @@ -136,23 +136,28 @@ between domain, application, and infrastructure layers. 4. Service calls `PasswordlessChallengeAggregate.issue()` factory method 5. Aggregate validates invariants and emits `PasswordlessChallengeIssuedEvent` 6. Service persists aggregate via `PasswordlessChallengeRepository` -7. Service returns `Result.ok(challenge)` or `Result.fail(error)` +7. Service returns `Result.ok(challenge)` or `Result.err(error)` ### Flow: Verifying a Challenge 1. Application service receives challenge ID and secret 2. Service loads challenge from repository via `findById()` -3. Service checks if challenge exists (returns `Result.fail` if not found) -4. Service checks if challenge is expired (returns `Result.fail` if expired) +3. Service checks if challenge exists (returns `Result.err` if not found) +4. Service checks if challenge is expired (returns `Result.err` if expired) 5. Service calls `challenge.verify(secret)` on aggregate 6. Aggregate validates secret using timing-safe comparison 7. Aggregate updates status to `verified` and emits `PasswordlessChallengeVerifiedEvent` 8. Service persists updated aggregate -9. Service returns `Result.ok(challenge)` or `Result.fail(error)` +9. Service returns `Result.ok(challenge)` or `Result.err(error)` ## Public API +> **Export gap:** The runtime API described below exists in source but is **not +> exported** from `src/index.ts` today β€” the package entry only loads type +> augmentation for `AuthMethodRegistry`. Import from source paths in the +> monorepo or wait until exports are added (see auth-core GAP_ANALYSIS.md). + ### PasswordlessChallengeAggregate The aggregate root managing passwordless challenge state and behavior. @@ -247,7 +252,7 @@ type Input = { Any other error thrown during execution (e.g. `PasswordlessChallengeAlreadyUsedError` from the aggregate, or repository -rejections) is caught and returned as `Result.fail(error)`. +rejections) is caught and returned as `Result.err(error)`. ### Value Objects diff --git a/packages/ddd/README.md b/packages/ddd/README.md index d16888b..f10a7e4 100644 --- a/packages/ddd/README.md +++ b/packages/ddd/README.md @@ -2,8 +2,6 @@ > Domain-Driven Design (DDD) primitives for building maintainable, scalable > TypeScript applications. -> -> _(Test change for version-in-same-PR workflow verification.)_ [![npm version](https://img.shields.io/npm/v/@rineex/ddd)](https://www.npmjs.com/package/@rineex/ddd) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) @@ -32,7 +30,7 @@ ## Overview `@rineex/ddd` provides type-safe building blocks for implementing Domain-Driven -Design patterns. Used by `@rineex/authentication` and other Rineex packages. +Design patterns. Used by `@rineex/auth-core` and other Rineex packages. **Features:** Value Objects, Entities, Aggregate Roots, Domain Events, Domain Errors (extensible namespaces), Result type, Application Service port, Clock @@ -62,7 +60,12 @@ import { AggregateId, DomainID, Email, + IPAddress, DomainError, + InferErrorCodes, + registryErrorCodes, + CoreDomainErrorRegistry, + BaseMapper, InvalidValueObjectError, EntityValidationError, InvalidValueError, @@ -191,6 +194,15 @@ email.value; // 'user@example.com' email.toString(); ``` +### Pre-built: IPAddress + +```typescript +import { IPAddress } from '@rineex/ddd'; + +const ip = IPAddress.fromString('192.168.1.1'); +ip.value; // '192.168.1.1' +``` + ### Pre-built: AggregateId & DomainID ```typescript @@ -215,7 +227,7 @@ Entities have stable identity. Equality is by `id`, not attributes. Use `mutate(updater)` for state changes; it re-freezes and re-validates. Use `AggregateId` or extend `DomainID` for custom identity types. -### Example (from `@rineex/authentication` OAuthAuthorization) +### Example (from `@rineex/auth-core` OAuthAuthorization) ```typescript import { Entity, EntityProps, DomainID } from '@rineex/ddd'; @@ -535,11 +547,16 @@ there is no `DomainError` default. ### Layer contract -| Layer | Mechanism | -| -------------------------------- | ------------------------------- | -| Domain (entity, VO, aggregate) | Throw on invariant violation | -| Application (use case) | Return `Result` | -| Infrastructure (HTTP, messaging) | Unwrap `Result` at the boundary | +| Layer | Mechanism | +| -------------------------------- | ------------------------------------------ | +| Domain (entity, VO, aggregate) | Throw on invariant violation | +| Application (use case) | Return `Result` from service methods | +| Infrastructure (HTTP, messaging) | Unwrap `Result` at the boundary | + +`ApplicationServicePort` returns `Promise` β€” it is a structural seam for +orchestration. Application services that model expected failures should return +`Result` from their own `execute` methods (or wrap the port call at the +composition root). ### Example @@ -548,6 +565,7 @@ import { Result, UseCaseError, InvalidValueError } from '@rineex/ddd'; // Creation const ok = Result.ok(42); +const voidOk = Result.ok(); // Ok for command use cases with no return value const failed = Result.err(new InvalidValueError('Invalid')); // Narrowing @@ -596,10 +614,13 @@ function createAccount( ## Application Services -Use `ApplicationServicePort` for use-case orchestration. +Use `ApplicationServicePort` for use-case orchestration. The port +signature is `execute(args: I): Promise` β€” it does not return `Result`. +Services that need explicit failure channels return `Result` from a dedicated +method or wrap domain outcomes at the caller. ```typescript -import { ApplicationServicePort, Result } from '@rineex/ddd'; +import { ApplicationServicePort, Result, InvalidValueError } from '@rineex/ddd'; interface CreateUserInput { name: string; @@ -620,6 +641,15 @@ class CreateUserService implements ApplicationServicePort< return { id: '...', name: args.name }; } } + +// Command with no return value β€” use Result.ok() +async function deactivateUser( + id: string, +): Promise> { + if (!id) return Result.err(new InvalidValueError('ID required')); + // ... persist + return Result.ok(); +} ``` --- @@ -672,6 +702,47 @@ const frozen = deepFreeze({ a: 1, nested: { b: 2 } }); --- +## Core Concepts + +Value Objects, Entities, Aggregate Roots, and Domain Events are documented in +the sections above. Domain errors use registry-backed codes; application +outcomes use `Result`. + +--- + +## Examples + +See [Aggregate Roots](#aggregate-roots) (Order example) and +[Integration Guide](#integration-guide) for end-to-end patterns. + +--- + +## Best Practices + +- Extend `DomainID` for branded aggregate identifiers +- Use `mutate()` for entity state changes β€” never mutate `props` directly +- Throw `DomainError` in domain layer; return `Result` in application layer +- Register error codes in a bounded-context registry and verify with + architecture tests +- Call `pullDomainEvents()` after persistence, then publish + +--- + +## Error Handling + +See [Domain Errors](#domain-errors) for `DomainError`, registries, and built-in +error classes. + +--- + +## Contributing + +Develop in `packages/ddd`. Run `pnpm test`, `pnpm lint`, and `pnpm check-types` +from the package directory. Add a changeset for publishable changes: +`pnpm changeset` from the monorepo root. + +--- + ## API Reference ### ValueObject\ @@ -733,6 +804,7 @@ Extends `Entity`. Adds: | Member | Description | | ---------------------------------------------- | ----------------------------------------- | +| `Result.ok()` | Success with no value (`Ok`) | | `Result.ok(value)` | Success (`{ kind: 'ok', value }`) | | `Result.err(error)` | Failure (`{ kind: 'err', error }`) | | `Result.isOk(r)` / `Result.isErr(r)` | Type guards | diff --git a/packages/ioredis/README.md b/packages/ioredis/README.md index b508bf3..cc6115a 100644 --- a/packages/ioredis/README.md +++ b/packages/ioredis/README.md @@ -1,3 +1,5 @@ +# @rineex/ioredis + ## Overview `@rineex/ioredis` provides a NestJS integration for diff --git a/packages/libs/README.md b/packages/libs/README.md index 7318c05..c81d529 100644 --- a/packages/libs/README.md +++ b/packages/libs/README.md @@ -1,6 +1,10 @@ -# libs +# @rineex/libs -libs package for Rineex core modules +Shared utility libraries for Rineex core modules. + +> **Note:** This package is published but most utilities are not yet exported +> from the public entry. `src/index.ts` currently exports an empty object. +> Internal utilities (e.g. `default-if-blank`) exist in source for monorepo use. ## Installation @@ -11,16 +15,9 @@ pnpm add @rineex/libs ## Development ```bash -# Build the package pnpm build - -# Run tests pnpm test - -# Lint code pnpm lint - -# Check types pnpm check-types ``` diff --git a/packages/pg-slonik/README.md b/packages/pg-slonik/README.md index 962eb8a..5e6fa50 100644 --- a/packages/pg-slonik/README.md +++ b/packages/pg-slonik/README.md @@ -4,6 +4,9 @@ > connection management with dependency injection, automatic retry logic, and > graceful shutdown handling. +> See also: [API Reference](./docs/API.md) for formal type and decorator +> documentation. + [![npm version](https://img.shields.io/npm/v/@rineex/pg-slonik)](https://www.npmjs.com/package/@rineex/pg-slonik) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue.svg)](https://www.typescriptlang.org/) diff --git a/packages/pg-slonik/docs/API.md b/packages/pg-slonik/docs/API.md index 5ef01ef..1dd3e44 100644 --- a/packages/pg-slonik/docs/API.md +++ b/packages/pg-slonik/docs/API.md @@ -1,5 +1,7 @@ # @rineex/pg-slonik β€” API Documentation +> [← Back to package README](../README.md) + ## 1. Overview **What it does:** NestJS module that provides one or more diff --git a/project-words.txt b/project-words.txt index e554c43..6275cd2 100644 --- a/project-words.txt +++ b/project-words.txt @@ -35,3 +35,7 @@ Turborepo unplugin uuidv vitest +Versionable +versionable +Explainability +uppercased