码桶
发现社区成员的开源项目
totp-setup.js4.9 KB
// TOTP setup / teardown for authenticated users.
import {
generateTOTPSecret,
verifyTOTP,
generateOTPAuthURI
} from '../_totp.js';
import { logAudit } from '../_audit.js';
const ISSUER = 'CF-DNS-Manager';
const PENDING_TTL = 600; // 10 minutes
function jsonResponse(body, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { 'Content-Type': 'application/json' }
});
}
function getUser(context) {
const user = context.data.user;
if (!user || !user.username) return null;
return user;
}
// ── GET — generate a new TOTP secret and store it as pending ────────────────
export async function onRequestGet(context) {
const { env } = context;
const kv = env.CF_DNS_KV;
if (!kv) return jsonResponse({ error: 'KV storage not configured.' }, 500);
const user = getUser(context);
if (!user) return jsonResponse({ error: 'Authentication required.' }, 401);
const username = user.username;
const secret = generateTOTPSecret();
// Store pending secret with 10-minute TTL
await kv.put(`TOTP_PENDING:${username}`, secret, { expirationTtl: PENDING_TTL });
const uri = generateOTPAuthURI(secret, username, ISSUER);
return jsonResponse({ secret, uri });
}
// ── POST — confirm TOTP setup by verifying the code ─────────────────────────
export async function onRequestPost(context) {
const { env } = context;
const kv = env.CF_DNS_KV;
if (!kv) return jsonResponse({ error: 'KV storage not configured.' }, 500);
const user = getUser(context);
if (!user) return jsonResponse({ error: 'Authentication required.' }, 401);
const username = user.username;
const body = await context.request.json();
const { code } = body;
if (!code) return jsonResponse({ error: 'TOTP code is required.' }, 400);
// Retrieve pending secret
const pendingSecret = await kv.get(`TOTP_PENDING:${username}`);
if (!pendingSecret) {
return jsonResponse({ error: 'No pending TOTP setup found. Please start setup again.' }, 400);
}
// Verify the code
const valid = await verifyTOTP(pendingSecret, String(code));
if (!valid) {
return jsonResponse({ error: 'Invalid TOTP code. Please try again.' }, 400);
}
// Persist secret into user data
if (username === 'admin') {
// Admin user doesn't have a USER:{username} record by default,
// so store the TOTP secret in a dedicated key.
await kv.put('TOTP_SECRET:admin', pendingSecret);
} else {
const dataJson = await kv.get(`USER:${username}`);
if (!dataJson) {
return jsonResponse({ error: 'User not found.' }, 404);
}
const userData = JSON.parse(dataJson);
userData.totpSecret = pendingSecret;
await kv.put(`USER:${username}`, JSON.stringify(userData));
}
// Clean up pending
await kv.delete(`TOTP_PENDING:${username}`);
await logAudit(kv, username, 'totp.enable', 'TOTP two-factor authentication enabled');
return jsonResponse({ success: true });
}
// ── DELETE — disable TOTP (requires a valid code to confirm) ────────────────
export async function onRequestDelete(context) {
const { env } = context;
const kv = env.CF_DNS_KV;
if (!kv) return jsonResponse({ error: 'KV storage not configured.' }, 500);
const user = getUser(context);
if (!user) return jsonResponse({ error: 'Authentication required.' }, 401);
const username = user.username;
const body = await context.request.json();
const { code } = body;
if (!code) return jsonResponse({ error: 'TOTP code is required.' }, 400);
// Get the stored TOTP secret
let totpSecret = null;
if (username === 'admin') {
totpSecret = await kv.get('TOTP_SECRET:admin');
} else {
const dataJson = await kv.get(`USER:${username}`);
if (!dataJson) {
return jsonResponse({ error: 'User not found.' }, 404);
}
const userData = JSON.parse(dataJson);
totpSecret = userData.totpSecret || null;
}
if (!totpSecret) {
return jsonResponse({ error: 'TOTP is not enabled for this account.' }, 400);
}
const valid = await verifyTOTP(totpSecret, String(code));
if (!valid) {
return jsonResponse({ error: 'Invalid TOTP code.' }, 403);
}
// Remove secret from user data
if (username === 'admin') {
await kv.delete('TOTP_SECRET:admin');
} else {
const dataJson = await kv.get(`USER:${username}`);
if (dataJson) {
const userData = JSON.parse(dataJson);
delete userData.totpSecret;
await kv.put(`USER:${username}`, JSON.stringify(userData));
}
}
await logAudit(kv, username, 'totp.disable', 'TOTP two-factor authentication disabled');
return jsonResponse({ success: true });
}