Open API token lifecycle
L'Open API di cTrader consente un solo valid access token per cTrader ID (cID) alla volta. Il momento in cui un nuovo token è emesso — un refresh scheduled, o una ri-authorization quando l'utente collega un altro account sullo stesso cID — il previous access token è invalidato. Un copy engine in esecuzione su un nodo remoto sta trattenendo quel now-dead token, quindi il nuovo token deve raggiungerlo senza far cadere la connessione live.
Modello
OpenApiAuthorizationè l'aggregate che trattiene i token encrypted access + refresh di un cID. Un unique index su(UserId, CtidUserId)applica esattamente una authorization per cID per user.TokenVersion— un contatore monotono incrementato ogni volta che il token ruota (Refresh(), che copre anche il percorso re-auth quando un altro account è collegato sullo stesso cID). È il version marker per la regola single-valid-token ed è ciò che un host in esecuzione usa per rilevare un cambiamento anche se due stringhe di token dovessero collidere.- I token sono crittografati at rest via
ISecretProtector(EncryptionPurposes.OpenApiAccessToken/OpenApiRefreshToken). Non sono mai loggati o memorizzati in plaintext.
Propagazione (graceful in-place swap)
- Un token ruota → il nuovo token +
TokenVersionbumped sono persistiti. - Il
CopyEngineSupervisorsul nodo hosting re-legge il piano ogni ciclo di reconcile e computa una token signature (access tokens + versions). Un cambiamento significa una rotazione. - Invece di abbattere l'host e restartare (che farebbe cadere l'execution stream del master), il supervisor spinge il nuovo token all'host in esecuzione.
- L'host ri-autentica l'account affected sul socket esistente
(
ProtoOAAccountAuthReqdi nuovo) viaSwapAccessTokenAsync, poi fa un light reconcile. Il vecchio token muore; lo stream copy non si ferma mai.
Questo è ciò che rende sicuro il caso cross-cID: un utente che aggiunge un secondo account dello stesso cID mid-run invalida il vecchio token, e il copy profile in esecuzione continua sulla nuovo.
Refresh
OpenApiTokenRefreshService (background) refresha proattivamente le authorization prima della scadenza;
OpenApiAuthorization.IsExpiring(threshold, now) la gating. cTrader ruota il refresh token
ogni refresh, quindi il nuovo refresh token è persistito immediatamente; una read-only cache che non può
persistere si auto-invaliderrebbe (rilevante per l'in-cluster test Job, che mounta una copia scrivibile
del secret).
Failure escalation
Un refresh fallito non è silenzioso. OpenApiAuthorization.MarkRefreshFailed(reason, now, criticalWindow)
registra RefreshFailedAt, incrementa ConsecutiveRefreshFailures, e solleva sempre
AccessTokenRefreshFailed (warning). Quando il token è ora entro App:OpenApi:TokenRefreshCriticalWindow
(default 6h) dalla scadenza e il refresh sta ancora fallendo, escala una volta con un
evento domain AccessTokenRefreshCritical + log Critical così l'owner può ri-authorizzare prima che
le operazioni copy/prop-firm perdano il token. Il contatore dei fallimenti e il latch di escalation
resettano al prossimo Refresh riuscito. Il servizio continua a ritentare ogni TokenRefreshInterval,
quindi un outage provider/maintenance si self-heals quando l'endpoint refresh ritorna.
Invalidation alert & auto-recovery (M1)
Una authorization parziale/again su un cID invalida il token che un copy host in esecuzione ancora
trattiene. Quando una chiamata trading rifiuta con OpenApiErrorKind.TokenInvalid, l'host solleva un
distinto CopyTokenInvalidated alert (log 1078) — non un failure generico — così il canale di
notifica sa che un token necessita attenzione. Il recovery è automatico: il supervisor re-legge
l'authorization ogni ciclo e, quando il token refreshato cambia la token signature, lo spinge nell'host
in esecuzione per uno in-place swap — il copying riprende senza re-add manuale. Un profilo
NotLinkable (token/auth temporaneamente unresolvable) è similmente re-evaluato ogni ciclo supervisor
e hosted nel momento in cui il suo piano si builda di nuovo.
Host liveness watchdog (M2)
Il supervisor osserva il run task di ogni profilo hosted. Se un host esce o va in fault mentre il suo
profilo è ancora assegnato a questo nodo, il watchdog cancella e restart esso next cycle (log
CopyHostRestarted), così un host incastrato si self-heala invece di necessitare un restart manuale —
e il fallimento di un profilo non ferma mai gli altri (isolamento per-profile).
Test
- Unit —
TokenVersionbumps onRefresh; l'host esegue uno in-place swap senza restart; cross-cID invalidation scambia source e destination token; un invalidated destination token sollevaCopyTokenInvalidatede auto-recupera sul next token push (M1); la decisione watchdogIsHostDeadrestart un host completed/faulted e lascia un profilo reassigned da solo (M2). - Integration —
TokenVersionpersiste + incrementa attraverso EF su Postgres reale; la token signature cambia su un version bump anche se la stringa è invariata.