{"record":{"id":"5f0250d1c1ba563e","repo":"DIYgod/RSSHub","slug":"cannot-get-refresh-token-since-mangadex-username-o","errorCode":null,"errorMessage":"Cannot get refresh token since MangaDex username or password is not set","messagePattern":"Cannot get refresh token since MangaDex username or password is not set","errorType":"exception","errorClass":"ConfigNotFoundError","httpStatus":null,"severity":"error","filePath":"lib/routes/mangadex/_access.ts","lineNumber":57,"sourceCode":"                if (error instanceof FetchError && error.statusCode === 400) {\n                    // If the refresh token is invalid, try to get a new one with the user credentials\n                    return getAccessTokenByUserCredentials();\n                }\n                throw error;\n            }\n        },\n        constants.TOKEN_EXPIRE,\n        false\n    );\n};\n\nconst getAccessTokenByUserCredentials = async () => {\n    if (!config.mangadex.clientId || !config.mangadex.clientSecret) {\n        throw new ConfigNotFoundError('Cannot get access token since MangaDex client ID or secret is not set.');\n    }\n\n    if (!config.mangadex.username || !config.mangadex.password) {\n        throw new ConfigNotFoundError('Cannot get refresh token since MangaDex username or password is not set');\n    }\n\n    const response = await got.post(constants.API.TOKEN, {\n        headers: {\n            'User-Agent': config.trueUA,\n        },\n        form: {\n            grant_type: 'password',\n            username: config.mangadex.username,\n            password: config.mangadex.password,\n            client_id: config.mangadex.clientId,\n            client_secret: config.mangadex.clientSecret,\n        },\n    });\n\n    const refreshToken = response?.data?.refresh_token;\n    const accessToken = response?.data?.access_token;\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/mangadex/_access.ts#L39-L75","documentation":"Thrown by `getAccessTokenByUserCredentials` (lib/routes/mangadex/_access.ts:57) as a `ConfigNotFoundError` when the client credentials ARE present but `config.mangadex.username` or `config.mangadex.password` is missing. This path is only taken when no refresh token is configured, so MangaDex is asked for a password grant — which needs the resource-owner credentials.","triggerScenarios":"Client id/secret set and no refresh token, but `MANGADEX_USERNAME`/`MANGADEX_PASSWORD` omitted; refresh token expired/invalid and the fallback to password grant finds no username/password.","commonSituations":"Admin intended to use refresh-token auth but never set it, leaving the password grant as the only path; credentials partially migrated.","solutions":["Set `MANGADEX_USERNAME` and `MANGADEX_PASSWORD` to enable the password grant, OR","Set `MANGADEX_REFRESH_TOKEN` so the password grant is never needed.","Restart RSSHub after updating config."],"exampleFix":"// before: client id/secret only\nMANGADEX_CLIENT_ID=xxx\nMANGADEX_CLIENT_SECRET=yyy\n\n// after: add a refresh token (preferred) so username/password are unnecessary\nMANGADEX_CLIENT_ID=xxx\nMANGADEX_CLIENT_SECRET=yyy\nMANGADEX_REFRESH_TOKEN=zzz","handlingStrategy":"validation","validationCode":"function mangadexUserCredentialsConfigured(): boolean {\n    return Boolean(config.mangadex?.username && config.mangadex?.password);\n}\nfunction mangadexRefreshTokenConfigured(): boolean {\n    return Boolean(config.mangadex?.refreshToken);\n}\n// password grant is only valid when EITHER a refresh token OR user creds exist; client creds alone are insufficient.\nif (!mangadexRefreshTokenConfigured() && !mangadexUserCredentialsConfigured()) {\n    throw new Error('Set MANGADEX_REFRESH_TOKEN or (MANGADEX_USERNAME + MANGADEX_PASSWORD)');\n}","typeGuard":"function hasMangadexUserCredentials(c: typeof config): c is typeof config & { mangadex: { username: string; password: string } } {\n    return typeof c.mangadex?.username === 'string' && typeof c.mangadex?.password === 'string';\n}","tryCatchPattern":"import ConfigNotFoundError from '@/errors/types/config-not-found';\ntry {\n    return await getAccessTokenByUserCredentials();\n} catch (e) {\n    if (e instanceof ConfigNotFoundError && /username or password is not set/.test(e.message)) {\n        return { disabled: true, reason: 'Set MANGADEX_REFRESH_TOKEN (preferred) or MANGADEX_USERNAME+MANGADEX_PASSWORD' };\n    }\n    throw e;\n}","preventionTips":["Prefer a refresh token to avoid the password-grant path and credential rotation pain.","If using password grant, ensure username/password are set alongside client id/secret.","Validate the full credential matrix on boot."],"tags":["config","missing-secret","oauth","mangadex"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}