- servers.service: sendCommand() now throws InternalServerErrorException on
NATS failure instead of silently succeeding; returns { success, message }
instead of the legacy { output } shape; adds NestJS Logger
- plugins.service: installPlugin() dispatches plugin_install to
corrosion.{license_id}.cmd.server after DB save; NATS failure is logged
but non-fatal so the DB record is preserved regardless
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The JWT was missing license_id, causing @CurrentTenant() to throw 401
on every protected route. Now generateTokens() accepts a licenseId
param, and all three callers (register, login, refresh) look up the
user's license and pass it through.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NATS has no auth configured, so NATS_TOKEN was a placeholder that
confused users. Made it optional in the Go agent (default empty) and
removed it from Quick Setup commands. Also removed GAME_SERVER_PATH
since one-click deploy handles server installation. License key already
auto-populates from auth store after previous commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- admin.service.ts: createLicense() now uses CORR-XXXX-XXXX-XXXX format
instead of raw hex hash
- admin.service.ts: getLicenses() flattens owner_email in response to
match frontend expected shape
- auth.service.ts: Login/register responses now include full license
object so frontend can populate auth store
- auth.service.ts: Email lookups are case-insensitive (LOWER()) to
prevent duplicate accounts from case variations
- LoginView/RegisterView: Call setLicense() after setAuth()
- AdminLicenses: Handle null expires_at (was showing Dec 31, 1969),
fix nullable types, fix query param name (per_page → limit)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend uses native WebSocket API, backend was using socket.io which
speaks an incompatible protocol. Switched to @nestjs/platform-ws so
both sides speak native WebSocket. Also fixed JWT TTL override in
docker-compose.yml (was hardcoded to 900s, now 14400s/4h).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Named after Carl Brashear — first Black U.S. Navy Master Diver.
Every Opus instance that boots on this project knows who it is
and what standard it's held to.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The refresh endpoint only returned access_token, causing the frontend to
set refreshToken=undefined after first refresh — breaking the entire
token chain. Now returns both tokens (rotating refresh). Access token
default bumped from 15min to 4h (14400s) for practical server setup
sessions. Also fixed empty license_key for super admin via DB update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Go deployment orchestrator with platform-specific SteamCMD install,
Rust server download, server.cfg generation, and service registration.
Wire deploy command subscription in daemon, make GameServerPath optional,
add InstallDir config with OS-aware defaults. Fix unused imports and
WebSocket subscribe API in ServerView.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds DeploymentConfig/DeploymentStatus types, deployment state management
in the server store, tabbed Linux/Windows quick setup commands, and a
Deploy Rust Server card with progress tracker and configuration form.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add deploy endpoint, DTO, NATS command publisher, and WebSocket bridge
subscription to support the one-click server deployment feature via the
companion agent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- DashboardView: Add non-null assertion on upcoming[0] (guarded by length check)
- EarlyAccessView: Add missing `computed` import from Vue
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI pipeline now uploads binaries to cdn.corrosionmgmt.com:
- /companion/latest/ (always current, overwritten each release)
- /companion/v1.x.x/ (versioned archive)
Frontend download links updated from Gitea releases to CDN.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend:
- Add Companion Agent card to ServerView (status, download links, setup instructions)
- Shows agent connection status, last heartbeat, license key for copy
- Download buttons for Linux/Windows amd64 from Gitea releases
CI/CD:
- Fix build-companion.yml: replace actions/github-script with Gitea API curl
- Inject version from git tag via ldflags (-X main.version)
- Add VERSION variable to Makefile with ldflags injection
- Change main.go version from const to var for ldflags compatibility
Deployment:
- Add systemd service file (deployment/corrosion-companion.service)
- Add .gitignore for bin/ (binaries should come from CI, not repo)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pin NATS image to nats:2.10-alpine for reproducible builds
- Add nginx healthcheck using wget (curl not present in alpine)
- Upgrade nginx depends_on to use condition: service_started
- Add proxy buffer directives to http block (prevents JWT/large-header truncation)
- Add X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, and
Referrer-Policy security headers to all SPA location blocks across
all five server blocks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend expects { subscriptions: [{ owner_email, module_name, license_id }] }
but backend returned raw WebstoreSubscription entities. Shaped the response
with license.owner join for owner_email and plan_id mapped to module_name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- HttpExceptionFilter: Log actual error details for non-HttpExceptions (was silently swallowing 500s)
- ServersService: Return null fields instead of 404 for new licenses without servers
- NotificationsController: Wrap config responses as { config } to match frontend expectations
- WebstoreController: Wrap config responses as { config } to match frontend expectations
- ChatController: Replace ParseIntPipe with manual parseInt (400 on missing optional param)
- WipesController: Same ParseIntPipe fix for history limit param
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause of all remaining 500s: TypeORM entities were scaffolded with
"ideal" column names that don't match the Postgres columns created by
the Rust migrations. Every query generated SQL referencing non-existent
columns.
Entity fixes:
- notifications_config: email_enabled→email_alerts_enabled, removed
6 phantom columns (email_address, notify_on_start, notify_on_stop,
notify_on_player_threshold, player_threshold), renamed 4 notify
columns to match DB (notify_server_crash, notify_wipe_start, etc),
added 3 missing columns (notify_server_offline, notify_store_purchase,
notify_player_report)
- team_members: joined_at→accepted_at (nullable, matches DB)
- roles: removed description column (doesn't exist in DB)
- scheduled_tasks: is_enabled→is_active, removed phantom last_run
- wipe_profiles: pre/post_wipe_config nullable→NOT NULL with default
Service/DTO fixes:
- Updated all property references across notifications, team, schedules
services and DTOs to match corrected entity names
- Added is_active to UpdateTaskDto (frontend sends it, was being
rejected by forbidNonWhitelisted validation)
- Removed description from CreateRoleDto
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: super_admin JWT returned early with no license_id, causing
@CurrentTenant() to pass undefined to every tenant-scoped service query.
- jwt.strategy: Move license lookup before super_admin early return so
admins who own licenses get their license_id in the JWT payload
- CurrentTenant decorator: Throw 401 with clear message when license_id
is undefined instead of letting undefined cascade into TypeORM queries
- Wipe store: Fix 6 wrong routes (/profiles → /wipes/profiles, etc.)
and remove redundant manual license_id guards
- Changelog module: Add stub controller/service returning empty array
to eliminate 404 on /api/changelog
- ChangelogView: Handle both array and {entries} response shapes
- AGENTS.md: Streamlined 3-tier roster (Opus/Sonnet/Haiku)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace `new Date(x).toLocaleString()` pattern with `safeDate()` utility to prevent crashes on null/undefined timestamps in scheduled task next_run field.
Part of ongoing safe formatter migration across all Vue views.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
API returns partial/empty stats object — individual fields can be undefined
even when the object exists. Added null guard to prevent toLocaleString crash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Auth service used flat env var names (JWT_SECRET, JWT_ACCESS_EXPIRY_SECONDS)
but @nestjs/config nests them under jwt.* — configService.get() returned
undefined, so expiresIn was 0 and tokens expired on issue (iat === exp)
- JWT strategy had same bug for secretOrKey
- AnalyticsView passed /api/analytics/... to useApi which already prepends /api,
resulting in /api/api/analytics/... (404)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
maps/ gitignore rule was catching backend-nest/src/modules/maps/.
Scoped to /maps/ (root only) so runtime data is still ignored
but source code isn't.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements missing frontend views and API integrations:
New Views:
- SchedulesView: CRUD for scheduled tasks (restart/announcement/command/plugin_reload)
- MigrationView: Export/import interface with file upload and history tracking
- ChangelogView: Paginated changelog feed with category badges
- ForgotPasswordView: Password reset flow with email submission
- AlertsView: Alert config dashboard with threshold settings and history
Component Updates:
- ErrorBoundary: Global error handler with retry functionality
- DashboardLayout: Mobile responsive sidebar, permission-based nav, new menu items
- ServerInfoView: Complete rewrite for public server info display
Infrastructure:
- useApi: Token refresh interceptor with 401 retry and infinite loop prevention
- plugins store: Implemented all stubbed methods with real API calls
- auth store: Added hasPermission() helper for RBAC UI visibility
- Router: Added new routes with catch-all fallback
Purpose: Closes frontend implementation gaps. Hardens auth flow, improves mobile UX,
enables server automation scheduling, alert configuration, and data migration tools.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete authentication system with JWT, refresh tokens, and TOTP 2FA.
Auto-generates license keys on registration (CORR-XXXX-XXXX-XXXX format).
JwtStrategy enriches payload with license_id and permissions from roles.
Multi-tenant isolation enforced at license access layer.
Auth Module:
- 9 REST endpoints (login, register, refresh, 2FA setup/verify, profile, password reset)
- Argon2 password hashing, TOTP with QR code generation
- Public endpoints: login, register, forgot-password, reset-password, validate-key
- Authenticated endpoints require JWT Bearer token
Users Module:
- Admin CRUD for user management (requires users.view permission)
- Password fields excluded from all responses
Licenses Module:
- License lookup with owner authorization
- Public key validation endpoint for plugin verification
- License key generation via random hex parts
All DTOs use class-validator, all controllers documented via Swagger.
Custom decorators: @Public(), @CurrentUser(), @RequirePermission().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove complex build caching
- Set SQLX_OFFLINE=true to skip compile-time query verification
- Queries still validated at runtime
- Eliminates need for DATABASE_URL during Docker build
- Remove unused ExternalLink import from ModuleStoreView
- Remove unused Tag import from StoreItemsView
- Prefix unused cmd variable with underscore in v-for loop
Fixes production build TypeScript errors.
Backend infrastructure for hosting provider reseller program (Model B).
Database Schema (Migration 012):
- hosts table: Hosting company accounts with API key authentication
- host_licenses: Tracks licenses provisioned by each host
- host_billing_records: Monthly billing data ($6/server wholesale)
Host Provisioning Service:
- API key authentication (SHA-256 hashed, bearer token)
- Bulk license provisioning (single call creates user + license + associations)
- Auto-generation: license keys, companion tokens, subdomain slugs
- Active license counting for billing
- Monthly billing record generation with CSV export support
Host API Endpoints:
- POST /api/host/provision: Bulk license creation
* Input: server_id, hostname, customer_email
* Output: license_key, companion_token, plugin_download_url, subdomain, panel_url
- GET /api/host/licenses: List all host-provisioned licenses with status
- GET /api/host/billing/:month: Monthly billing report (YYYY-MM format)
Security:
- Separate authentication system (API keys vs user JWTs)
- Host-level query isolation (all operations scoped by host_id)
- SHA-256 API key hashing
- CORS protection on host endpoints
Business Model:
- $6/server/month wholesale rate (configurable per host)
- Manual invoicing (no Stripe integration in MVP)
- Hosts control their own markup to end customers
Per B2B_RESELLER_PLAN.md: Minimal viable B2B implementation (Model B).
No white-label branding, SSO, or complex integration required.
Simple API-based provisioning for hosting partners.
Production ready for initial hosting partner testing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Built StoreConfigView.vue for webstore setup
- Form fields: store name, description, currency (USD/EUR/GBP)
- PayPal credentials (client ID/secret) with encryption support
- Sandbox/production mode toggle with warning states
- Store enable/disable with validation
- Empty state for unconfigured stores
- TypeScript StoreConfig interface
- Route: /admin/webstore/config (auth required)
- API integration: GET/PUT /api/webstore/config
- Responsive Tailwind design
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Phase 4 Contributions (Agent Golf):
- Module auto-installation service (module_installer.rs)
- NATS subject pattern for module installation commands
- Companion agent contract documentation
- API endpoint: POST /api/modules/install
Phase 5 XO Direct Touch:
- Webstore subscription API (PayPal recurring billing)
* POST /api/webstore/subscription/create
* GET /api/webstore/subscription
* POST /api/webstore/subscription/cancel
* POST /api/webstore/subscription/webhook
- Store configuration API (CRUD for store settings)
* GET /api/webstore/config
* PUT /api/webstore/config
- Store category/item management APIs (multi-tenant CRUD)
* GET/POST/PUT/DELETE /api/webstore/categories
* GET/POST/PUT/DELETE /api/webstore/items
- Public store API (customer-facing, subdomain-scoped)
* GET /api/public-store/:subdomain
* GET /api/public-store/:subdomain/items
* POST /api/public-store/:subdomain/purchase
* POST /api/public-store/:subdomain/webhook
- Transaction history API
* GET /api/webstore/transactions
- Delivery system (NATS command execution on purchase)
- Migrations: payment_orders, webstore_subscriptions, store_config, store_items, store_transactions
Security:
- JWT auth + license_id scoping on admin endpoints
- Subdomain → license_id mapping on public endpoints
- Purchase limit enforcement
- Command injection prevention via placeholder replacement
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>