DIYgod/RSSHub · error · ConfigNotFoundError

Cannot get access token since MangaDex refresh token is not

Error message

Cannot get access token since MangaDex refresh token is not set.

What it means

ConfigNotFoundError raised inside getAccessTokenByRefreshToken when config.mangadex.refreshToken is falsy. In the normal flow getToken routes falsy refreshToken to the username/password path (line 32), so this guard is effectively a defensive check that is only reachable if getAccessTokenByRefreshToken is invoked directly or config.mangadex.refreshToken becomes falsy between the two reads.

Source

Thrown at lib/routes/mangadex/_access.ts:90

    const refreshToken = response?.data?.refresh_token;
    const accessToken = response?.data?.access_token;

    if (!refreshToken || !accessToken) {
        throw new Error('Failed to retrieve refresh token from MangaDex API.');
    }

    config.mangadex.refreshToken = refreshToken; // cache the refresh token
    return accessToken;
};

const getAccessTokenByRefreshToken = async () => {
    if (!config.mangadex.clientId || !config.mangadex.clientSecret) {
        throw new ConfigNotFoundError('Cannot get access token since MangaDex client ID or secret is not set.');
    }

    if (!config.mangadex.refreshToken) {
        throw new ConfigNotFoundError('Cannot get access token since MangaDex refresh token is not set.');
    }

    const response = await got.post(constants.API.TOKEN, {
        headers: {
            'User-Agent': config.trueUA,
        },
        form: {
            grant_type: 'refresh_token',
            refresh_token: config.mangadex.refreshToken,
            client_id: config.mangadex.clientId,
            client_secret: config.mangadex.clientSecret,
        },
    });

    const accessToken = response?.data?.access_token;
    if (!accessToken) {
        throw new Error('Failed to retrieve access token from MangaDex API.');
    }

View on GitHub (pinned to bed535e087)

Solutions

  1. If you intend to use the refresh-token flow, set MANGADEX_REFRESH_TOKEN to a valid token obtained from a prior password grant.
  2. Otherwise ensure MANGADEX_USERNAME and MANGADEX_PASSWORD are set so getToken takes the credentials path.
  3. Do not import getAccessTokenByRefreshToken directly; route all auth through the default getToken export.
  4. If you set refreshToken at runtime, do it before the first cache populate (key mangadex:access-token).
Defensive patterns

Strategy: validation

Validate before calling

const useRefreshFlow = Boolean(config.mangadex.refreshToken);
if (useRefreshFlow && (!config.mangadex.clientId || !config.mangadex.clientSecret)) {
    throw new ConfigNotFoundError('Refresh-token flow needs MANGADEX_CLIENT_ID and MANGADEX_CLIENT_SECRET');
}
if (useRefreshFlow && !config.mangadex.refreshToken) {
    // unreachable via getToken, but guard direct callers
    throw new ConfigNotFoundError('Set MANGADEX_REFRESH_TOKEN or username/password');
}

Type guard

const hasRefreshFlow = (c: typeof config): boolean =>
    Boolean(c.mangadex?.refreshToken && c.mangadex?.clientId && c.mangadex?.clientSecret);

Prevention

When it happens

Trigger: Direct invocation of getAccessTokenByRefreshToken with no MANGADEX_REFRESH_TOKEN configured; or a race where config.mangadex.refreshToken was truthy at getToken line 32 but cleared by the time line 89 runs (extremely unlikely given config is a synchronous singleton).

Common situations: Operator expected a refresh-token-only flow but never set MANGADEX_REFRESH_TOKEN; a custom route or test imported the non-exported function directly.

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/c91fcf194ecca6e8. Report an issue: GitHub.