IceWhaleTech/CasaOS · error
%s
Error message
%s
What it means
Identical to error [2]: the OneDrive token exchange (grant_type=authorization_code) succeeded at the HTTP level, the error struct was empty, but e.ErrorDescription is being formatted into the returned error. This fires when the token endpoint returns an OAuth error object (e.g. invalid_grant) that sets Error/ErrorDescription but not via HTTP failure.
Source
Thrown at drivers/onedrive/util.go:86
func (d *Onedrive) getRefreshToken() error {
url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token"
var resp base.TokenResp
var e TokenErr
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "authorization_code",
"client_id": d.ClientID,
"client_secret": d.ClientSecret,
"code": d.Code,
"redirect_uri": d.RedirectUri,
}).Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf("%s", e.ErrorDescription)
}
if resp.RefreshToken == "" {
return errors.New("refresh token is empty")
}
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
func (d *Onedrive) _refreshToken() error {
url := d.GetMetaUrl(true, "") + "/common/oauth2/v2.0/token"
var resp base.TokenResp
var e TokenErr
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
"client_id": d.ClientID,
"client_secret": d.ClientSecret,
"redirect_uri": d.RedirectUri,View on GitHub (pinned to 0d3b2f444e)
Solutions
- Read the ErrorDescription text — it states the exact OAuth failure (e.g. 'AADSTS70000: provided authorization code has been redeemed').
- Restart the consent flow for a new code and exchange it immediately.
- Verify ClientID/ClientSecret/RedirectUri match the Azure app registration exactly.
- Avoid double-submitting the exchange request (retries consume the code).
Defensive patterns
Strategy: validation
Validate before calling
if d.Code == "" {
return errors.New("missing authorization code; complete the consent flow first")
}
if d.RedirectUri == "" {
return errors.New("RedirectUri must be set to the registered redirect URI")
} Try / catch
if err := d.GetRefreshToken(); err != nil {
if strings.Contains(err.Error(), "AADSTS") || strings.Contains(err.Error(), "invalid_grant") {
// read the AADSTS code, then restart consent; same code must not be retried
}
return err
} Prevention
- Exchange codes immediately and exactly once — no retries with a consumed code
- Keep Azure app registration secret and redirect URIs in sync with driver config
- Log and persist the first refresh token; later failures need re-consent
When it happens
Trigger: Calling the initial code exchange with an expired/redeemed code, wrong client_secret, or mismatched redirect_uri; the endpoint returns 400 with body {"error":"invalid_grant","error_description":"..."} which is surfaced verbatim via fmt.Errorf("%s", e.ErrorDescription).
Common situations: Fresh setup where the user delayed pasting the code; the code was already consumed by a retry; client secret rotated in Azure AD app registration; redirect URI not registered as a SPA/web redirect.
Related errors
AI-assisted analysis of IceWhaleTech/CasaOS@0d3b2f444e (2026-08-15).
Data as JSON: /api/errors/19b8b0b74254b3f0.
Report an issue: GitHub.