hailin
e31baa1f40
fix(auth): fix invite flow UUID/slug mismatch and removeMember cleanup
...
- TenantController invite endpoints (list/create/revoke) were passing the
tenant UUID from the URL param directly to AuthService methods that
expect a slug, causing 404 on every invite operation. Now resolves
tenant via findTenantOrFail() first and passes slug.
- removeMember now also deletes from public.users so removed members
can no longer log in.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 05:34:47 -08:00
hailin
915bd400c1
fix(auth): insert invited users into public.users on acceptInvite
...
Previously, acceptInvite only wrote to the tenant schema, causing invited
users to be invisible to the login() flow which queries public.users for
cross-tenant email/phone lookup. Now inserts into both public.users and
the tenant schema within the same transaction, matching registerWithNewTenant behavior.
Also tightens duplicate check to cross-tenant uniqueness (public.users)
instead of per-tenant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 05:31:50 -08:00
hailin
6459e5b500
fix(tenants): allow platform_admin to delete tenants; fix invite/user cleanup
...
- DELETE /api/v1/admin/tenants/:id now accepts platform_admin role
- Fix cascade cleanup to use tenant slug (not UUID) for users/invites/api_keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 04:44:42 -08:00
hailin
129c5cbeab
fix(auth): use slug lookup for tenant in validateInvite and acceptInvite
...
invite.tenantId stores the slug (not UUID), so findOneBy must use { slug }
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 04:26:21 -08:00
hailin
100ca43460
fix(auth): use slug for tenant lookup in createInvite; fix getMemberCount search_path
...
- createInvite: findOneBy({ slug }) instead of { id } since JWT tenantId is slug
- getMemberCount: use SET LOCAL + transaction to prevent pool search_path leak
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 04:17:14 -08:00
hailin
76389a337e
fix(auth): properly map raw SQL snake_case rows to User entity camelCase fields
2026-03-07 03:54:34 -08:00
hailin
00357d855d
fix(auth): use explicit public. schema for all login/register queries to prevent search_path contamination
2026-03-07 03:50:05 -08:00
hailin
c2ba432341
fix(auth): dual-write tenant admin to public.users for login + tenant schema for management
2026-03-07 03:42:27 -08:00
hailin
7a6752bf74
fix(auth): use SET LOCAL search_path to prevent connection pool contamination; fix api-test routing
2026-03-07 03:35:49 -08:00
hailin
09d9200235
fix(auth): make tenant.adminEmail nullable for phone-only registrations; fix api-test status parsing
2026-03-07 03:27:23 -08:00
hailin
b8128b7a07
fix(auth): make JwtPayload email optional, add phone to JWT payload
2026-03-07 03:21:32 -08:00
hailin
96bf5e7390
feat(auth): add phone registration support + enterprise register page redesign
...
- User entity: email nullable, add phone field (nullable unique)
- AuthService/Controller: login/register accept email OR phone
- UserRepository: findByPhone(), findByIdentifier() (auto-detects email vs phone)
- Migration 007: ALTER public.users + all existing tenant schemas to add phone
- Tenant schema template (002): users table now includes phone column
- Register page: enterprise-focused design, email/phone toggle, app download section
- Auth i18n (zh/en): new keys for phone, enterprise messaging, download CTA
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 03:14:47 -08:00
hailin
816c5461f9
feat(auth): add platform_super_admin role for two-level platform access control
...
在 platform_admin 之上新增 platform_super_admin 角色,实现平台管理员的两级权限体系。
## 角色层级
platform_super_admin > platform_admin > admin > operator > viewer
- platform_super_admin:最高平台权限,含所有 platform_admin 操作 + 破坏性操作(删除租户/用户/版本)
- platform_admin:日常平台运营,可查看/编辑租户、管理 App 版本、配置账单套餐,不可执行删除
## 变更明细
### auth-service — role-type.vo.ts
- 新增 RoleType.PLATFORM_SUPER_ADMIN = 'platform_super_admin'
### auth-service — tenant.controller.ts
- 租户列表/创建/查看/编辑:@Roles('platform_admin', 'platform_super_admin')(两级均可)
- 删除租户 DELETE /:id:@Roles('platform_super_admin')(仅超管)
### auth-service — user.controller.ts
- 类级别:@Roles('platform_admin', 'platform_super_admin')(两级均可访问用户列表/创建/编辑)
- 删除用户 DELETE /:id:@Roles('platform_super_admin')(仅超管)
### version-service — guards/platform-admin.guard.ts
- 更新:接受 platform_admin 或 platform_super_admin 任一角色
- 重构:抽取 decodeJwtRoles() 工具函数,供 PlatformSuperAdminGuard 复用
### version-service — guards/platform-super-admin.guard.ts(新文件)
- 仅接受 platform_super_admin 角色
- 与 PlatformAdminGuard(类级别)叠加使用,实现方法级别的超管限制
### version-service — version.controller.ts
- DELETE /:id:叠加 @UseGuards(PlatformSuperAdminGuard)(仅超管可删除版本文件)
### web-admin — sidebar.tsx
- isPlatformAdmin 检测同时涵盖 platform_admin 和 platform_super_admin
- 两级平台管理员均显示相同侧边栏菜单
## 升级现有账号为 platform_super_admin
UPDATE public.users SET roles = '{platform_super_admin}' WHERE email = 'xxx@xxx.com';
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 01:17:27 -08:00
hailin
0ab7261129
feat(auth): introduce platform_admin role with proper access separation
...
新增 platform_admin 角色,将平台超管与租户管理员的权限彻底分离。
## 后端变更
### auth-service — role-type.vo.ts
- 新增 RoleType.PLATFORM_ADMIN = 'platform_admin'
- DEFAULT_ROLE_PERMISSIONS 中为 PLATFORM_ADMIN 添加空权限集(平台层操作,不参与租户内权限体系)
### auth-service — tenant.controller.ts
- 移除类级别 @Roles('admin'),改为方法级别精细控制:
- 租户 CRUD(列表/创建/GET/:id/PATCH/:id/PUT/:id/DELETE/:id)→ @Roles('platform_admin')
- 成员管理(listMembers/updateMember/removeMember)→ @Roles('admin')
- 邀请管理(listInvites/createInvite/revokeInvite)→ @Roles('admin')
- 租户管理员可继续管理自己团队的成员和邀请,但无法访问跨租户的租户 CRUD
### auth-service — user.controller.ts
- /api/v1/auth/users(跨租户用户列表/CRUD)→ @Roles('platform_admin')
- 原来任意 admin 均可查看所有用户,现仅平台超管可访问
### version-service — guards/platform-admin.guard.ts(新文件)
- 新增 PlatformAdminGuard:从 Authorization: Bearer <JWT> 中 base64 解码 payload,
检查 roles 包含 'platform_admin'(无需重复验签,Kong 已完成签名校验)
- 不依赖 @nestjs/passport,轻量、无额外依赖
### version-service — version.controller.ts
- 整个 /api/v1/versions 控制器挂载 @UseGuards(PlatformAdminGuard)
- App 版本管理(上传/发布/删除 APK/IPA)仅平台超管可操作
## 前端变更
### it0-web-admin — sidebar.tsx
- 登录时从 localStorage.user.roles 检测是否为 platform_admin
- 平台超管侧边栏:仪表盘 / 租户管理 / 用户(跨租户)/ App版本 / 账单(套餐+概览+账单记录)/ 设置
- 租户用户侧边栏:仪表盘 / Agent配置 / Runbooks / 常驻指令 / 服务器 / 监控 / 终端 / 安全 / 审计 / 通信 / 账单(概览+账单记录,无套餐管理)/ 设置
## 创建第一个平台超管账号
直接更新数据库:
UPDATE it0_t_default.users SET roles = '{platform_admin}' WHERE email = 'xxx@xxx.com';
或通过已有 platform_admin 账号调用 POST /api/v1/auth/users 并指定 role: 'platform_admin'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 00:57:40 -08:00
hailin
9ed80cd0bc
feat: implement complete commercial monetization loop (Phases 1-4)
...
## Phase 1 - Token Metering + Quota Enforcement
### Usage Tracking
- agent-service: add UsageRecord entity (per-tenant schema) tracking
inputTokens/outputTokens/costUsd per AI task
- Modify all 3 AI engines (claude-api, claude-code-cli, claude-agent-sdk)
to emit separate input/output token counts in the `completed` event
- claude-api-engine: costUsd = (input*3 + output*15) / 1,000,000
(claude-sonnet-4-5 pricing: $3/MTok in, $15/MTok out)
- agent.controller: persist UsageRecord and publish `usage.recorded`
event to Redis Streams on every task completion (non-blocking)
- shared/events: new events UsageRecordedEvent, SubscriptionChangedEvent,
QuotaExceededEvent, PaymentReceivedEvent
### Quota Enforcement
- TenantInfo: add maxServers, maxUsers, maxStandingOrders,
maxAgentTokensPerMonth fields
- TenantContextMiddleware: rewritten to query public.tenants table for
real quota values; 5-min in-memory cache; plan-based fallback on error
- TenantContextService: getTenant() returns null instead of throwing;
added getTenantOrThrow() for strict callers
- inventory-service/server.controller: 429 when maxServers exceeded
- ops-service/standing-order.controller: 429 when maxStandingOrders exceeded
- auth-service/auth.service: 429 when maxUsers exceeded
- 002-create-tenant-schema-template.sql: add usage_records table
## Phase 2 - billing-service (New Microservice, port 3010)
### Domain Layer (public schema, all UUIDs)
Entities: Plan, Subscription, Invoice, InvoiceItem, Payment, PaymentMethod,
UsageAggregate
Domain services:
- SubscriptionLifecycleService: full state machine (trialing -> active ->
past_due -> cancelled/expired); upgrades immediate, downgrades at period end
- InvoiceGeneratorService: monthly invoice = base fee + overage charges;
proration item for mid-cycle upgrades
- OverageCalculatorService: (totalTokens - includedTokens) * overageRate
### Infrastructure (all repos use DataSource directly, NOT TenantAwareRepository)
- PlanRepository, SubscriptionRepository, InvoiceRepository (atomic
transaction for invoice+items), PaymentRepository (payments + methods),
UsageAggregateRepository (UPSERT via ON CONFLICT for atomic accumulation)
### Application Use Cases
- CreateSubscriptionUseCase: called on tenant registration
- ChangePlanUseCase: upgrade (immediate + proration) or downgrade (scheduled)
- CancelSubscriptionUseCase: immediate or at-period-end
- GenerateMonthlyInvoiceUseCase: cron target (1st of month 00:05 UTC);
generates invoices, renews periods, applies scheduled downgrades
- AggregateUsageUseCase: Redis Streams consumer group billing-service,
upserts monthly usage aggregates from usage.recorded events
- CheckTokenQuotaUseCase: hard limit enforcement per plan
- CreatePaymentSessionUseCase + HandlePaymentWebhookUseCase
### REST API
- GET /api/v1/billing/plans
- GET/POST /api/v1/billing/subscription (+ /upgrade, /cancel)
- GET /api/v1/billing/invoices (paginated)
- GET /api/v1/billing/invoices/:id
- POST /api/v1/billing/invoices/:id/pay
- GET /api/v1/billing/usage/current + /history
- CRUD /api/v1/billing/payment-methods
- POST /api/v1/billing/webhooks/{stripe,alipay,wechat,crypto}
### Plan Seed (auto on startup via PlanSeedService)
- free: $0/mo, 100K tokens, no overage, hard limit 100%
- pro: $49.99/mo, 1M tokens, $8/MTok, hard limit 150%
- enterprise: $199.99/mo, 10M tokens, $5/MTok, no hard limit
## Phase 3 - Payment Provider Integration
### PaymentProviderRegistry (Strategy Pattern, mirrors EngineRegistry)
All providers use @Optional() injection; unconfigured providers omitted
- StripeProvider: PaymentIntent API; webhook via stripe.webhooks.constructEvent
- AlipayProvider: alipay-sdk; Native QR (precreate); RSA2 signature verify
- WeChatPayProvider: v3 REST; Native Pay code_url; AES-256-GCM decrypt;
HMAC-SHA256 request signing and webhook verification
- CryptoProvider: Coinbase Commerce; hosted checkout; HMAC-SHA256 verify
### WebhookController
All 4 webhook endpoints are public (no JWT) for payment provider callbacks.
rawBody: true enabled in main.ts for signature verification.
## Infrastructure Changes
- docker-compose.yml: billing-service container (port 13010);
added as dependency of api-gateway
- kong.yml: /api/v1/billing routes (JWT); /api/v1/billing/webhooks (public)
- 005-create-billing-tables.sql: 7 billing tables + invoice sequence +
ALTER tenants to add quota columns
- run-migrations.ts: 005 runs as part of shared schema step
## Phase 4 - Frontend
### Web Admin (Next.js)
New pages:
- /billing: subscription card + token usage bar + warning banner + invoices
- /billing/plans: comparison grid with USD/CNY toggle + upgrade/downgrade flow
- /billing/invoices: paginated table with Pay Now button
Sidebar: Billing group (CreditCard icon, 3 sub-items)
i18n: billing keys added to en + zh sidebar translations
### Flutter App
New feature module it0_app/lib/features/billing/:
- BillingOverviewPage: plan card + token LinearProgressIndicator +
latest invoice + upgrade button
- BillingProvider (FutureProvider): parallel fetch subscription/quota/invoice
Settings page: "订阅与用量" entry card
Router: /settings/billing sub-route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 21:09:17 -08:00
hailin
51b348e609
feat: complete tenant member management (CRUD + delete tenant)
...
Backend: add 5 missing endpoints to TenantController:
- DELETE /tenants/:id (deprovision schema + cleanup)
- GET /tenants/:id/members (query tenant schema users)
- PATCH /tenants/:id/members/:memberId (change role)
- DELETE /tenants/:id/members/:memberId (remove member)
- PUT /tenants/:id (alias for frontend compatibility)
Frontend: add member actions to tenant detail page:
- Role column changed to dropdown selector
- Added remove member button with confirmation
- Added updateMember and removeMember mutations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:00:09 -08:00
hailin
9a1ecf10ec
fix: add restart policy, global error handlers, and fix tenant schema bug
...
- Add restart: unless-stopped to all 12 Docker services
- Add process.on(unhandledRejection/uncaughtException) to all 7 service main.ts
- Fix handleEventTrigger using tenantId UUID as schema name instead of slug lookup
- Wrap Redis event subscription callbacks in try/catch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 05:30:34 -08:00
hailin
7ff012cd91
fix: use underscores in tenant slug for valid PostgreSQL schema names
...
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 03:23:14 -08:00
hailin
5d81667ddd
feat: add dual tenant registration (self-service + invitation)
...
Backend:
- Enhanced register endpoint to accept companyName for self-service
tenant creation with schema provisioning and admin user setup
- Added TenantInvite entity with token-based invitation system
- Added invite CRUD endpoints to TenantController (create/list/revoke)
- Added public endpoints for invite validation and acceptance
Frontend:
- Created registration page with optional organization name field
- Created invitation acceptance page at /invite/[token]
- Added invite management UI to tenant detail page
- Updated login page with link to registration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 03:10:18 -08:00
hailin
7dbd2c1414
feat: add settings, roles, permissions, and metrics controllers
...
Implement remaining backend controllers for all web admin menu pages:
- SettingsController: general, notification, theme, account, API keys
- RoleController: CRUD roles with permission assignment
- PermissionController: permission matrix for RBAC management
- MetricsController: server metrics overview and per-server data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:03:34 -08:00
hailin
8f89b8121c
fix: format tenant API response to match frontend DTO
...
Map flat quota fields to nested quota object and add userCount field
to match the frontend's expected Tenant interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 00:41:19 -08:00
hailin
3816d6841d
fix: add users endpoint, admin route, and fix agent-config paths
...
- Add UsersController to auth-service for user CRUD (GET/POST/PUT/DELETE /api/v1/auth/users)
- Add Kong route /api/v1/admin -> auth-service for tenant management
- Remove AuthGuard from TenantController (Kong handles JWT)
- Fix frontend agent-config API paths from /api/v1/agent/config to /api/v1/agent-config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 00:35:57 -08:00
hailin
c710303b60
fix: per-service JWT in Kong, fix auth-service tenant-aware repos
...
- Replace global JWT plugin with per-service JWT (skip auth-service)
to fix auth routes being blocked by global JWT in DB-less mode
- Fix UserRepository and ApiKeyRepository to use standard TypeORM
instead of TenantAwareRepository (users are global, not per-schema)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:31:32 -08:00
hailin
48e47975ca
fix: configure Kong JWT auth flow with consumer credentials
...
- Add kid claim to auth-service JWT for Kong validation
- Add Kong consumer with JWT credential (shared secret via env)
- Add agent-config route to Kong for /api/v1/agent-config
- Kong Dockerfile uses entrypoint script to inject JWT_SECRET at runtime
- Fix frontend login path (/auth/login → /api/v1/auth/login)
- Extract tenantId from JWT on login and store as current_tenant
- Add auth guard in admin layout (redirect to /login if no token)
- Pass JWT_SECRET env var to Kong container in docker-compose
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:20:06 -08:00
hailin
00f8801d51
Initial commit: IT0 AI-powered server cluster operations platform
...
Full-stack monorepo with DDD + Clean Architecture:
- Backend: 7 NestJS microservices + 5 shared libraries (TypeScript)
- Mobile: Flutter app with Riverpod (Dart)
- Web Admin: Next.js dashboard with Zustand + React Query
- Voice: Python voice service (STT/TTS/VAD)
- Infra: Docker Compose, K8s manifests, Turborepo build
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 22:54:37 -08:00