Files
zpan/server/licensing/refresh.ts
T
2026-05-08 15:42:05 -04:00

73 lines
2.3 KiB
TypeScript

import type { Database } from '../platform/interface'
import {
CloudInvalidResponseError,
CloudNetworkError,
CloudUnboundError,
refreshEntitlement,
} from '../services/licensing-cloud'
import { invalidateEntitlementCache } from './entitlement'
import {
clearLicenseBinding,
loadLicenseState,
setLicenseRefreshError,
updateLicenseBindingAfterRefresh,
} from './license-state'
import { verifyCertificate } from './verify'
const INVALID_CERTIFICATE_ERROR = 'Invalid certificate from cloud'
const INVALID_ENTITLEMENT_RESPONSE_ERROR = 'Invalid entitlement response from cloud'
function normaliseCert(
raw: string,
options: { instanceId: string; cloudBaseUrl: string },
): { cert: string; certificateExpiresAt: number | null } {
const assertion = verifyCertificate(raw, { instanceId: options.instanceId, cloudBaseUrl: options.cloudBaseUrl })
return { cert: raw, certificateExpiresAt: assertion?.expiresAt ?? null }
}
export async function performRefresh(db: Database, baseUrl: string): Promise<void> {
const state = await loadLicenseState(db)
if (!state.refreshToken || !state.instanceId) return
try {
const data = await refreshEntitlement(baseUrl, state.refreshToken)
const { cert, certificateExpiresAt } = normaliseCert(data.certificate, {
instanceId: state.instanceId,
cloudBaseUrl: baseUrl,
})
if (!certificateExpiresAt) {
await setLicenseRefreshError(db, state.id, INVALID_CERTIFICATE_ERROR)
return
}
if (!data.binding?.storeId || !data.account) {
await setLicenseRefreshError(db, state.id, INVALID_ENTITLEMENT_RESPONSE_ERROR)
return
}
await updateLicenseBindingAfterRefresh(db, {
id: state.id,
refreshToken: data.refreshToken,
cloudStoreId: data.binding.storeId,
cachedCert: cert,
cachedExpiresAt: certificateExpiresAt,
cloudAccountEmail: data.account.email,
lastRefreshAt: Math.floor(Date.now() / 1000),
})
invalidateEntitlementCache()
} catch (err) {
if (err instanceof CloudUnboundError) {
await clearLicenseBinding(db, 'revoked')
invalidateEntitlementCache()
return
}
if (err instanceof CloudInvalidResponseError || err instanceof CloudNetworkError || err instanceof Error) {
await setLicenseRefreshError(db, state.id, err.message)
return
}
throw err
}
}