@enderworld/onlyapi 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/LICENSE +21 -0
  3. package/README.md +338 -0
  4. package/dist/cli.js +14 -0
  5. package/package.json +69 -0
  6. package/src/application/dtos/admin.dto.ts +25 -0
  7. package/src/application/dtos/auth.dto.ts +97 -0
  8. package/src/application/dtos/index.ts +40 -0
  9. package/src/application/index.ts +2 -0
  10. package/src/application/services/admin.service.ts +150 -0
  11. package/src/application/services/api-key.service.ts +65 -0
  12. package/src/application/services/auth.service.ts +606 -0
  13. package/src/application/services/health.service.ts +97 -0
  14. package/src/application/services/index.ts +10 -0
  15. package/src/application/services/user.service.ts +95 -0
  16. package/src/cli/commands/help.ts +86 -0
  17. package/src/cli/commands/init.ts +301 -0
  18. package/src/cli/commands/upgrade.ts +471 -0
  19. package/src/cli/index.ts +76 -0
  20. package/src/cli/ui.ts +189 -0
  21. package/src/cluster.ts +62 -0
  22. package/src/core/entities/index.ts +1 -0
  23. package/src/core/entities/user.entity.ts +24 -0
  24. package/src/core/errors/app-error.ts +81 -0
  25. package/src/core/errors/index.ts +15 -0
  26. package/src/core/index.ts +7 -0
  27. package/src/core/ports/account-lockout.ts +15 -0
  28. package/src/core/ports/alert-sink.ts +27 -0
  29. package/src/core/ports/api-key.ts +37 -0
  30. package/src/core/ports/audit-log.ts +46 -0
  31. package/src/core/ports/cache.ts +24 -0
  32. package/src/core/ports/circuit-breaker.ts +42 -0
  33. package/src/core/ports/event-bus.ts +78 -0
  34. package/src/core/ports/index.ts +62 -0
  35. package/src/core/ports/job-queue.ts +73 -0
  36. package/src/core/ports/logger.ts +21 -0
  37. package/src/core/ports/metrics.ts +49 -0
  38. package/src/core/ports/oauth.ts +55 -0
  39. package/src/core/ports/password-hasher.ts +10 -0
  40. package/src/core/ports/password-history.ts +23 -0
  41. package/src/core/ports/password-policy.ts +43 -0
  42. package/src/core/ports/refresh-token-store.ts +37 -0
  43. package/src/core/ports/retry.ts +23 -0
  44. package/src/core/ports/token-blacklist.ts +16 -0
  45. package/src/core/ports/token-service.ts +23 -0
  46. package/src/core/ports/totp-service.ts +16 -0
  47. package/src/core/ports/user.repository.ts +40 -0
  48. package/src/core/ports/verification-token.ts +41 -0
  49. package/src/core/ports/webhook.ts +58 -0
  50. package/src/core/types/brand.ts +19 -0
  51. package/src/core/types/index.ts +19 -0
  52. package/src/core/types/pagination.ts +28 -0
  53. package/src/core/types/result.ts +52 -0
  54. package/src/infrastructure/alerting/index.ts +1 -0
  55. package/src/infrastructure/alerting/webhook.ts +100 -0
  56. package/src/infrastructure/cache/in-memory-cache.ts +111 -0
  57. package/src/infrastructure/cache/index.ts +6 -0
  58. package/src/infrastructure/cache/redis-cache.ts +204 -0
  59. package/src/infrastructure/config/config.ts +185 -0
  60. package/src/infrastructure/config/index.ts +1 -0
  61. package/src/infrastructure/database/in-memory-user.repository.ts +134 -0
  62. package/src/infrastructure/database/index.ts +37 -0
  63. package/src/infrastructure/database/migrations/001_create_users.ts +26 -0
  64. package/src/infrastructure/database/migrations/002_create_token_blacklist.ts +21 -0
  65. package/src/infrastructure/database/migrations/003_create_audit_log.ts +31 -0
  66. package/src/infrastructure/database/migrations/004_auth_platform.ts +112 -0
  67. package/src/infrastructure/database/migrations/runner.ts +120 -0
  68. package/src/infrastructure/database/mssql/index.ts +14 -0
  69. package/src/infrastructure/database/mssql/migrations.ts +299 -0
  70. package/src/infrastructure/database/mssql/mssql-account-lockout.ts +95 -0
  71. package/src/infrastructure/database/mssql/mssql-api-keys.ts +146 -0
  72. package/src/infrastructure/database/mssql/mssql-audit-log.ts +86 -0
  73. package/src/infrastructure/database/mssql/mssql-oauth-accounts.ts +118 -0
  74. package/src/infrastructure/database/mssql/mssql-password-history.ts +71 -0
  75. package/src/infrastructure/database/mssql/mssql-refresh-token-store.ts +144 -0
  76. package/src/infrastructure/database/mssql/mssql-token-blacklist.ts +54 -0
  77. package/src/infrastructure/database/mssql/mssql-user.repository.ts +263 -0
  78. package/src/infrastructure/database/mssql/mssql-verification-tokens.ts +120 -0
  79. package/src/infrastructure/database/postgres/index.ts +14 -0
  80. package/src/infrastructure/database/postgres/migrations.ts +235 -0
  81. package/src/infrastructure/database/postgres/pg-account-lockout.ts +75 -0
  82. package/src/infrastructure/database/postgres/pg-api-keys.ts +126 -0
  83. package/src/infrastructure/database/postgres/pg-audit-log.ts +74 -0
  84. package/src/infrastructure/database/postgres/pg-oauth-accounts.ts +101 -0
  85. package/src/infrastructure/database/postgres/pg-password-history.ts +61 -0
  86. package/src/infrastructure/database/postgres/pg-refresh-token-store.ts +117 -0
  87. package/src/infrastructure/database/postgres/pg-token-blacklist.ts +48 -0
  88. package/src/infrastructure/database/postgres/pg-user.repository.ts +237 -0
  89. package/src/infrastructure/database/postgres/pg-verification-tokens.ts +97 -0
  90. package/src/infrastructure/database/sqlite-account-lockout.ts +97 -0
  91. package/src/infrastructure/database/sqlite-api-keys.ts +155 -0
  92. package/src/infrastructure/database/sqlite-audit-log.ts +90 -0
  93. package/src/infrastructure/database/sqlite-oauth-accounts.ts +105 -0
  94. package/src/infrastructure/database/sqlite-password-history.ts +54 -0
  95. package/src/infrastructure/database/sqlite-refresh-token-store.ts +122 -0
  96. package/src/infrastructure/database/sqlite-token-blacklist.ts +47 -0
  97. package/src/infrastructure/database/sqlite-user.repository.ts +260 -0
  98. package/src/infrastructure/database/sqlite-verification-tokens.ts +112 -0
  99. package/src/infrastructure/events/event-bus.ts +105 -0
  100. package/src/infrastructure/events/event-factory.ts +31 -0
  101. package/src/infrastructure/events/in-memory-webhook-registry.ts +67 -0
  102. package/src/infrastructure/events/index.ts +4 -0
  103. package/src/infrastructure/events/webhook-dispatcher.ts +114 -0
  104. package/src/infrastructure/index.ts +58 -0
  105. package/src/infrastructure/jobs/index.ts +1 -0
  106. package/src/infrastructure/jobs/job-queue.ts +185 -0
  107. package/src/infrastructure/logging/index.ts +1 -0
  108. package/src/infrastructure/logging/logger.ts +63 -0
  109. package/src/infrastructure/metrics/index.ts +1 -0
  110. package/src/infrastructure/metrics/prometheus.ts +231 -0
  111. package/src/infrastructure/oauth/github.ts +116 -0
  112. package/src/infrastructure/oauth/google.ts +83 -0
  113. package/src/infrastructure/oauth/index.ts +2 -0
  114. package/src/infrastructure/resilience/circuit-breaker.ts +133 -0
  115. package/src/infrastructure/resilience/index.ts +2 -0
  116. package/src/infrastructure/resilience/retry.ts +50 -0
  117. package/src/infrastructure/security/account-lockout.ts +73 -0
  118. package/src/infrastructure/security/index.ts +6 -0
  119. package/src/infrastructure/security/password-hasher.ts +31 -0
  120. package/src/infrastructure/security/password-policy.ts +77 -0
  121. package/src/infrastructure/security/token-blacklist.ts +45 -0
  122. package/src/infrastructure/security/token-service.ts +144 -0
  123. package/src/infrastructure/security/totp-service.ts +142 -0
  124. package/src/infrastructure/tracing/index.ts +7 -0
  125. package/src/infrastructure/tracing/trace-context.ts +93 -0
  126. package/src/main.ts +479 -0
  127. package/src/presentation/context.ts +26 -0
  128. package/src/presentation/handlers/admin.handler.ts +114 -0
  129. package/src/presentation/handlers/api-key.handler.ts +68 -0
  130. package/src/presentation/handlers/auth.handler.ts +218 -0
  131. package/src/presentation/handlers/health.handler.ts +27 -0
  132. package/src/presentation/handlers/index.ts +15 -0
  133. package/src/presentation/handlers/metrics.handler.ts +21 -0
  134. package/src/presentation/handlers/oauth.handler.ts +61 -0
  135. package/src/presentation/handlers/openapi.handler.ts +543 -0
  136. package/src/presentation/handlers/response.ts +29 -0
  137. package/src/presentation/handlers/sse.handler.ts +165 -0
  138. package/src/presentation/handlers/user.handler.ts +81 -0
  139. package/src/presentation/handlers/webhook.handler.ts +92 -0
  140. package/src/presentation/handlers/websocket.handler.ts +226 -0
  141. package/src/presentation/i18n/index.ts +254 -0
  142. package/src/presentation/index.ts +5 -0
  143. package/src/presentation/middleware/api-key.ts +18 -0
  144. package/src/presentation/middleware/auth.ts +39 -0
  145. package/src/presentation/middleware/cors.ts +41 -0
  146. package/src/presentation/middleware/index.ts +12 -0
  147. package/src/presentation/middleware/rate-limit.ts +65 -0
  148. package/src/presentation/middleware/security-headers.ts +18 -0
  149. package/src/presentation/middleware/validate.ts +16 -0
  150. package/src/presentation/middleware/versioning.ts +69 -0
  151. package/src/presentation/routes/index.ts +1 -0
  152. package/src/presentation/routes/router.ts +272 -0
  153. package/src/presentation/server.ts +381 -0
  154. package/src/shared/cli.ts +294 -0
  155. package/src/shared/container.ts +65 -0
  156. package/src/shared/index.ts +2 -0
  157. package/src/shared/log-format.ts +148 -0
  158. package/src/shared/utils/id.ts +5 -0
  159. package/src/shared/utils/index.ts +2 -0
  160. package/src/shared/utils/timing-safe.ts +20 -0
@@ -0,0 +1,62 @@
1
+ export type {
2
+ UserRepository,
3
+ CreateUserData,
4
+ UpdateUserData,
5
+ UserListOptions,
6
+ } from "./user.repository.js";
7
+ export type { PasswordHasher } from "./password-hasher.js";
8
+ export type { TokenService, TokenPayload, TokenPair } from "./token-service.js";
9
+ export type { Logger, LogLevel, LogEntry } from "./logger.js";
10
+ export type { TokenBlacklist } from "./token-blacklist.js";
11
+ export type { AccountLockout } from "./account-lockout.js";
12
+ export type { AuditLog, AuditEntry, AuditQueryOptions } from "./audit-log.js";
13
+ export { AuditAction } from "./audit-log.js";
14
+ export type {
15
+ MetricsCollector,
16
+ Counter,
17
+ Histogram,
18
+ Gauge,
19
+ HistogramSnapshot,
20
+ } from "./metrics.js";
21
+ export type {
22
+ CircuitBreaker,
23
+ CircuitBreakerOptions,
24
+ } from "./circuit-breaker.js";
25
+ export { CircuitState } from "./circuit-breaker.js";
26
+ export type { RetryPolicy, RetryOptions } from "./retry.js";
27
+ export type { AlertSink, AlertPayload } from "./alert-sink.js";
28
+ export { AlertLevel } from "./alert-sink.js";
29
+ export type {
30
+ VerificationToken,
31
+ VerificationTokenRepository,
32
+ } from "./verification-token.js";
33
+ export { VerificationTokenType } from "./verification-token.js";
34
+ export type {
35
+ RefreshTokenFamily,
36
+ RefreshTokenStore,
37
+ } from "./refresh-token-store.js";
38
+ export type { ApiKey, ApiKeyRepository } from "./api-key.js";
39
+ export type { PasswordHistory, PasswordHistoryEntry } from "./password-history.js";
40
+ export type { TotpService } from "./totp-service.js";
41
+ export type {
42
+ OAuthProvider,
43
+ OAuthUserInfo,
44
+ OAuthAccount,
45
+ OAuthAccountRepository,
46
+ } from "./oauth.js";
47
+ export type {
48
+ PasswordPolicy,
49
+ PasswordPolicyConfig,
50
+ PasswordPolicyResult,
51
+ } from "./password-policy.js";
52
+ export type { DomainEvent, DomainEventType, EventBus, EventHandler } from "./event-bus.js";
53
+ export { DomainEventType as DomainEventTypes } from "./event-bus.js";
54
+ export type {
55
+ WebhookSubscription,
56
+ CreateWebhookData,
57
+ WebhookDelivery,
58
+ WebhookRegistry,
59
+ } from "./webhook.js";
60
+ export type { Job, JobHandler, SubmitJobOptions, JobQueue, JobQueueStats } from "./job-queue.js";
61
+ export { JobStatus } from "./job-queue.js";
62
+ export type { Cache } from "./cache.js";
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Background job queue port — async task processing with retry.
3
+ *
4
+ * Jobs are submitted and executed asynchronously. Failed jobs are retried
5
+ * with exponential backoff up to maxRetries.
6
+ */
7
+
8
+ import type { AppError } from "../errors/app-error.js";
9
+ import type { Result } from "../types/result.js";
10
+
11
+ export const JobStatus = {
12
+ PENDING: "pending",
13
+ RUNNING: "running",
14
+ COMPLETED: "completed",
15
+ FAILED: "failed",
16
+ DEAD: "dead",
17
+ } as const;
18
+
19
+ export type JobStatus = (typeof JobStatus)[keyof typeof JobStatus];
20
+
21
+ export interface Job {
22
+ readonly id: string;
23
+ readonly type: string;
24
+ readonly payload: Readonly<Record<string, unknown>>;
25
+ readonly status: JobStatus;
26
+ readonly attempts: number;
27
+ readonly maxRetries: number;
28
+ /** ISO 8601 — when the job should next be attempted */
29
+ readonly runAt: string;
30
+ readonly createdAt: string;
31
+ readonly updatedAt: string;
32
+ /** Last error message (if failed) */
33
+ readonly lastError?: string | undefined;
34
+ }
35
+
36
+ export type JobHandler = (payload: Readonly<Record<string, unknown>>) => Promise<void>;
37
+
38
+ export interface SubmitJobOptions {
39
+ readonly type: string;
40
+ readonly payload: Readonly<Record<string, unknown>>;
41
+ /** Max retry attempts (default: 3) */
42
+ readonly maxRetries?: number | undefined;
43
+ /** Delay before first run in ms (default: 0 = immediate) */
44
+ readonly delayMs?: number | undefined;
45
+ }
46
+
47
+ export interface JobQueue {
48
+ /** Submit a job for async processing */
49
+ submit(options: SubmitJobOptions): Result<Job, AppError>;
50
+
51
+ /** Register a handler for a job type */
52
+ registerHandler(type: string, handler: JobHandler): void;
53
+
54
+ /** Start processing jobs (called once at boot) */
55
+ start(): void;
56
+
57
+ /** Stop processing (graceful shutdown) */
58
+ stop(): void;
59
+
60
+ /** Get job by ID */
61
+ getJob(id: string): Result<Job | null, AppError>;
62
+
63
+ /** Get queue statistics */
64
+ stats(): Result<JobQueueStats, AppError>;
65
+ }
66
+
67
+ export interface JobQueueStats {
68
+ readonly pending: number;
69
+ readonly running: number;
70
+ readonly completed: number;
71
+ readonly failed: number;
72
+ readonly dead: number;
73
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Port: Logger — structured, level-based logging contract.
3
+ */
4
+ export type LogLevel = "debug" | "info" | "warn" | "error" | "fatal";
5
+
6
+ export interface LogEntry {
7
+ readonly level: LogLevel;
8
+ readonly message: string;
9
+ readonly timestamp: string;
10
+ readonly requestId?: string | undefined;
11
+ readonly [key: string]: unknown;
12
+ }
13
+
14
+ export interface Logger {
15
+ debug(msg: string, meta?: Record<string, unknown>): void;
16
+ info(msg: string, meta?: Record<string, unknown>): void;
17
+ warn(msg: string, meta?: Record<string, unknown>): void;
18
+ error(msg: string, meta?: Record<string, unknown>): void;
19
+ fatal(msg: string, meta?: Record<string, unknown>): void;
20
+ child(bindings: Record<string, unknown>): Logger;
21
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Metrics collector port — Prometheus-compatible metrics interface.
3
+ * The core defines WHAT metrics we track; infrastructure decides HOW.
4
+ */
5
+
6
+ export interface Counter {
7
+ inc(labels?: Record<string, string>, value?: number): void;
8
+ get(labels?: Record<string, string>): number;
9
+ }
10
+
11
+ export interface Histogram {
12
+ observe(value: number, labels?: Record<string, string>): void;
13
+ /** Returns { count, sum, buckets: Map<number, number> } per label set */
14
+ get(labels?: Record<string, string>): HistogramSnapshot | undefined;
15
+ }
16
+
17
+ export interface Gauge {
18
+ set(value: number, labels?: Record<string, string>): void;
19
+ inc(labels?: Record<string, string>, value?: number): void;
20
+ dec(labels?: Record<string, string>, value?: number): void;
21
+ get(labels?: Record<string, string>): number;
22
+ }
23
+
24
+ export interface HistogramSnapshot {
25
+ readonly count: number;
26
+ readonly sum: number;
27
+ readonly buckets: ReadonlyMap<number, number>;
28
+ }
29
+
30
+ export interface MetricsCollector {
31
+ /** Total HTTP requests */
32
+ readonly httpRequestsTotal: Counter;
33
+ /** HTTP request duration in milliseconds */
34
+ readonly httpRequestDurationMs: Histogram;
35
+ /** Currently active connections */
36
+ readonly httpActiveConnections: Gauge;
37
+ /** Total HTTP errors (4xx + 5xx) */
38
+ readonly httpErrorsTotal: Counter;
39
+ /** Circuit breaker state changes */
40
+ readonly circuitBreakerState: Gauge;
41
+ /** Alert notifications sent */
42
+ readonly alertsSentTotal: Counter;
43
+
44
+ /** Serialize all metrics to Prometheus text exposition format */
45
+ serialize(): string;
46
+
47
+ /** Reset all metrics (useful for testing) */
48
+ reset(): void;
49
+ }
@@ -0,0 +1,55 @@
1
+ import type { AppError } from "../errors/app-error.js";
2
+ import type { UserId } from "../types/brand.js";
3
+ import type { Result } from "../types/result.js";
4
+
5
+ /**
6
+ * Port: OAuth Provider
7
+ * Adapters for external OAuth2 providers (Google, GitHub, etc.)
8
+ */
9
+
10
+ export interface OAuthUserInfo {
11
+ readonly providerId: string;
12
+ readonly email: string;
13
+ readonly name?: string;
14
+ }
15
+
16
+ export interface OAuthProvider {
17
+ /** Provider name (e.g., "google", "github") */
18
+ readonly name: string;
19
+ /** Build the authorization URL for the user to visit */
20
+ getAuthorizationUrl(state: string, redirectUri: string): string;
21
+ /** Exchange an authorization code for user info */
22
+ exchangeCode(code: string, redirectUri: string): Promise<Result<OAuthUserInfo, AppError>>;
23
+ }
24
+
25
+ /**
26
+ * Port: OAuth Account Repository
27
+ * Links external OAuth identities to internal user accounts.
28
+ */
29
+ export interface OAuthAccount {
30
+ readonly id: string;
31
+ readonly userId: UserId;
32
+ readonly provider: string;
33
+ readonly providerUserId: string;
34
+ readonly email: string | null;
35
+ readonly createdAt: number;
36
+ }
37
+
38
+ export interface OAuthAccountRepository {
39
+ /** Link an OAuth identity to a user */
40
+ link(
41
+ userId: UserId,
42
+ provider: string,
43
+ providerUserId: string,
44
+ email: string | null,
45
+ ): Promise<Result<OAuthAccount, AppError>>;
46
+ /** Find a user by their OAuth identity */
47
+ findByProvider(
48
+ provider: string,
49
+ providerUserId: string,
50
+ ): Promise<Result<OAuthAccount | null, AppError>>;
51
+ /** List all OAuth accounts for a user */
52
+ listByUser(userId: UserId): Promise<Result<readonly OAuthAccount[], AppError>>;
53
+ /** Unlink an OAuth identity from a user */
54
+ unlink(id: string, userId: UserId): Promise<Result<void, AppError>>;
55
+ }
@@ -0,0 +1,10 @@
1
+ import type { AppError } from "../errors/app-error.js";
2
+ import type { Result } from "../types/result.js";
3
+
4
+ /**
5
+ * Port: Password Hasher
6
+ */
7
+ export interface PasswordHasher {
8
+ hash(plain: string): Promise<Result<string, AppError>>;
9
+ verify(plain: string, hash: string): Promise<Result<boolean, AppError>>;
10
+ }
@@ -0,0 +1,23 @@
1
+ import type { AppError } from "../errors/app-error.js";
2
+ import type { UserId } from "../types/brand.js";
3
+ import type { Result } from "../types/result.js";
4
+
5
+ /**
6
+ * Port: Password History
7
+ * Stores past password hashes to prevent reuse.
8
+ */
9
+ export interface PasswordHistoryEntry {
10
+ readonly id: string;
11
+ readonly userId: UserId;
12
+ readonly passwordHash: string;
13
+ readonly createdAt: number;
14
+ }
15
+
16
+ export interface PasswordHistory {
17
+ /** Add a password hash to the user's history */
18
+ add(userId: UserId, passwordHash: string): Promise<Result<void, AppError>>;
19
+ /** Get the last N password hashes for a user */
20
+ getRecent(userId: UserId, count: number): Promise<Result<readonly string[], AppError>>;
21
+ /** Prune old history beyond the configured retention limit */
22
+ prune(userId: UserId, keepCount: number): Promise<Result<void, AppError>>;
23
+ }
@@ -0,0 +1,43 @@
1
+ import type { AppError } from "../errors/app-error.js";
2
+ import type { UserId } from "../types/brand.js";
3
+ import type { Result } from "../types/result.js";
4
+ import type { PasswordHasher } from "./password-hasher.js";
5
+ import type { PasswordHistory } from "./password-history.js";
6
+
7
+ /**
8
+ * Port: Password Policy
9
+ * Validates passwords against configured rules:
10
+ * - Minimum length, uppercase, lowercase, digit, special char
11
+ * - History check (no reuse of last N passwords)
12
+ * - Expiry detection
13
+ */
14
+ export interface PasswordPolicyConfig {
15
+ readonly minLength: number;
16
+ readonly requireUppercase: boolean;
17
+ readonly requireLowercase: boolean;
18
+ readonly requireDigit: boolean;
19
+ readonly requireSpecial: boolean;
20
+ readonly historyCount: number;
21
+ readonly maxAgeDays: number; // 0 = no expiry
22
+ }
23
+
24
+ export interface PasswordPolicyResult {
25
+ readonly valid: boolean;
26
+ readonly violations: readonly string[];
27
+ }
28
+
29
+ export interface PasswordPolicy {
30
+ /** Validate a password against the policy rules (no history check) */
31
+ validate(password: string): PasswordPolicyResult;
32
+ /** Check if the password was recently used (requires DB lookup) */
33
+ checkHistory(
34
+ userId: UserId,
35
+ password: string,
36
+ passwordHasher: PasswordHasher,
37
+ history: PasswordHistory,
38
+ ): Promise<Result<boolean, AppError>>;
39
+ /** Check if the user's password has expired */
40
+ isExpired(passwordChangedAt: number | null): boolean;
41
+ /** Get the current policy config */
42
+ readonly config: PasswordPolicyConfig;
43
+ }
@@ -0,0 +1,37 @@
1
+ import type { AppError } from "../errors/app-error.js";
2
+ import type { UserId } from "../types/brand.js";
3
+ import type { Result } from "../types/result.js";
4
+
5
+ /**
6
+ * Port: Refresh Token Family
7
+ * Tracks refresh token families for rotation with reuse detection.
8
+ * Each family represents a single login session.
9
+ */
10
+
11
+ export interface RefreshTokenFamily {
12
+ readonly id: string;
13
+ readonly userId: UserId;
14
+ readonly currentTokenHash: string;
15
+ readonly revoked: boolean;
16
+ readonly createdAt: number;
17
+ readonly updatedAt: number;
18
+ }
19
+
20
+ export interface RefreshTokenStore {
21
+ /** Create a new token family for a login session */
22
+ createFamily(userId: UserId, tokenHash: string): Promise<Result<string, AppError>>;
23
+ /** Rotate: set a new current token hash, return old hash for comparison */
24
+ rotate(
25
+ familyId: string,
26
+ oldTokenHash: string,
27
+ newTokenHash: string,
28
+ ): Promise<Result<void, AppError>>;
29
+ /** Find family by current token hash */
30
+ findByTokenHash(tokenHash: string): Promise<Result<RefreshTokenFamily | null, AppError>>;
31
+ /** Revoke an entire family (reuse detection) */
32
+ revokeFamily(familyId: string): Promise<Result<void, AppError>>;
33
+ /** Revoke all families for a user (logout-all) */
34
+ revokeAllForUser(userId: UserId): Promise<Result<void, AppError>>;
35
+ /** Prune expired/old families */
36
+ prune(maxAgeMs: number): Promise<Result<number, AppError>>;
37
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Retry policy port — configurable retry with backoff for transient failures.
3
+ */
4
+
5
+ export interface RetryOptions {
6
+ /** Maximum number of retry attempts (default: 3) */
7
+ readonly maxRetries: number;
8
+ /** Base delay in ms for exponential backoff (default: 100) */
9
+ readonly baseDelayMs: number;
10
+ /** Maximum delay cap in ms (default: 5000) */
11
+ readonly maxDelayMs: number;
12
+ /** Jitter factor 0–1 (default: 0.2) */
13
+ readonly jitter: number;
14
+ /** Predicate: should this error be retried? (default: all errors) */
15
+ readonly retryable?: (error: unknown) => boolean;
16
+ /** Called on each retry attempt */
17
+ readonly onRetry?: (attempt: number, error: unknown, delayMs: number) => void;
18
+ }
19
+
20
+ export interface RetryPolicy {
21
+ /** Execute a function with retry logic */
22
+ execute<T>(fn: () => Promise<T>): Promise<T>;
23
+ }
@@ -0,0 +1,16 @@
1
+ import type { AppError } from "../errors/app-error.js";
2
+ import type { Result } from "../types/result.js";
3
+
4
+ /**
5
+ * Port: Token Blacklist
6
+ * Allows checking if a token has been revoked (e.g. on logout).
7
+ * Infrastructure can implement this with in-memory Set, SQLite, or Redis.
8
+ */
9
+ export interface TokenBlacklist {
10
+ /** Add a token to the blacklist. expiresAt is epoch ms when the token naturally expires. */
11
+ add(tokenHash: string, expiresAt: number): Promise<Result<void, AppError>>;
12
+ /** Check whether a token hash is blacklisted */
13
+ isBlacklisted(tokenHash: string): Promise<Result<boolean, AppError>>;
14
+ /** Prune expired entries (housekeeping) */
15
+ prune(): Promise<Result<number, AppError>>;
16
+ }
@@ -0,0 +1,23 @@
1
+ import type { UserRole } from "../entities/user.entity.js";
2
+ import type { AppError } from "../errors/app-error.js";
3
+ import type { UserId } from "../types/brand.js";
4
+ import type { Result } from "../types/result.js";
5
+
6
+ /**
7
+ * Port: Token Service (JWT or similar)
8
+ */
9
+ export interface TokenPayload {
10
+ readonly sub: UserId;
11
+ readonly role: UserRole;
12
+ }
13
+
14
+ export interface TokenPair {
15
+ readonly accessToken: string;
16
+ readonly refreshToken: string;
17
+ }
18
+
19
+ export interface TokenService {
20
+ sign(payload: TokenPayload): Promise<Result<TokenPair, AppError>>;
21
+ verify(token: string): Promise<Result<TokenPayload, AppError>>;
22
+ refresh(refreshToken: string): Promise<Result<TokenPair, AppError>>;
23
+ }
@@ -0,0 +1,16 @@
1
+ import type { AppError } from "../errors/app-error.js";
2
+ import type { Result } from "../types/result.js";
3
+
4
+ /**
5
+ * Port: TOTP Service
6
+ * Time-based One-Time Password (RFC 6238) for MFA/2FA.
7
+ * Compatible with Google Authenticator, Authy, etc.
8
+ */
9
+ export interface TotpService {
10
+ /** Generate a random TOTP secret (base32-encoded) */
11
+ generateSecret(): string;
12
+ /** Generate an otpauth:// URI for QR code display */
13
+ generateUri(secret: string, email: string, issuer: string): string;
14
+ /** Verify a TOTP code against a secret. Allows ±1 window for clock drift. */
15
+ verify(secret: string, code: string): Result<boolean, AppError>;
16
+ }
@@ -0,0 +1,40 @@
1
+ import type { User, UserRole } from "../entities/user.entity.js";
2
+ import type { AppError } from "../errors/app-error.js";
3
+ import type { UserId } from "../types/brand.js";
4
+ import type { CursorParams, PaginatedResult } from "../types/pagination.js";
5
+ import type { Result } from "../types/result.js";
6
+
7
+ /**
8
+ * Port: User Repository
9
+ * Defines the contract the domain expects — infrastructure implements.
10
+ */
11
+ export interface UserRepository {
12
+ findById(id: UserId): Promise<Result<User, AppError>>;
13
+ findByEmail(email: string): Promise<Result<User, AppError>>;
14
+ create(data: CreateUserData): Promise<Result<User, AppError>>;
15
+ update(id: UserId, data: UpdateUserData): Promise<Result<User, AppError>>;
16
+ delete(id: UserId): Promise<Result<void, AppError>>;
17
+ list(options: UserListOptions): Promise<Result<PaginatedResult<User>, AppError>>;
18
+ count(): Promise<Result<number, AppError>>;
19
+ }
20
+
21
+ export interface CreateUserData {
22
+ readonly email: string;
23
+ readonly passwordHash: string;
24
+ readonly role: UserRole;
25
+ }
26
+
27
+ export interface UpdateUserData {
28
+ readonly email?: string | undefined;
29
+ readonly passwordHash?: string | undefined;
30
+ readonly role?: UserRole | undefined;
31
+ readonly emailVerified?: boolean | undefined;
32
+ readonly mfaEnabled?: boolean | undefined;
33
+ readonly mfaSecret?: string | null | undefined;
34
+ readonly passwordChangedAt?: number | undefined;
35
+ }
36
+
37
+ export interface UserListOptions extends CursorParams {
38
+ readonly search?: string | undefined;
39
+ readonly role?: UserRole | undefined;
40
+ }
@@ -0,0 +1,41 @@
1
+ import type { AppError } from "../errors/app-error.js";
2
+ import type { UserId } from "../types/brand.js";
3
+ import type { Result } from "../types/result.js";
4
+
5
+ /**
6
+ * Port: Verification Token Repository
7
+ * Handles time-limited tokens for email verification and password reset.
8
+ */
9
+
10
+ export const VerificationTokenType = {
11
+ EMAIL_VERIFICATION: "email_verification",
12
+ PASSWORD_RESET: "password_reset",
13
+ } as const;
14
+
15
+ export type VerificationTokenType =
16
+ (typeof VerificationTokenType)[keyof typeof VerificationTokenType];
17
+
18
+ export interface VerificationToken {
19
+ readonly id: string;
20
+ readonly userId: UserId;
21
+ readonly type: VerificationTokenType;
22
+ readonly tokenHash: string;
23
+ readonly expiresAt: number;
24
+ readonly usedAt: number | null;
25
+ readonly createdAt: number;
26
+ }
27
+
28
+ export interface VerificationTokenRepository {
29
+ /** Create a new verification token. Returns the raw (unhashed) token. */
30
+ create(
31
+ userId: UserId,
32
+ type: VerificationTokenType,
33
+ ttlMs: number,
34
+ ): Promise<Result<string, AppError>>;
35
+ /** Verify and consume a token. Marks it as used. Returns the userId. */
36
+ verify(rawToken: string, type: VerificationTokenType): Promise<Result<UserId, AppError>>;
37
+ /** Invalidate all tokens for a user of a given type */
38
+ invalidateAll(userId: UserId, type: VerificationTokenType): Promise<Result<void, AppError>>;
39
+ /** Prune expired tokens */
40
+ prune(): Promise<Result<number, AppError>>;
41
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Webhook registry port — manages outbound HTTP webhook subscriptions
3
+ * that fire on domain events.
4
+ */
5
+
6
+ import type { AppError } from "../errors/app-error.js";
7
+ import type { Result } from "../types/result.js";
8
+ import type { DomainEventType } from "./event-bus.js";
9
+
10
+ export interface WebhookSubscription {
11
+ readonly id: string;
12
+ readonly url: string;
13
+ /** Event types this webhook listens for. Empty = all events. */
14
+ readonly events: ReadonlyArray<DomainEventType>;
15
+ /** Shared secret for HMAC-SHA256 signature verification */
16
+ readonly secret: string;
17
+ /** Whether this subscription is active */
18
+ readonly active: boolean;
19
+ /** ISO 8601 creation timestamp */
20
+ readonly createdAt: string;
21
+ }
22
+
23
+ export interface CreateWebhookData {
24
+ readonly url: string;
25
+ readonly events: ReadonlyArray<DomainEventType>;
26
+ readonly secret: string;
27
+ }
28
+
29
+ export interface WebhookDelivery {
30
+ readonly id: string;
31
+ readonly webhookId: string;
32
+ readonly eventId: string;
33
+ readonly url: string;
34
+ readonly status: number;
35
+ readonly success: boolean;
36
+ readonly attemptNumber: number;
37
+ readonly deliveredAt: string;
38
+ }
39
+
40
+ export interface WebhookRegistry {
41
+ /** Create a new webhook subscription */
42
+ create(data: CreateWebhookData): Result<WebhookSubscription, AppError>;
43
+
44
+ /** List all active webhook subscriptions */
45
+ list(): Result<ReadonlyArray<WebhookSubscription>, AppError>;
46
+
47
+ /** Find subscriptions matching a specific event type */
48
+ findByEvent(eventType: DomainEventType): Result<ReadonlyArray<WebhookSubscription>, AppError>;
49
+
50
+ /** Remove a webhook subscription by ID */
51
+ remove(id: string): Result<void, AppError>;
52
+
53
+ /** Toggle a webhook subscription on/off */
54
+ setActive(id: string, active: boolean): Result<void, AppError>;
55
+
56
+ /** Record a delivery attempt */
57
+ recordDelivery(delivery: WebhookDelivery): Result<void, AppError>;
58
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Branded / Opaque type utility.
3
+ * Prevents accidental interchange of structurally identical primitives.
4
+ *
5
+ * @example
6
+ * type UserId = Brand<string, "UserId">;
7
+ * const id: UserId = "abc" as UserId;
8
+ */
9
+ declare const __brand: unique symbol;
10
+
11
+ export type Brand<T, B extends string> = T & { readonly [__brand]: B };
12
+
13
+ /** Common branded identifiers */
14
+ export type UserId = Brand<string, "UserId">;
15
+ export type RequestId = Brand<string, "RequestId">;
16
+ export type Timestamp = Brand<number, "Timestamp">;
17
+
18
+ /** Helper to create branded values (runtime no-op, compile-time safety) */
19
+ export const brand = <T, B extends string>(value: T): Brand<T, B> => value as Brand<T, B>;
@@ -0,0 +1,19 @@
1
+ export { type Brand, type UserId, type RequestId, type Timestamp, brand } from "./brand.js";
2
+ export {
3
+ type Result,
4
+ type Ok,
5
+ type Err,
6
+ ok,
7
+ err,
8
+ map,
9
+ flatMap,
10
+ unwrapOr,
11
+ tryCatch,
12
+ tryCatchAsync,
13
+ } from "./result.js";
14
+ export {
15
+ type CursorParams,
16
+ type PaginatedResult,
17
+ encodeCursor,
18
+ decodeCursor,
19
+ } from "./pagination.js";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Cursor-based pagination types.
3
+ * Cursor is an opaque base64-encoded string containing the sort key.
4
+ */
5
+
6
+ export interface CursorParams {
7
+ readonly cursor?: string | undefined;
8
+ readonly limit: number;
9
+ }
10
+
11
+ export interface PaginatedResult<T> {
12
+ readonly items: readonly T[];
13
+ readonly nextCursor: string | null;
14
+ readonly hasMore: boolean;
15
+ readonly total?: number | undefined;
16
+ }
17
+
18
+ /** Encode a cursor value (ISO timestamp or ID) to a URL-safe opaque string */
19
+ export const encodeCursor = (value: string): string => btoa(value);
20
+
21
+ /** Decode an opaque cursor back to the original value */
22
+ export const decodeCursor = (cursor: string): string | null => {
23
+ try {
24
+ return atob(cursor);
25
+ } catch {
26
+ return null;
27
+ }
28
+ };