fix: reject invalid cloud refresh certificates

This commit is contained in:
saltbo
2026-04-27 08:39:30 -04:00
parent faed8cbece
commit 45b623e362
2 changed files with 42 additions and 3 deletions
+33
View File
@@ -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')
})
})
+9 -3
View File
@@ -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<voi
try {
const data = await refreshEntitlement(baseUrl, state.refreshToken)
const { cert, entitlement } = normaliseCert(data.certificate, state.instanceId)
if (!entitlement) {
await setLicenseOptions(db, {
[LICENSE_KEYS.lastRefreshError]: INVALID_CERTIFICATE_ERROR,
})
return
}
await setLicenseOptions(db, {
[LICENSE_KEYS.refreshToken]: data.refresh_token,
[LICENSE_KEYS.cachedCert]: cert,
[LICENSE_KEYS.cachedExpiresAt]: entitlement
? String(Math.floor(new Date(entitlement.expires_at).getTime() / 1000))
: null,
[LICENSE_KEYS.cachedExpiresAt]: String(Math.floor(new Date(entitlement.expires_at).getTime() / 1000)),
[LICENSE_KEYS.lastRefreshAt]: String(Math.floor(Date.now() / 1000)),
[LICENSE_KEYS.lastRefreshError]: null,
})