diff --git a/server/licensing/refresh.test.ts b/server/licensing/refresh.test.ts index a8c3b2bb..48c43641 100644 --- a/server/licensing/refresh.test.ts +++ b/server/licensing/refresh.test.ts @@ -159,4 +159,37 @@ describe('performRefresh', () => { expect(state.refreshToken).toBe('old-rt') expect(state.lastRefreshError).toBe('Connection timeout') }) + + it('keeps the previous binding when cloud returns an invalid certificate', async () => { + const db = makeDb() + await seedBinding(db, { + [LICENSE_KEYS.cachedCert]: 'old-cert', + [LICENSE_KEYS.cachedExpiresAt]: '1234567890', + }) + + const cert = sign(TEST_SECRET, { + account_id: 'acct-1', + instance_id: 'wrong-instance', + plan: 'pro', + features: ['white_label'], + issued_at: new Date().toISOString(), + expires_at: futureIso(3_600_000), + }) + + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ refresh_token: 'new-rt', certificate: cert }), + text: async () => '', + } as unknown as Response) + + await performRefresh(db, 'https://cloud.zpan.space') + + const { loadLicenseState } = await import('./license-state') + const state = await loadLicenseState(db) + expect(state.refreshToken).toBe('old-rt') + expect(state.cachedCert).toBe('old-cert') + expect(state.cachedExpiresAt).toBe(1234567890) + expect(state.lastRefreshError).toBe('Invalid certificate from cloud') + }) }) diff --git a/server/licensing/refresh.ts b/server/licensing/refresh.ts index 774ad3f7..0b20e4e5 100644 --- a/server/licensing/refresh.ts +++ b/server/licensing/refresh.ts @@ -5,6 +5,8 @@ import { invalidateEntitlementCache } from './entitlement' import { clearLicenseBinding, LICENSE_KEYS, loadLicenseState, setLicenseOptions } from './license-state' import { verifyCertificate } from './verify' +const INVALID_CERTIFICATE_ERROR = 'Invalid certificate from cloud' + function normaliseCert(raw: string, instanceId: string): { cert: string; entitlement: LicenseEntitlement | null } { const entitlement = verifyCertificate(raw, instanceId) return { cert: raw, entitlement } @@ -17,13 +19,17 @@ export async function performRefresh(db: Database, baseUrl: string): Promise