@agentforge-io/core 2.0.24 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/factory.js +56 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +7 -1
  4. package/dist/services/agent-runner.service.js +57 -4
  5. package/dist/services/agent.service.d.ts +21 -1
  6. package/dist/services/agent.service.js +42 -8
  7. package/dist/services/orchestrator.service.d.ts +40 -1
  8. package/dist/services/orchestrator.service.js +220 -0
  9. package/dist/types/agent.types.d.ts +31 -6
  10. package/package.json +1 -1
  11. package/dist/adapters/billing/billing-adapter.interface.d.ts +0 -41
  12. package/dist/adapters/billing/billing-adapter.interface.js +0 -5
  13. package/dist/adapters/billing/stripe/stripe.adapter.d.ts +0 -30
  14. package/dist/adapters/billing/stripe/stripe.adapter.js +0 -122
  15. package/dist/adapters/email/email-adapter.interface.d.ts +0 -25
  16. package/dist/adapters/email/email-adapter.interface.js +0 -6
  17. package/dist/adapters/email/noop.adapter.d.ts +0 -10
  18. package/dist/adapters/email/noop.adapter.js +0 -15
  19. package/dist/adapters/email/resend.adapter.d.ts +0 -8
  20. package/dist/adapters/email/resend.adapter.js +0 -39
  21. package/dist/adapters/upload/noop.adapter.d.ts +0 -9
  22. package/dist/adapters/upload/noop.adapter.js +0 -14
  23. package/dist/adapters/upload/s3.adapter.d.ts +0 -38
  24. package/dist/adapters/upload/s3.adapter.js +0 -69
  25. package/dist/adapters/upload/upload-adapter.interface.d.ts +0 -37
  26. package/dist/adapters/upload/upload-adapter.interface.js +0 -15
  27. package/dist/billing/index.d.ts +0 -12
  28. package/dist/billing/index.js +0 -28
  29. package/dist/domain/agent.d.ts +0 -59
  30. package/dist/domain/agent.js +0 -2
  31. package/dist/domain/api-key.d.ts +0 -28
  32. package/dist/domain/api-key.js +0 -2
  33. package/dist/domain/auth-identity.d.ts +0 -10
  34. package/dist/domain/auth-identity.js +0 -2
  35. package/dist/domain/email-token.d.ts +0 -11
  36. package/dist/domain/email-token.js +0 -2
  37. package/dist/domain/external-user.d.ts +0 -23
  38. package/dist/domain/external-user.js +0 -2
  39. package/dist/domain/plan.d.ts +0 -20
  40. package/dist/domain/plan.js +0 -2
  41. package/dist/domain/platform-secret.d.ts +0 -24
  42. package/dist/domain/platform-secret.js +0 -8
  43. package/dist/domain/refresh-token.d.ts +0 -15
  44. package/dist/domain/refresh-token.js +0 -2
  45. package/dist/domain/subscription.d.ts +0 -21
  46. package/dist/domain/subscription.js +0 -2
  47. package/dist/domain/tenant.d.ts +0 -21
  48. package/dist/domain/tenant.js +0 -2
  49. package/dist/domain/usage-record.d.ts +0 -15
  50. package/dist/domain/usage-record.js +0 -2
  51. package/dist/domain/user.d.ts +0 -43
  52. package/dist/domain/user.js +0 -2
  53. package/dist/services/agent-config.service.d.ts +0 -45
  54. package/dist/services/agent-config.service.js +0 -114
  55. package/dist/services/api-key.service.d.ts +0 -41
  56. package/dist/services/api-key.service.js +0 -80
  57. package/dist/services/auth.service.d.ts +0 -133
  58. package/dist/services/auth.service.js +0 -411
  59. package/dist/services/billing.service.d.ts +0 -67
  60. package/dist/services/billing.service.js +0 -254
  61. package/dist/services/email-templates.d.ts +0 -18
  62. package/dist/services/email-templates.js +0 -39
  63. package/dist/services/email.service.d.ts +0 -26
  64. package/dist/services/email.service.js +0 -42
  65. package/dist/services/errors.d.ts +0 -7
  66. package/dist/services/errors.js +0 -27
  67. package/dist/services/oauth.service.d.ts +0 -73
  68. package/dist/services/oauth.service.js +0 -174
  69. package/dist/services/plan.service.d.ts +0 -54
  70. package/dist/services/plan.service.js +0 -120
  71. package/dist/services/refresh-token.service.d.ts +0 -38
  72. package/dist/services/refresh-token.service.js +0 -73
  73. package/dist/services/secrets/crypto.d.ts +0 -37
  74. package/dist/services/secrets/crypto.js +0 -110
  75. package/dist/services/secrets/known-keys.d.ts +0 -38
  76. package/dist/services/secrets/known-keys.js +0 -50
  77. package/dist/services/secrets.service.d.ts +0 -91
  78. package/dist/services/secrets.service.js +0 -193
  79. package/dist/services/tenant-billing.service.d.ts +0 -121
  80. package/dist/services/tenant-billing.service.js +0 -290
  81. package/dist/services/tenant.service.d.ts +0 -54
  82. package/dist/services/tenant.service.js +0 -96
  83. package/dist/services/upload.service.d.ts +0 -37
  84. package/dist/services/upload.service.js +0 -84
  85. package/dist/services/usage.service.d.ts +0 -34
  86. package/dist/services/usage.service.js +0 -108
  87. package/dist/types/billing.types.d.ts +0 -82
  88. package/dist/types/billing.types.js +0 -3
@@ -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
- }
@@ -1,411 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.AuthService = void 0;
37
- const bcrypt = __importStar(require("bcryptjs"));
38
- const jwt = __importStar(require("jsonwebtoken"));
39
- const crypto_1 = require("crypto");
40
- const errors_1 = require("./errors");
41
- const BCRYPT_COST = 12;
42
- const noopLogger = { log: () => { }, warn: () => { }, debug: () => { }, error: () => { } };
43
- class AuthService {
44
- constructor(users, identities, emailTokens, refreshTokens, email, opts) {
45
- this.users = users;
46
- this.identities = identities;
47
- this.emailTokens = emailTokens;
48
- this.refreshTokens = refreshTokens;
49
- this.email = email;
50
- this.opts = opts;
51
- if (!opts.jwtSecret)
52
- throw new Error('AuthService: jwtSecret is required');
53
- this.logger = opts.logger ?? noopLogger;
54
- }
55
- // ─── Local strategy ───────────────────────────────────────────────────────
56
- async register(params, meta = {}) {
57
- const email = params.email.trim().toLowerCase();
58
- const existing = await this.users.findByEmail(email);
59
- if (existing)
60
- throw new errors_1.AuthError('email_exists', 'An account with that email already exists');
61
- const passwordHash = await bcrypt.hash(params.password, BCRYPT_COST);
62
- // Bootstrap: the very first user on a fresh install is auto-promoted to
63
- // platform_admin. Subsequent users default to `user` and can be promoted
64
- // through the admin UI by an existing platform admin.
65
- const isFirstUser = (await this.users.count()) === 0;
66
- if (isFirstUser) {
67
- this.logger.log('Bootstrap: first user — granting platform_admin role.');
68
- }
69
- const user = await this.users.create({
70
- id: (0, crypto_1.randomUUID)(),
71
- email,
72
- name: params.name,
73
- passwordHash,
74
- authProvider: 'local',
75
- currentPlanId: this.opts.defaultPlanId ?? 'free',
76
- creditsBalance: this.opts.initialCredits ?? 0,
77
- role: isFirstUser ? 'platform_admin' : 'user',
78
- isActive: true,
79
- failedLoginCount: 0,
80
- });
81
- await this.runCreatedHook(user);
82
- if (this.email.isEnabled()) {
83
- this.sendVerificationEmail(user).catch((err) => this.logger.warn(`Verification email on register failed: ${err.message}`));
84
- }
85
- return this.issueSession(user, meta);
86
- }
87
- async validateCredentials(email, password) {
88
- const normalized = email.trim().toLowerCase();
89
- const user = await this.users.findByEmailWithSecrets(normalized);
90
- const invalid = () => new errors_1.AuthError('invalid_credentials', 'Invalid email or password');
91
- if (!user || !user.passwordHash)
92
- throw invalid();
93
- if (!user.isActive)
94
- throw new errors_1.AuthError('account_disabled', 'Account is disabled');
95
- if (user.lockedUntil && user.lockedUntil > new Date()) {
96
- const minutes = Math.ceil((user.lockedUntil.getTime() - Date.now()) / 60000);
97
- throw new errors_1.AuthError('account_locked', `Account temporarily locked. Try again in ${minutes} minute${minutes === 1 ? '' : 's'}.`);
98
- }
99
- if (this.opts.requireEmailVerification && !user.emailVerifiedAt) {
100
- throw new errors_1.AuthError('email_unverified', 'Please verify your email before signing in.');
101
- }
102
- const ok = await bcrypt.compare(password, user.passwordHash);
103
- if (!ok) {
104
- await this.recordFailedLogin(user);
105
- throw invalid();
106
- }
107
- await this.recordSuccessfulLogin(user);
108
- return user;
109
- }
110
- async login(email, password, meta = {}) {
111
- const user = await this.validateCredentials(email, password);
112
- return this.issueSession(user, meta);
113
- }
114
- async refresh(rawToken, meta = {}) {
115
- const rotated = await this.refreshTokens.rotate(rawToken, meta);
116
- const user = await this.users.findById(rotated.userId);
117
- if (!user || !user.isActive) {
118
- throw new errors_1.AuthError('account_disabled', 'Account is disabled');
119
- }
120
- return {
121
- accessToken: this.signAccess(user),
122
- refreshToken: rotated.token,
123
- user: { id: user.id, email: user.email, name: user.name },
124
- };
125
- }
126
- async logout(rawRefreshToken) {
127
- if (rawRefreshToken)
128
- await this.refreshTokens.revoke(rawRefreshToken);
129
- }
130
- async logoutEverywhere(userId) {
131
- await this.refreshTokens.revokeAllForUser(userId);
132
- }
133
- // ─── OAuth / external providers ───────────────────────────────────────────
134
- async signInWithProvider(profile, meta = {}) {
135
- const identity = await this.identities.findByProvider(profile.provider, profile.providerId);
136
- if (identity) {
137
- const user = await this.users.findById(identity.userId);
138
- if (!user || !user.isActive)
139
- throw new errors_1.AuthError('account_disabled', 'Account is disabled');
140
- return this.issueSession(user, meta);
141
- }
142
- const email = profile.email?.trim().toLowerCase();
143
- let user = null;
144
- if (email && profile.emailVerified) {
145
- user = await this.users.findByEmail(email);
146
- }
147
- if (!user) {
148
- user = await this.users.create({
149
- id: (0, crypto_1.randomUUID)(),
150
- email,
151
- name: profile.name,
152
- authProvider: profile.provider,
153
- currentPlanId: this.opts.defaultPlanId ?? 'free',
154
- creditsBalance: this.opts.initialCredits ?? 0,
155
- isActive: true,
156
- failedLoginCount: 0,
157
- });
158
- await this.runCreatedHook(user);
159
- }
160
- else if (!user.isActive) {
161
- throw new errors_1.AuthError('account_disabled', 'Account is disabled');
162
- }
163
- await this.identities.create({
164
- userId: user.id,
165
- provider: profile.provider,
166
- providerId: profile.providerId,
167
- email,
168
- metadata: profile.metadata,
169
- });
170
- return this.issueSession(user, meta);
171
- }
172
- // ─── Email verification + password reset ──────────────────────────────────
173
- async sendVerificationEmail(user) {
174
- if (!user.email)
175
- return;
176
- if (user.emailVerifiedAt)
177
- return;
178
- const ttlHours = this.opts.verifyTokenTtlHours ?? 24;
179
- const token = await this.mintEmailToken(user.id, 'verify_email', ttlHours * 60 * 60 * 1000);
180
- await this.email.sendVerifyEmail({ to: user.email, token });
181
- }
182
- async resendVerificationEmail(userId) {
183
- const user = await this.users.findById(userId);
184
- if (!user || !user.email) {
185
- throw new errors_1.AuthError('no_email_on_file', 'No email on file for this user');
186
- }
187
- if (user.emailVerifiedAt)
188
- return;
189
- await this.sendVerificationEmail(user);
190
- }
191
- async verifyEmail(rawToken) {
192
- const entity = await this.consumeEmailToken(rawToken, 'verify_email');
193
- await this.users.update(entity.userId, { emailVerifiedAt: new Date() });
194
- return { userId: entity.userId };
195
- }
196
- /** Always returns success — never reveals whether the email exists. */
197
- async requestPasswordReset(email) {
198
- const normalized = email.trim().toLowerCase();
199
- const user = await this.users.findByEmail(normalized);
200
- if (!user || !user.email || !user.isActive) {
201
- // Constant-time-ish jitter so request timing doesn't leak existence either.
202
- await new Promise((r) => setTimeout(r, 100 + Math.random() * 200));
203
- return;
204
- }
205
- const ttlMinutes = this.opts.resetTokenTtlMinutes ?? 60;
206
- const token = await this.mintEmailToken(user.id, 'reset_password', ttlMinutes * 60 * 1000);
207
- try {
208
- await this.email.sendPasswordResetEmail({ to: user.email, token });
209
- }
210
- catch (err) {
211
- this.logger.warn(`Password-reset email failed for user ${user.id}: ${err.message}`);
212
- }
213
- }
214
- async resetPassword(rawToken, newPassword) {
215
- if (!newPassword || newPassword.length < 8) {
216
- throw new errors_1.AuthError('invalid_password', 'Password must be at least 8 characters');
217
- }
218
- const entity = await this.consumeEmailToken(rawToken, 'reset_password');
219
- const passwordHash = await bcrypt.hash(newPassword, BCRYPT_COST);
220
- await this.users.update(entity.userId, {
221
- passwordHash,
222
- failedLoginCount: 0,
223
- lockedUntil: undefined,
224
- });
225
- await this.refreshTokens.revokeAllForUser(entity.userId);
226
- }
227
- // ─── Token verification (used by HTTP middleware) ─────────────────────────
228
- /**
229
- * Verify a JWT and return the resolved user. Returns null if the token is
230
- * invalid/expired or the user no longer exists / is disabled.
231
- */
232
- /**
233
- * Resolve a user from their id. Used by adapter-level JWT strategies that
234
- * receive the decoded token payload from passport-jwt and need to populate
235
- * `req.user`.
236
- */
237
- async getById(userId) {
238
- const user = await this.users.findById(userId);
239
- if (!user || !user.isActive)
240
- return null;
241
- return this.toAuthUser(user);
242
- }
243
- async verifyAccessToken(rawToken) {
244
- let payload;
245
- try {
246
- payload = jwt.verify(rawToken, this.opts.jwtSecret);
247
- }
248
- catch {
249
- return null;
250
- }
251
- if (!payload.sub)
252
- return null;
253
- const user = await this.users.findById(payload.sub);
254
- if (!user || !user.isActive)
255
- return null;
256
- return this.toAuthUser(user);
257
- }
258
- toAuthUser(user) {
259
- return {
260
- userId: user.id,
261
- email: user.email,
262
- name: user.name,
263
- role: user.role,
264
- isPlatformAdmin: user.role === 'platform_admin',
265
- };
266
- }
267
- // ─── User management (platform-admin operations) ──────────────────────────
268
- /**
269
- * Update a user's role. Caller is responsible for authorization; this
270
- * method only enforces invariants:
271
- * - Demoting a platform_admin is blocked if it would leave zero platform
272
- * admins, to prevent locking yourself out of admin operations.
273
- */
274
- async setUserRole(userId, role) {
275
- const user = await this.users.findById(userId);
276
- if (!user)
277
- throw new errors_1.AuthError('user_not_found', 'User not found');
278
- if (user.role === role)
279
- return user;
280
- if (user.role === 'platform_admin' && role !== 'platform_admin') {
281
- const remaining = await this.users.countByRole('platform_admin');
282
- if (remaining <= 1) {
283
- throw new errors_1.AuthError('last_admin', 'Cannot remove the last platform admin — promote someone else first.');
284
- }
285
- }
286
- await this.users.update(userId, { role });
287
- return { ...user, role };
288
- }
289
- async listUsers(opts) {
290
- return this.users.list(opts);
291
- }
292
- /**
293
- * Look up a user by id and return the full `User` row (role, plan, last
294
- * login, etc.) — distinct from `getById` which returns the lightweight
295
- * `AuthUser` projection used in request contexts. Used by admin detail
296
- * pages. Returns null when not found.
297
- */
298
- async getFullUserById(userId) {
299
- return this.users.findById(userId);
300
- }
301
- /**
302
- * Boot-time backfill for instances upgrading to RBAC. If no user holds the
303
- * `platform_admin` role, the oldest user is promoted so the operator isn't
304
- * locked out. Idempotent — no-op when at least one admin already exists.
305
- *
306
- * Call once from your server bootstrap after the DB is connected.
307
- */
308
- async ensurePlatformAdminBootstrap() {
309
- const adminCount = await this.users.countByRole('platform_admin');
310
- if (adminCount > 0)
311
- return;
312
- const totalUsers = await this.users.count();
313
- if (totalUsers === 0) {
314
- // Fresh install — the first `register()` call will promote naturally.
315
- return;
316
- }
317
- // Existing install with users but no admin. Prefer the oldest user that
318
- // has an email — that's a real signup, not a test fixture seeded by
319
- // earlier scripts. Fall back to the absolute oldest if no emailed user
320
- // exists.
321
- const all = await this.users.list();
322
- // `list()` returns newest-first, so iterate from the tail (oldest first).
323
- const ordered = [...all.items].reverse();
324
- const target = ordered.find((u) => !!u.email) ?? ordered[0];
325
- if (!target)
326
- return;
327
- await this.users.update(target.id, { role: 'platform_admin' });
328
- this.logger.warn(`RBAC bootstrap: no platform admins found, promoted ` +
329
- `${target.email ?? target.id} to platform_admin.`);
330
- }
331
- // ─── Internals ────────────────────────────────────────────────────────────
332
- async issueSession(user, meta = {}) {
333
- const accessToken = this.signAccess(user);
334
- const minted = await this.refreshTokens.issue(user.id, meta);
335
- return {
336
- accessToken,
337
- refreshToken: minted.token,
338
- user: { id: user.id, email: user.email, name: user.name },
339
- };
340
- }
341
- signAccess(user) {
342
- const ttl = this.opts.accessTokenTtl ?? '15m';
343
- return jwt.sign({ sub: user.id, email: user.email, name: user.name }, this.opts.jwtSecret,
344
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
345
- { expiresIn: ttl });
346
- }
347
- async recordFailedLogin(user) {
348
- const threshold = this.opts.lockoutThreshold ?? 5;
349
- const lockMinutes = this.opts.lockoutMinutes ?? 15;
350
- const next = (user.failedLoginCount ?? 0) + 1;
351
- if (next >= threshold) {
352
- this.logger.warn(`User ${user.id} locked for ${lockMinutes}m after ${threshold} failed logins`);
353
- await this.users.update(user.id, {
354
- failedLoginCount: 0,
355
- lockedUntil: new Date(Date.now() + lockMinutes * 60_000),
356
- });
357
- }
358
- else {
359
- await this.users.update(user.id, { failedLoginCount: next });
360
- }
361
- }
362
- async recordSuccessfulLogin(user) {
363
- await this.users.update(user.id, {
364
- failedLoginCount: 0,
365
- lockedUntil: undefined,
366
- lastLoginAt: new Date(),
367
- });
368
- }
369
- async mintEmailToken(userId, purpose, ttlMs) {
370
- await this.emailTokens.invalidateActive(userId, purpose);
371
- const raw = (0, crypto_1.randomBytes)(32).toString('hex');
372
- await this.emailTokens.create({
373
- userId,
374
- tokenHash: this.hashToken(raw),
375
- purpose,
376
- expiresAt: new Date(Date.now() + ttlMs),
377
- });
378
- return raw;
379
- }
380
- async consumeEmailToken(rawToken, purpose) {
381
- const tokenHash = this.hashToken(rawToken);
382
- const entity = await this.emailTokens.findByHash(tokenHash);
383
- if (!entity || entity.purpose !== purpose) {
384
- throw new errors_1.AuthError('invalid_token', 'Invalid or expired token');
385
- }
386
- if (entity.consumedAt)
387
- throw new errors_1.AuthError('token_consumed', 'Token already used');
388
- if (entity.expiresAt <= new Date())
389
- throw new errors_1.AuthError('token_expired', 'Token expired');
390
- await this.emailTokens.markConsumed(entity.id);
391
- return entity;
392
- }
393
- hashToken(token) {
394
- return (0, crypto_1.createHash)('sha256').update(token).digest('hex');
395
- }
396
- async cleanupExpiredEmailTokens(olderThan = new Date()) {
397
- return this.emailTokens.deleteExpired(olderThan);
398
- }
399
- async runCreatedHook(user) {
400
- const hook = this.opts.onUserCreated;
401
- if (!hook)
402
- return;
403
- try {
404
- await hook({ id: user.id, email: user.email, name: user.name });
405
- }
406
- catch (err) {
407
- this.logger.warn(`onUserCreated hook failed: ${err.message}`);
408
- }
409
- }
410
- }
411
- exports.AuthService = AuthService;
@@ -1,67 +0,0 @@
1
- import type { SubscriptionRepository, UserRepository, UsageRecordRepository } from '../repositories';
2
- import type { IBillingAdapter } from '../adapters/billing/billing-adapter.interface';
3
- import type { BillingConfig } from '../types/config.types';
4
- import type { Plan } from '../domain/plan';
5
- import type { UsageService } from './usage.service';
6
- import type { PlanService } from './plan.service';
7
- import type { BillingOverviewResult, CheckoutResult, PortalResult } from '../types/billing.types';
8
- export interface BillingServiceOptions {
9
- billing?: BillingConfig;
10
- defaultLimits?: {
11
- requestsPerMonth?: number;
12
- tokensPerMonth?: number;
13
- };
14
- logger?: {
15
- warn: (m: string) => void;
16
- log: (m: string) => void;
17
- debug?: (m: string) => void;
18
- };
19
- /** Source of truth for plans (DB-backed in production). */
20
- plans: PlanService;
21
- }
22
- /**
23
- * Billing for the B2C path: the end user is the payer. Mirrors
24
- * TenantBillingService, scoped to individual users.
25
- */
26
- export declare class BillingService {
27
- private readonly users;
28
- private readonly subscriptions;
29
- private readonly usageRecords;
30
- private readonly adapter;
31
- private readonly usageService;
32
- private readonly opts;
33
- constructor(users: UserRepository, subscriptions: SubscriptionRepository, usageRecords: UsageRecordRepository, adapter: IBillingAdapter, usageService: UsageService, opts: BillingServiceOptions);
34
- getPlans(): Promise<Plan[]>;
35
- getPlan(planId: string): Promise<Plan>;
36
- getDefaultPlanId(): Promise<string>;
37
- createCheckout(input: {
38
- userId: string;
39
- planId: string;
40
- /** Override URLs per request (e.g. coming from a frontend on a custom domain). */
41
- successUrl?: string;
42
- cancelUrl?: string;
43
- appUrl?: string;
44
- }): Promise<CheckoutResult>;
45
- getPortalUrl(userId: string, returnUrl?: string): Promise<PortalResult>;
46
- handleWebhook(payload: Buffer, signature: string): Promise<void>;
47
- /**
48
- * Same as handleWebhook but accepts an already-verified event. Used by
49
- * routers that want to dispatch a single event to both the tenant and the
50
- * user-centric handlers without re-verifying the signature.
51
- */
52
- handleWebhookEvent(event: {
53
- type: string;
54
- data: Record<string, unknown>;
55
- }): Promise<void>;
56
- private onCheckoutCompleted;
57
- private onSubscriptionUpdated;
58
- private onSubscriptionDeleted;
59
- private onPaymentFailed;
60
- getOverview(userId: string): Promise<BillingOverviewResult>;
61
- cancelSubscription(userId: string, immediately?: boolean): Promise<void>;
62
- }
63
- export declare class BillingError extends Error {
64
- status: number;
65
- code: 'not_found' | 'plan_not_found' | 'plan_not_purchasable' | 'no_customer' | 'no_active_subscription';
66
- constructor(code: 'not_found' | 'plan_not_found' | 'plan_not_purchasable' | 'no_customer' | 'no_active_subscription', message: string);
67
- }