登录上线
This commit is contained in:
123
accounts/static/accounts/login.js
Normal file
123
accounts/static/accounts/login.js
Normal 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;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user