@agentforge-io/core 2.0.23 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/ai/index.d.ts +2 -0
  2. package/dist/ai/index.js +5 -1
  3. package/dist/factory.js +56 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +7 -1
  6. package/dist/services/agent-runner.service.js +117 -7
  7. package/dist/services/agent.service.d.ts +21 -1
  8. package/dist/services/agent.service.js +77 -10
  9. package/dist/services/orchestrator.service.d.ts +40 -1
  10. package/dist/services/orchestrator.service.js +220 -0
  11. package/dist/types/agent.types.d.ts +31 -6
  12. package/dist/types/config.types.d.ts +8 -1
  13. package/dist/types/index.d.ts +1 -0
  14. package/dist/types/index.js +1 -0
  15. package/dist/types/model-strategy.d.ts +97 -0
  16. package/dist/types/model-strategy.js +83 -0
  17. package/package.json +1 -1
  18. package/dist/adapters/billing/billing-adapter.interface.d.ts +0 -41
  19. package/dist/adapters/billing/billing-adapter.interface.js +0 -5
  20. package/dist/adapters/billing/stripe/stripe.adapter.d.ts +0 -30
  21. package/dist/adapters/billing/stripe/stripe.adapter.js +0 -122
  22. package/dist/adapters/email/email-adapter.interface.d.ts +0 -25
  23. package/dist/adapters/email/email-adapter.interface.js +0 -6
  24. package/dist/adapters/email/noop.adapter.d.ts +0 -10
  25. package/dist/adapters/email/noop.adapter.js +0 -15
  26. package/dist/adapters/email/resend.adapter.d.ts +0 -8
  27. package/dist/adapters/email/resend.adapter.js +0 -39
  28. package/dist/adapters/upload/noop.adapter.d.ts +0 -9
  29. package/dist/adapters/upload/noop.adapter.js +0 -14
  30. package/dist/adapters/upload/s3.adapter.d.ts +0 -38
  31. package/dist/adapters/upload/s3.adapter.js +0 -69
  32. package/dist/adapters/upload/upload-adapter.interface.d.ts +0 -37
  33. package/dist/adapters/upload/upload-adapter.interface.js +0 -15
  34. package/dist/billing/index.d.ts +0 -12
  35. package/dist/billing/index.js +0 -28
  36. package/dist/domain/agent.d.ts +0 -59
  37. package/dist/domain/agent.js +0 -2
  38. package/dist/domain/api-key.d.ts +0 -28
  39. package/dist/domain/api-key.js +0 -2
  40. package/dist/domain/auth-identity.d.ts +0 -10
  41. package/dist/domain/auth-identity.js +0 -2
  42. package/dist/domain/email-token.d.ts +0 -11
  43. package/dist/domain/email-token.js +0 -2
  44. package/dist/domain/external-user.d.ts +0 -23
  45. package/dist/domain/external-user.js +0 -2
  46. package/dist/domain/plan.d.ts +0 -20
  47. package/dist/domain/plan.js +0 -2
  48. package/dist/domain/platform-secret.d.ts +0 -24
  49. package/dist/domain/platform-secret.js +0 -8
  50. package/dist/domain/refresh-token.d.ts +0 -15
  51. package/dist/domain/refresh-token.js +0 -2
  52. package/dist/domain/subscription.d.ts +0 -21
  53. package/dist/domain/subscription.js +0 -2
  54. package/dist/domain/tenant.d.ts +0 -21
  55. package/dist/domain/tenant.js +0 -2
  56. package/dist/domain/usage-record.d.ts +0 -15
  57. package/dist/domain/usage-record.js +0 -2
  58. package/dist/domain/user.d.ts +0 -43
  59. package/dist/domain/user.js +0 -2
  60. package/dist/services/agent-config.service.d.ts +0 -45
  61. package/dist/services/agent-config.service.js +0 -114
  62. package/dist/services/api-key.service.d.ts +0 -41
  63. package/dist/services/api-key.service.js +0 -80
  64. package/dist/services/auth.service.d.ts +0 -133
  65. package/dist/services/auth.service.js +0 -411
  66. package/dist/services/billing.service.d.ts +0 -67
  67. package/dist/services/billing.service.js +0 -254
  68. package/dist/services/email-templates.d.ts +0 -18
  69. package/dist/services/email-templates.js +0 -39
  70. package/dist/services/email.service.d.ts +0 -26
  71. package/dist/services/email.service.js +0 -42
  72. package/dist/services/errors.d.ts +0 -7
  73. package/dist/services/errors.js +0 -27
  74. package/dist/services/oauth.service.d.ts +0 -73
  75. package/dist/services/oauth.service.js +0 -174
  76. package/dist/services/plan.service.d.ts +0 -54
  77. package/dist/services/plan.service.js +0 -120
  78. package/dist/services/refresh-token.service.d.ts +0 -38
  79. package/dist/services/refresh-token.service.js +0 -73
  80. package/dist/services/secrets/crypto.d.ts +0 -37
  81. package/dist/services/secrets/crypto.js +0 -110
  82. package/dist/services/secrets/known-keys.d.ts +0 -38
  83. package/dist/services/secrets/known-keys.js +0 -50
  84. package/dist/services/secrets.service.d.ts +0 -91
  85. package/dist/services/secrets.service.js +0 -193
  86. package/dist/services/tenant-billing.service.d.ts +0 -121
  87. package/dist/services/tenant-billing.service.js +0 -290
  88. package/dist/services/tenant.service.d.ts +0 -54
  89. package/dist/services/tenant.service.js +0 -96
  90. package/dist/services/upload.service.d.ts +0 -37
  91. package/dist/services/upload.service.js +0 -84
  92. package/dist/services/usage.service.d.ts +0 -34
  93. package/dist/services/usage.service.js +0 -108
  94. package/dist/types/billing.types.d.ts +0 -82
  95. package/dist/types/billing.types.js +0 -3
@@ -1,24 +0,0 @@
1
- export interface PlatformSecret {
2
- id: string;
3
- /** Stable identifier matching the canonical env-var name. */
4
- key: string;
5
- /** Encrypted blob: AES-GCM `iv | ciphertext | tag`. Persisted as bytea. */
6
- valueEncrypted: Buffer;
7
- /** Last 4 chars of the plaintext (or "••••" when too short). UI display. */
8
- valueHint: string;
9
- /** Free-form description shown next to the field in the admin UI. */
10
- description?: string;
11
- /** User who last wrote this row. Optional because the first import from
12
- * .env may have no user attached. */
13
- updatedByUserId?: string;
14
- createdAt: Date;
15
- updatedAt: Date;
16
- }
17
- export interface NewPlatformSecret {
18
- key: string;
19
- valueEncrypted: Buffer;
20
- valueHint: string;
21
- description?: string;
22
- updatedByUserId?: string;
23
- }
24
- export type PlatformSecretPatch = Partial<Pick<PlatformSecret, 'valueEncrypted' | 'valueHint' | 'description' | 'updatedByUserId'>>;
@@ -1,8 +0,0 @@
1
- "use strict";
2
- // Domain type for platform-wide secrets (Tavily/Brave/Stripe/Anthropic).
3
- //
4
- // Note what's MISSING: the plaintext value. The plaintext only exists at
5
- // two moments in the system's life — when the operator types it into the
6
- // UI, and during the boot-time decrypt inside SecretsService. Anything
7
- // else (repos, API responses, logs) must work off the hint + ciphertext.
8
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,15 +0,0 @@
1
- export interface RefreshToken {
2
- id: string;
3
- userId: string;
4
- tokenHash: string;
5
- family: string;
6
- expiresAt: Date;
7
- revokedAt?: Date;
8
- replacedById?: string;
9
- userAgent?: string;
10
- ip?: string;
11
- createdAt: Date;
12
- lastUsedAt?: Date;
13
- }
14
- export type NewRefreshToken = Omit<RefreshToken, 'id' | 'createdAt'>;
15
- export type RefreshTokenPatch = Partial<Omit<RefreshToken, 'id' | 'createdAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,21 +0,0 @@
1
- import type { SubscriptionStatus } from '../types/billing.types';
2
- export interface Subscription {
3
- id: string;
4
- /** End-user subscription. Set for B2C; may be null in B2B mode where the
5
- * payer is the tenant, not an individual user. */
6
- userId?: string;
7
- /** Tenant subscription. Set in B2B mode; null for B2C/end-user subs. */
8
- tenantId?: string;
9
- planId: string;
10
- status: SubscriptionStatus;
11
- providerSubscriptionId?: string;
12
- providerCustomerId?: string;
13
- currentPeriodStart?: Date;
14
- currentPeriodEnd?: Date;
15
- trialEnd?: Date;
16
- cancelAtPeriodEnd: boolean;
17
- createdAt: Date;
18
- updatedAt: Date;
19
- }
20
- export type NewSubscription = Omit<Subscription, 'id' | 'createdAt' | 'updatedAt'>;
21
- export type SubscriptionPatch = Partial<Omit<Subscription, 'id' | 'createdAt' | 'updatedAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,21 +0,0 @@
1
- /**
2
- * A Tenant (a.k.a. "client") is a consumer service that signed up to use
3
- * AgentForge as a backend. Every B2B request carries an API key that resolves
4
- * to one Tenant, and all data is scoped under it. The Tenant's `ownerUserId`
5
- * points at a User row (the human admin who manages the tenant).
6
- */
7
- export interface Tenant {
8
- id: string;
9
- name: string;
10
- /** UserId of the human admin who manages this tenant. */
11
- ownerUserId: string;
12
- planId: string;
13
- /** Stripe (or other provider) customer id — lazy-created on first checkout. */
14
- providerCustomerId?: string;
15
- metadata?: Record<string, unknown>;
16
- isActive: boolean;
17
- createdAt: Date;
18
- updatedAt: Date;
19
- }
20
- export type NewTenant = Pick<Tenant, 'id' | 'name' | 'ownerUserId'> & Partial<Omit<Tenant, 'id' | 'name' | 'ownerUserId' | 'createdAt' | 'updatedAt'>>;
21
- export type TenantPatch = Partial<Omit<Tenant, 'id' | 'createdAt' | 'updatedAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,15 +0,0 @@
1
- export interface UsageRecord {
2
- id: string;
3
- userId: string;
4
- conversationId: string;
5
- messageId: string;
6
- agentId: string;
7
- inputTokens: number;
8
- outputTokens: number;
9
- totalTokens: number;
10
- requestCount: number;
11
- /** YYYY-MM bucket used by quota counters. */
12
- billingPeriod: string;
13
- recordedAt: Date;
14
- }
15
- export type NewUsageRecord = Omit<UsageRecord, 'id'>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,43 +0,0 @@
1
- /**
2
- * Roles in AgentForge's RBAC.
3
- *
4
- * - `user` — default. Can own tenants, manage their own API keys,
5
- * view their billing. Bulk of signups land here.
6
- * - `platform_admin` — operator of the AgentForge instance. Can edit the
7
- * plan catalog, manage users, and any other endpoint
8
- * that operates at instance scope.
9
- *
10
- * Bootstrap: the very first user registered on a fresh install is auto-
11
- * promoted to `platform_admin` (since there's nobody else to grant it).
12
- * Subsequent users default to `user`; existing platform admins can promote
13
- * them via the admin UI.
14
- */
15
- export type UserRole = 'user' | 'platform_admin';
16
- /**
17
- * Plain domain shape for a User. Mirrors the columns of `af_users` but is
18
- * deliberately decoupled from TypeORM (no decorators, no `@Entity`). Each
19
- * adapter is free to back this with TypeORM, Prisma, Drizzle, raw SQL, etc.
20
- */
21
- export interface User {
22
- id: string;
23
- email?: string;
24
- name?: string;
25
- passwordHash?: string;
26
- authProvider?: string;
27
- providerCustomerId?: string;
28
- currentPlanId: string;
29
- creditsBalance: number;
30
- /** RBAC role. See {@link UserRole} for semantics. */
31
- role: UserRole;
32
- externalId?: string;
33
- metadata?: Record<string, unknown>;
34
- isActive: boolean;
35
- failedLoginCount: number;
36
- lockedUntil?: Date;
37
- lastLoginAt?: Date;
38
- emailVerifiedAt?: Date;
39
- createdAt: Date;
40
- updatedAt: Date;
41
- }
42
- export type NewUser = Pick<User, 'id'> & Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;
43
- export type UserPatch = Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,45 +0,0 @@
1
- import type { AgentConfigRepository, AgentConfigListOptions, AgentConfigListResult } from '../repositories';
2
- import type { AgentRecord, AgentRecordPatch } from '../domain/agent';
3
- export declare class AgentConfigError extends Error {
4
- status: number;
5
- code: 'not_found' | 'duplicate_slug' | 'invalid' | 'forbidden';
6
- constructor(code: AgentConfigError['code'], message: string);
7
- }
8
- export interface AgentConfigCreateInput {
9
- tenantId: string;
10
- slug: string;
11
- name: string;
12
- systemPrompt: string;
13
- description?: string;
14
- model?: string;
15
- context?: string;
16
- temperature?: number;
17
- topP?: number;
18
- maxTokens?: number;
19
- tools?: string[];
20
- isActive?: boolean;
21
- createdByUserId?: string;
22
- metadata?: Record<string, unknown>;
23
- }
24
- /**
25
- * Application-level CRUD for per-tenant agent configurations. Controllers
26
- * call this — they do NOT touch the repository directly so we can keep
27
- * validation (slug format, unique-per-tenant) in one place.
28
- */
29
- export declare class AgentConfigService {
30
- private readonly repo;
31
- constructor(repo: AgentConfigRepository);
32
- create(input: AgentConfigCreateInput): Promise<AgentRecord>;
33
- getById(id: string): Promise<AgentRecord>;
34
- /**
35
- * Confirms the agent belongs to `tenantId`. Used by admin endpoints so a
36
- * tenant can't fetch another tenant's agent by guessing the id.
37
- */
38
- getByIdForTenant(id: string, tenantId: string): Promise<AgentRecord>;
39
- /** SDK hot path. Returns null on miss — callers decide whether to 404 or
40
- * fall back to the legacy hardcoded array. */
41
- findBySlug(tenantId: string, slug: string): Promise<AgentRecord | null>;
42
- listForTenant(tenantId: string, opts?: AgentConfigListOptions): Promise<AgentConfigListResult>;
43
- update(id: string, tenantId: string, patch: AgentRecordPatch): Promise<AgentRecord>;
44
- deactivate(id: string, tenantId: string): Promise<void>;
45
- }
@@ -1,114 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AgentConfigService = exports.AgentConfigError = void 0;
4
- const crypto_1 = require("crypto");
5
- class AgentConfigError extends Error {
6
- constructor(code, message) {
7
- super(message);
8
- this.code = code;
9
- this.status =
10
- code === 'forbidden'
11
- ? 403
12
- : code === 'not_found'
13
- ? 404
14
- : code === 'duplicate_slug'
15
- ? 409
16
- : 400;
17
- }
18
- }
19
- exports.AgentConfigError = AgentConfigError;
20
- // Slug rules are deliberately strict so they're URL-safe and predictable for
21
- // SDK callers: lowercase letters, digits, hyphen, underscore. Length 2..64.
22
- const SLUG_RE = /^[a-z0-9][a-z0-9_-]{1,62}[a-z0-9]$/;
23
- /**
24
- * Application-level CRUD for per-tenant agent configurations. Controllers
25
- * call this — they do NOT touch the repository directly so we can keep
26
- * validation (slug format, unique-per-tenant) in one place.
27
- */
28
- class AgentConfigService {
29
- constructor(repo) {
30
- this.repo = repo;
31
- }
32
- async create(input) {
33
- if (!input.tenantId)
34
- throw new AgentConfigError('invalid', 'tenantId is required');
35
- if (!input.name?.trim())
36
- throw new AgentConfigError('invalid', 'name is required');
37
- if (!input.systemPrompt?.trim())
38
- throw new AgentConfigError('invalid', 'systemPrompt is required');
39
- const slug = input.slug.trim().toLowerCase();
40
- if (!SLUG_RE.test(slug)) {
41
- throw new AgentConfigError('invalid', 'slug must be 3–64 chars, lowercase letters/digits/hyphen/underscore, start and end alphanumeric');
42
- }
43
- const existing = await this.repo.findBySlug(input.tenantId, slug);
44
- if (existing) {
45
- throw new AgentConfigError('duplicate_slug', `An agent with slug "${slug}" already exists for this tenant.`);
46
- }
47
- const record = {
48
- id: (0, crypto_1.randomUUID)(),
49
- tenantId: input.tenantId,
50
- slug,
51
- name: input.name.trim(),
52
- systemPrompt: input.systemPrompt,
53
- description: input.description?.trim(),
54
- model: input.model,
55
- context: input.context,
56
- temperature: input.temperature,
57
- topP: input.topP,
58
- maxTokens: input.maxTokens,
59
- tools: input.tools,
60
- isActive: input.isActive ?? true,
61
- createdByUserId: input.createdByUserId,
62
- metadata: input.metadata,
63
- };
64
- return this.repo.create(record);
65
- }
66
- async getById(id) {
67
- const a = await this.repo.findById(id);
68
- if (!a)
69
- throw new AgentConfigError('not_found', 'Agent not found');
70
- return a;
71
- }
72
- /**
73
- * Confirms the agent belongs to `tenantId`. Used by admin endpoints so a
74
- * tenant can't fetch another tenant's agent by guessing the id.
75
- */
76
- async getByIdForTenant(id, tenantId) {
77
- const a = await this.getById(id);
78
- if (a.tenantId !== tenantId) {
79
- // Don't reveal existence — return not_found.
80
- throw new AgentConfigError('not_found', 'Agent not found');
81
- }
82
- return a;
83
- }
84
- /** SDK hot path. Returns null on miss — callers decide whether to 404 or
85
- * fall back to the legacy hardcoded array. */
86
- async findBySlug(tenantId, slug) {
87
- return this.repo.findBySlug(tenantId, slug.toLowerCase());
88
- }
89
- async listForTenant(tenantId, opts = {}) {
90
- return this.repo.listForTenant(tenantId, opts);
91
- }
92
- async update(id, tenantId, patch) {
93
- const current = await this.getByIdForTenant(id, tenantId);
94
- // Slug changes need re-validation + uniqueness check.
95
- if (patch.slug !== undefined && patch.slug !== current.slug) {
96
- const nextSlug = patch.slug.trim().toLowerCase();
97
- if (!SLUG_RE.test(nextSlug)) {
98
- throw new AgentConfigError('invalid', 'slug must be 3–64 chars, lowercase letters/digits/hyphen/underscore');
99
- }
100
- const dup = await this.repo.findBySlug(tenantId, nextSlug);
101
- if (dup && dup.id !== id) {
102
- throw new AgentConfigError('duplicate_slug', `An agent with slug "${nextSlug}" already exists for this tenant.`);
103
- }
104
- patch.slug = nextSlug;
105
- }
106
- await this.repo.update(id, patch);
107
- return this.getById(id);
108
- }
109
- async deactivate(id, tenantId) {
110
- await this.getByIdForTenant(id, tenantId);
111
- await this.repo.deactivate(id);
112
- }
113
- }
114
- exports.AgentConfigService = AgentConfigService;
@@ -1,41 +0,0 @@
1
- import type { ApiKeyRepository } from '../repositories';
2
- import type { ApiKey } from '../domain/api-key';
3
- export interface MintedApiKey {
4
- /** Plaintext key. Only available at creation time — never persisted. */
5
- key: string;
6
- entity: ApiKey;
7
- }
8
- /** Operational errors. Adapters map `.status` to an HTTP code. */
9
- export declare class ApiKeyError extends Error {
10
- status: number;
11
- code: 'invalid' | 'revoked' | 'expired';
12
- constructor(code: 'invalid' | 'revoked' | 'expired', message: string);
13
- }
14
- export declare class ApiKeyService {
15
- private readonly repo;
16
- constructor(repo: ApiKeyRepository);
17
- /** Mint a new API key for a tenant. Returns the plaintext key (one-time).
18
- * Pass `agentId` to scope the key to a specific agent in the tenant —
19
- * calls with that key only resolve when the requested agent matches. */
20
- issue(input: {
21
- tenantId: string;
22
- agentId?: string;
23
- name: string;
24
- scopes?: string[];
25
- expiresAt?: Date;
26
- }): Promise<MintedApiKey>;
27
- /**
28
- * Validate a presented key. Returns the matching ApiKey row (without
29
- * touching the plaintext) or throws ApiKeyError. Updates lastUsedAt as a
30
- * best-effort side-effect (never blocks on the write).
31
- */
32
- validate(rawKey: string): Promise<ApiKey>;
33
- listForTenant(tenantId: string): Promise<ApiKey[]>;
34
- listForAgent(tenantId: string, agentId: string): Promise<ApiKey[]>;
35
- revoke(id: string): Promise<void>;
36
- /** Tells callers (HTTP middleware) whether a header value looks like an API key. */
37
- static isApiKeyToken(token: string | undefined | null): boolean;
38
- private hash;
39
- }
40
- /** Convenience for `randomUUID` access from external callers. */
41
- export declare const newApiKeyId: () => `${string}-${string}-${string}-${string}-${string}`;
@@ -1,80 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.newApiKeyId = exports.ApiKeyService = exports.ApiKeyError = void 0;
4
- const crypto_1 = require("crypto");
5
- const API_KEY_PREFIX_LIVE = 'ak_live_';
6
- /** Operational errors. Adapters map `.status` to an HTTP code. */
7
- class ApiKeyError extends Error {
8
- constructor(code, message) {
9
- super(message);
10
- this.code = code;
11
- this.status = 401;
12
- }
13
- }
14
- exports.ApiKeyError = ApiKeyError;
15
- class ApiKeyService {
16
- constructor(repo) {
17
- this.repo = repo;
18
- }
19
- /** Mint a new API key for a tenant. Returns the plaintext key (one-time).
20
- * Pass `agentId` to scope the key to a specific agent in the tenant —
21
- * calls with that key only resolve when the requested agent matches. */
22
- async issue(input) {
23
- const random = (0, crypto_1.randomBytes)(24).toString('hex'); // 192-bit
24
- const key = `${API_KEY_PREFIX_LIVE}${random}`;
25
- const keyHash = this.hash(key);
26
- const keyPrefix = key.slice(0, 16); // "ak_live_a1b2c3d4"
27
- const entity = await this.repo.create({
28
- tenantId: input.tenantId,
29
- agentId: input.agentId,
30
- name: input.name,
31
- keyHash,
32
- keyPrefix,
33
- scopes: input.scopes,
34
- expiresAt: input.expiresAt,
35
- });
36
- return { key, entity };
37
- }
38
- /**
39
- * Validate a presented key. Returns the matching ApiKey row (without
40
- * touching the plaintext) or throws ApiKeyError. Updates lastUsedAt as a
41
- * best-effort side-effect (never blocks on the write).
42
- */
43
- async validate(rawKey) {
44
- if (!rawKey?.startsWith(API_KEY_PREFIX_LIVE)) {
45
- throw new ApiKeyError('invalid', 'Invalid API key');
46
- }
47
- const keyHash = this.hash(rawKey);
48
- const apiKey = await this.repo.findByHash(keyHash);
49
- if (!apiKey)
50
- throw new ApiKeyError('invalid', 'Invalid API key');
51
- if (apiKey.revokedAt)
52
- throw new ApiKeyError('revoked', 'API key has been revoked');
53
- if (apiKey.expiresAt && apiKey.expiresAt <= new Date()) {
54
- throw new ApiKeyError('expired', 'API key has expired');
55
- }
56
- // Best-effort lastUsedAt update — fire and forget.
57
- this.repo.update(apiKey.id, { lastUsedAt: new Date() }).catch(() => { });
58
- return apiKey;
59
- }
60
- async listForTenant(tenantId) {
61
- return this.repo.listByTenant(tenantId);
62
- }
63
- async listForAgent(tenantId, agentId) {
64
- return this.repo.listByAgent(tenantId, agentId);
65
- }
66
- async revoke(id) {
67
- await this.repo.update(id, { revokedAt: new Date() });
68
- }
69
- /** Tells callers (HTTP middleware) whether a header value looks like an API key. */
70
- static isApiKeyToken(token) {
71
- return !!token && token.startsWith(API_KEY_PREFIX_LIVE);
72
- }
73
- hash(raw) {
74
- return (0, crypto_1.createHash)('sha256').update(raw).digest('hex');
75
- }
76
- }
77
- exports.ApiKeyService = ApiKeyService;
78
- /** Convenience for `randomUUID` access from external callers. */
79
- const newApiKeyId = () => (0, crypto_1.randomUUID)();
80
- exports.newApiKeyId = newApiKeyId;
@@ -1,133 +0,0 @@
1
- import type { UserRepository, UserListOptions, UserListResult, AuthIdentityRepository, EmailTokenRepository } from '../repositories';
2
- import type { User } from '../domain/user';
3
- import type { RefreshTokenService } from './refresh-token.service';
4
- import type { EmailService } from './email.service';
5
- import type { Logger } from './tool-registry.service';
6
- export interface AuthUser {
7
- userId: string;
8
- email?: string;
9
- name?: string;
10
- /** RBAC role from the `af_users.role` column. */
11
- role?: 'user' | 'platform_admin';
12
- /** Convenience boolean derived from `role === 'platform_admin'`. */
13
- isPlatformAdmin?: boolean;
14
- }
15
- export interface AuthResult {
16
- accessToken: string;
17
- refreshToken: string;
18
- user: {
19
- id: string;
20
- email?: string;
21
- name?: string;
22
- };
23
- }
24
- export interface SessionMeta {
25
- userAgent?: string;
26
- ip?: string;
27
- }
28
- export interface AuthServiceOptions {
29
- jwtSecret: string;
30
- /** Access-token TTL — string accepted by jsonwebtoken (e.g. '15m'). Default: '15m'. */
31
- accessTokenTtl?: string;
32
- /** Lock account after N consecutive failed logins. Default: 5. */
33
- lockoutThreshold?: number;
34
- /** Lockout duration in minutes. Default: 15. */
35
- lockoutMinutes?: number;
36
- /** When true, login is blocked until the user has clicked the verify link. Default: false. */
37
- requireEmailVerification?: boolean;
38
- /** Default plan id assigned at registration. */
39
- defaultPlanId?: string;
40
- /** Initial credit balance for newly-registered users. */
41
- initialCredits?: number;
42
- /** Verify-email token TTL in hours. Default: 24. */
43
- verifyTokenTtlHours?: number;
44
- /** Password-reset token TTL in minutes. Default: 60. */
45
- resetTokenTtlMinutes?: number;
46
- /** Hook fired after a user is created via any strategy. */
47
- onUserCreated?: (user: {
48
- id: string;
49
- email?: string;
50
- name?: string;
51
- }) => void | Promise<void>;
52
- logger?: Logger;
53
- }
54
- export declare class AuthService {
55
- private readonly users;
56
- private readonly identities;
57
- private readonly emailTokens;
58
- private readonly refreshTokens;
59
- private readonly email;
60
- private readonly opts;
61
- private readonly logger;
62
- constructor(users: UserRepository, identities: AuthIdentityRepository, emailTokens: EmailTokenRepository, refreshTokens: RefreshTokenService, email: EmailService, opts: AuthServiceOptions);
63
- register(params: {
64
- email: string;
65
- password: string;
66
- name?: string;
67
- }, meta?: SessionMeta): Promise<AuthResult>;
68
- validateCredentials(email: string, password: string): Promise<User>;
69
- login(email: string, password: string, meta?: SessionMeta): Promise<AuthResult>;
70
- refresh(rawToken: string, meta?: SessionMeta): Promise<AuthResult>;
71
- logout(rawRefreshToken?: string): Promise<void>;
72
- logoutEverywhere(userId: string): Promise<void>;
73
- signInWithProvider(profile: {
74
- provider: string;
75
- providerId: string;
76
- email?: string;
77
- emailVerified?: boolean;
78
- name?: string;
79
- metadata?: Record<string, unknown>;
80
- }, meta?: SessionMeta): Promise<AuthResult>;
81
- sendVerificationEmail(user: User): Promise<void>;
82
- resendVerificationEmail(userId: string): Promise<void>;
83
- verifyEmail(rawToken: string): Promise<{
84
- userId: string;
85
- }>;
86
- /** Always returns success — never reveals whether the email exists. */
87
- requestPasswordReset(email: string): Promise<void>;
88
- resetPassword(rawToken: string, newPassword: string): Promise<void>;
89
- /**
90
- * Verify a JWT and return the resolved user. Returns null if the token is
91
- * invalid/expired or the user no longer exists / is disabled.
92
- */
93
- /**
94
- * Resolve a user from their id. Used by adapter-level JWT strategies that
95
- * receive the decoded token payload from passport-jwt and need to populate
96
- * `req.user`.
97
- */
98
- getById(userId: string): Promise<AuthUser | null>;
99
- verifyAccessToken(rawToken: string): Promise<AuthUser | null>;
100
- private toAuthUser;
101
- /**
102
- * Update a user's role. Caller is responsible for authorization; this
103
- * method only enforces invariants:
104
- * - Demoting a platform_admin is blocked if it would leave zero platform
105
- * admins, to prevent locking yourself out of admin operations.
106
- */
107
- setUserRole(userId: string, role: User['role']): Promise<User>;
108
- listUsers(opts?: UserListOptions): Promise<UserListResult>;
109
- /**
110
- * Look up a user by id and return the full `User` row (role, plan, last
111
- * login, etc.) — distinct from `getById` which returns the lightweight
112
- * `AuthUser` projection used in request contexts. Used by admin detail
113
- * pages. Returns null when not found.
114
- */
115
- getFullUserById(userId: string): Promise<User | null>;
116
- /**
117
- * Boot-time backfill for instances upgrading to RBAC. If no user holds the
118
- * `platform_admin` role, the oldest user is promoted so the operator isn't
119
- * locked out. Idempotent — no-op when at least one admin already exists.
120
- *
121
- * Call once from your server bootstrap after the DB is connected.
122
- */
123
- ensurePlatformAdminBootstrap(): Promise<void>;
124
- issueSession(user: User, meta?: SessionMeta): Promise<AuthResult>;
125
- private signAccess;
126
- private recordFailedLogin;
127
- private recordSuccessfulLogin;
128
- private mintEmailToken;
129
- private consumeEmailToken;
130
- private hashToken;
131
- cleanupExpiredEmailTokens(olderThan?: Date): Promise<number>;
132
- private runCreatedHook;
133
- }