登录上线

This commit is contained in:
2025-11-09 20:31:37 +08:00
parent e650a087ca
commit aba94c074a
35 changed files with 675 additions and 5919 deletions

View File

@@ -0,0 +1,123 @@
// Utility: read cookie value
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// Convert base64 string to ArrayBuffer
function base64ToArrayBuffer(b64) {
const binary = atob(b64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
}
// ArrayBuffer to base64
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
async function deriveKey(password, saltBytes, iterations = 100000, length = 32) {
const encoder = new TextEncoder();
const keyMaterial = await window.crypto.subtle.importKey(
'raw',
encoder.encode(password),
{ name: 'PBKDF2' },
false,
['deriveBits']
);
const derivedBits = await window.crypto.subtle.deriveBits(
{
name: 'PBKDF2',
salt: saltBytes,
iterations,
hash: 'SHA-256'
},
keyMaterial,
length * 8
);
return new Uint8Array(derivedBits);
}
async function hmacSha256(keyBytes, messageBytes) {
const key = await window.crypto.subtle.importKey(
'raw',
keyBytes,
{ name: 'HMAC', hash: { name: 'SHA-256' } },
false,
['sign']
);
const signature = await window.crypto.subtle.sign('HMAC', key, messageBytes);
return new Uint8Array(signature);
}
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const errorEl = document.getElementById('error');
errorEl.textContent = '';
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value;
if (!username || !password) {
errorEl.textContent = '请输入账户与密码';
return;
}
const btn = document.getElementById('loginBtn');
btn.disabled = true;
try {
// Step 1: get challenge (nonce + salt)
const csrftoken = getCookie('csrftoken');
const chalResp = await fetch('/accounts/challenge/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken || ''
},
body: JSON.stringify({ username })
});
if (!chalResp.ok) {
throw new Error('获取挑战失败');
}
const chal = await chalResp.json();
const nonceBytes = new Uint8Array(base64ToArrayBuffer(chal.nonce));
const saltBytes = new Uint8Array(base64ToArrayBuffer(chal.salt));
// Step 2: derive secret and compute HMAC
const derived = await deriveKey(password, saltBytes, 100000, 32);
const hmac = await hmacSha256(derived, nonceBytes);
const hmacB64 = arrayBufferToBase64(hmac);
// Step 3: submit login with username and hmac
const submitResp = await fetch('/accounts/login/submit/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken || ''
},
body: JSON.stringify({ username, hmac: hmacB64 })
});
const submitJson = await submitResp.json();
if (!submitResp.ok || !submitJson.ok) {
throw new Error(submitJson.message || '登录失败');
}
// Redirect to home with user_id
window.location.href = submitJson.redirect_url;
} catch (err) {
console.error(err);
errorEl.textContent = err.message || '发生错误';
} finally {
btn.disabled = false;
}
});