{"record":{"id":"19b8b0b74254b3f0","repo":"IceWhaleTech/CasaOS","slug":"s","errorCode":null,"errorMessage":"%s","messagePattern":"%s","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/onedrive/util.go","lineNumber":86,"sourceCode":"\nfunc (d *Onedrive) getRefreshToken() error {\n\turl := d.GetMetaUrl(true, \"\") + \"/common/oauth2/v2.0/token\"\n\tvar resp base.TokenResp\n\tvar e TokenErr\n\n\tres, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{\n\t\t\"grant_type\":    \"authorization_code\",\n\t\t\"client_id\":     d.ClientID,\n\t\t\"client_secret\": d.ClientSecret,\n\t\t\"code\":          d.Code,\n\t\t\"redirect_uri\":  d.RedirectUri,\n\t}).Post(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogger.Info(\"get refresh token\", zap.String(\"res\", res.String()))\n\tif e.Error != \"\" {\n\t\treturn fmt.Errorf(\"%s\", e.ErrorDescription)\n\t}\n\tif resp.RefreshToken == \"\" {\n\t\treturn errors.New(\"refresh token is empty\")\n\t}\n\td.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken\n\treturn nil\n}\n\nfunc (d *Onedrive) _refreshToken() error {\n\turl := d.GetMetaUrl(true, \"\") + \"/common/oauth2/v2.0/token\"\n\tvar resp base.TokenResp\n\tvar e TokenErr\n\n\tres, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{\n\t\t\"grant_type\":    \"refresh_token\",\n\t\t\"client_id\":     d.ClientID,\n\t\t\"client_secret\": d.ClientSecret,\n\t\t\"redirect_uri\":  d.RedirectUri,","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/IceWhaleTech/CasaOS/blob/0d3b2f444ec0193193cf03eef6d43c6e35b0183e/drivers/onedrive/util.go#L68-L104","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if d.Code == \"\" {\n\treturn errors.New(\"missing authorization code; complete the consent flow first\")\n}\nif d.RedirectUri == \"\" {\n\treturn errors.New(\"RedirectUri must be set to the registered redirect URI\")\n}","typeGuard":null,"tryCatchPattern":"if err := d.GetRefreshToken(); err != nil {\n\tif strings.Contains(err.Error(), \"AADSTS\") || strings.Contains(err.Error(), \"invalid_grant\") {\n\t\t// read the AADSTS code, then restart consent; same code must not be retried\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["onedrive","oauth","authorization-code","azure-ad"],"backgroundTag":null,"analyzedSha":"0d3b2f444ec0193193cf03eef6d43c6e35b0183e","analyzedAt":"2026-08-15T13:27:57.821Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}