码桶

发现社区成员的开源项目

refresh.js4.1 KB
import { SignJWT, jwtVerify } from 'jose';

export async function onRequestPost(context) {
    const { request, env } = context;
    const body = await request.json();
    const { refreshToken } = body;

    if (!refreshToken) {
        return new Response(JSON.stringify({ error: 'Refresh token required.' }), {
            status: 400, headers: { 'Content-Type': 'application/json' }
        });
    }

    const serverPassword = env.APP_PASSWORD;
    if (!serverPassword) {
        return new Response(JSON.stringify({ error: 'Server not configured.' }), {
            status: 400, headers: { 'Content-Type': 'application/json' }
        });
    }

    const kv = env.CF_DNS_KV;

    try {
        const secret = new TextEncoder().encode(serverPassword);
        const { payload } = await jwtVerify(refreshToken, secret);

        if (payload.type !== 'refresh') {
            return new Response(JSON.stringify({ error: 'Invalid token type.' }), {
                status: 401, headers: { 'Content-Type': 'application/json' }
            });
        }

        // Check if refresh token is revoked
        if (kv) {
            const revoked = await kv.get(`REVOKED_RT:${payload.jti}`);
            if (revoked) {
                return new Response(JSON.stringify({ error: 'Token has been revoked.' }), {
                    status: 401, headers: { 'Content-Type': 'application/json' }
                });
            }
        }

        // Verify user still exists and is active
        const username = payload.sub;
        if (username !== 'admin' && kv) {
            const userData = await kv.get(`USER:${username}`);
            if (!userData) {
                return new Response(JSON.stringify({ error: 'User no longer exists.' }), {
                    status: 401, headers: { 'Content-Type': 'application/json' }
                });
            }
            const user = JSON.parse(userData);
            if (user.status === 'pending') {
                return new Response(JSON.stringify({ error: 'Account not active.' }), {
                    status: 401, headers: { 'Content-Type': 'application/json' }
                });
            }
        }

        // Issue new access token (15 min)
        const accessToken = await new SignJWT({ sub: username, role: payload.role })
            .setProtectedHeader({ alg: 'HS256' })
            .setIssuedAt()
            .setExpirationTime('15m')
            .sign(secret);

        // Load accounts
        let accounts = [];
        if (kv) {
            const tokensJson = await kv.get(`USER_TOKENS:${username}`);
            if (tokensJson) {
                const tokens = JSON.parse(tokensJson);
                accounts = tokens.map(t => { const tp = t.type || (t.email ? 'global_key' : 'api_token'); return { id: t.id, name: t.name, type: tp, hint: tp === 'global_key' ? (t.email || '') : (t.token ? '…' + t.token.slice(-4) : '') }; });
            }
        }

        // Fallback: env var accounts for admin
        if (accounts.length === 0 && username === 'admin') {
            for (let i = 0; i < 10; i++) {
                const envToken = i === 0 ? env.CF_API_TOKEN : env[`CF_API_TOKEN${i}`];
                const envEmail = i === 0 ? env.CF_API_EMAIL : env[`CF_API_EMAIL${i}`];
                const envKey = i === 0 ? env.CF_GLOBAL_API_KEY : env[`CF_GLOBAL_API_KEY${i}`];
                if (envEmail && (envKey || envToken)) {
                    accounts.push({ id: i, name: envEmail, type: 'global_key', hint: envEmail });
                } else if (envToken) {
                    accounts.push({ id: i, name: `API Token ${i}`, type: 'api_token', hint: '…' + envToken.slice(-4) });
                } else {
                    break;
                }
            }
        }

        return new Response(JSON.stringify({
            token: accessToken,
            accounts,
            role: payload.role,
            username
        }), {
            headers: { 'Content-Type': 'application/json' }
        });
    } catch (e) {
        return new Response(JSON.stringify({ error: 'Invalid or expired refresh token.' }), {
            status: 401, headers: { 'Content-Type': 'application/json' }
        });
    }
}