修改登录逻辑,使用RSA-OAEP 包裹每会话独立 AES-GCM 密钥 + 加密提交凭据
This commit is contained in:
@@ -1,5 +1,23 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import base64
|
||||
from typing import Tuple
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
||||
from cryptography.hazmat.primitives import serialization, hashes
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
except Exception:
|
||||
rsa = None
|
||||
padding = None
|
||||
serialization = None
|
||||
hashes = None
|
||||
Cipher = None
|
||||
algorithms = None
|
||||
modes = None
|
||||
default_backend = None
|
||||
|
||||
|
||||
def salt_for_username(username: str) -> bytes:
|
||||
@@ -18,3 +36,58 @@ def derive_password(password_plain: str, salt: bytes, iterations: int = 100_000,
|
||||
def hmac_sha256(key: bytes, message: bytes) -> bytes:
|
||||
"""Compute HMAC-SHA256 signature for the given message using key bytes."""
|
||||
return hmac.new(key, message, hashlib.sha256).digest()
|
||||
|
||||
|
||||
_RSA_PRIVATE = None
|
||||
_RSA_PUBLIC = None
|
||||
|
||||
def _ensure_rsa_keys():
|
||||
global _RSA_PRIVATE, _RSA_PUBLIC
|
||||
if _RSA_PRIVATE is None:
|
||||
if rsa is None:
|
||||
raise RuntimeError("cryptography library is required for RSA operations")
|
||||
_RSA_PRIVATE = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
_RSA_PUBLIC = _RSA_PRIVATE.public_key()
|
||||
|
||||
def get_public_key_spki_b64() -> str:
|
||||
_ensure_rsa_keys()
|
||||
spki = _RSA_PUBLIC.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
return base64.b64encode(spki).decode('ascii')
|
||||
|
||||
def rsa_oaep_decrypt_b64(ciphertext_b64: str) -> bytes:
|
||||
_ensure_rsa_keys()
|
||||
ct = base64.b64decode(ciphertext_b64)
|
||||
return _RSA_PRIVATE.decrypt(ct, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
|
||||
|
||||
def aes_gcm_decrypt_b64(key_bytes: bytes, iv_b64: str, ciphertext_b64: str) -> bytes:
|
||||
if Cipher is None:
|
||||
raise RuntimeError("cryptography library is required for AES operations")
|
||||
iv = base64.b64decode(iv_b64)
|
||||
data = base64.b64decode(ciphertext_b64)
|
||||
if len(data) < 16:
|
||||
raise ValueError("ciphertext too short")
|
||||
ct = data[:-16]
|
||||
tag = data[-16:]
|
||||
decryptor = Cipher(algorithms.AES(key_bytes), modes.GCM(iv, tag), backend=default_backend()).decryptor()
|
||||
pt = decryptor.update(ct) + decryptor.finalize()
|
||||
return pt
|
||||
|
||||
def gen_salt(length: int = 16) -> bytes:
|
||||
return os.urandom(length)
|
||||
|
||||
def hash_password_with_salt(password_plain: str, salt: bytes, iterations: int = 200_000, dklen: int = 32) -> bytes:
|
||||
return hashlib.pbkdf2_hmac('sha256', password_plain.encode('utf-8'), salt, iterations, dklen=dklen)
|
||||
|
||||
def hash_password_random_salt(password_plain: str) -> Tuple[str, str]:
|
||||
salt = gen_salt(16)
|
||||
h = hash_password_with_salt(password_plain, salt)
|
||||
return base64.b64encode(salt).decode('ascii'), base64.b64encode(h).decode('ascii')
|
||||
|
||||
def verify_password(password_plain: str, salt_b64: str, hash_b64: str) -> bool:
|
||||
try:
|
||||
salt = base64.b64decode(salt_b64)
|
||||
expected = base64.b64decode(hash_b64)
|
||||
actual = hash_password_with_salt(password_plain, salt)
|
||||
return hmac.compare_digest(actual, expected)
|
||||
except Exception:
|
||||
return False
|
||||
@@ -1,19 +1,14 @@
|
||||
import base64
|
||||
from elastic.es_connect import get_user_by_username as es_get_user_by_username
|
||||
from .crypto import salt_for_username, derive_password
|
||||
|
||||
def get_user_by_username(username: str):
|
||||
"""
|
||||
期望ES中存储的是明文密码,登录时按用户名盐派生后对nonce做HMAC验证。
|
||||
"""
|
||||
es_user = es_get_user_by_username(username)
|
||||
if es_user:
|
||||
salt = salt_for_username(username)
|
||||
derived = derive_password(es_user.get('password', ''), salt)
|
||||
return {
|
||||
'user_id': es_user.get('user_id', 0),
|
||||
'username': es_user.get('username', ''),
|
||||
'password': base64.b64encode(derived).decode('ascii'),
|
||||
'password_hash': es_user.get('password_hash'),
|
||||
'password_salt': es_user.get('password_salt'),
|
||||
'permission': es_user.get('permission', 1),
|
||||
}
|
||||
return None
|
||||
@@ -1,64 +1,39 @@
|
||||
// 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);
|
||||
}
|
||||
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]);
|
||||
}
|
||||
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 importRsaPublicKey(spkiBytes) {
|
||||
return window.crypto.subtle.importKey('spki', spkiBytes, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt']);
|
||||
}
|
||||
|
||||
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);
|
||||
async function rsaOaepEncrypt(publicKey, dataBytes) {
|
||||
const encrypted = await window.crypto.subtle.encrypt({ name: 'RSA-OAEP' }, publicKey, dataBytes);
|
||||
return new Uint8Array(encrypted);
|
||||
}
|
||||
|
||||
async function importAesKey(keyBytes) {
|
||||
return window.crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, ['encrypt']);
|
||||
}
|
||||
|
||||
async function aesGcmEncrypt(aesKey, ivBytes, dataBytes) {
|
||||
const ct = await window.crypto.subtle.encrypt({ name: 'AES-GCM', iv: ivBytes }, aesKey, dataBytes);
|
||||
return new Uint8Array(ct);
|
||||
}
|
||||
|
||||
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
@@ -68,53 +43,41 @@ document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
|
||||
const username = document.getElementById('username').value.trim();
|
||||
const password = document.getElementById('password').value;
|
||||
if (!username || !password) {
|
||||
errorEl.textContent = '请输入账户与密码';
|
||||
return;
|
||||
}
|
||||
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',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
},
|
||||
body: JSON.stringify({ username })
|
||||
const pkResp = await fetch('/accounts/pubkey/', { method: 'GET', credentials: 'same-origin', headers: { 'X-CSRFToken': csrftoken || '' } });
|
||||
if (!pkResp.ok) throw new Error('获取公钥失败');
|
||||
const pkJson = await pkResp.json();
|
||||
const spkiBytes = new Uint8Array(base64ToArrayBuffer(pkJson.public_key_spki));
|
||||
const pubKey = await importRsaPublicKey(spkiBytes);
|
||||
|
||||
const aesKeyRaw = new Uint8Array(32); window.crypto.getRandomValues(aesKeyRaw);
|
||||
const encAesKey = await rsaOaepEncrypt(pubKey, aesKeyRaw);
|
||||
const encAesKeyB64 = arrayBufferToBase64(encAesKey);
|
||||
|
||||
const setKeyResp = await fetch('/accounts/session-key/', {
|
||||
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken || '' }, body: JSON.stringify({ encrypted_key: encAesKeyB64 })
|
||||
});
|
||||
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));
|
||||
const setKeyJson = await setKeyResp.json();
|
||||
if (!setKeyResp.ok || !setKeyJson.ok) throw new Error('设置会话密钥失败');
|
||||
|
||||
// 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);
|
||||
const aesKey = await importAesKey(aesKeyRaw);
|
||||
const iv = new Uint8Array(12); window.crypto.getRandomValues(iv);
|
||||
const payload = new TextEncoder().encode(JSON.stringify({ username, password }));
|
||||
const ct = await aesGcmEncrypt(aesKey, iv, payload);
|
||||
const ctB64 = arrayBufferToBase64(ct);
|
||||
const ivB64 = arrayBufferToBase64(iv);
|
||||
|
||||
// Step 3: submit login with username and hmac
|
||||
const submitResp = await fetch('/accounts/login/submit/', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
},
|
||||
body: JSON.stringify({ username, hmac: hmacB64 })
|
||||
const submitResp = await fetch('/accounts/login/secure-submit/', {
|
||||
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken || '' }, body: JSON.stringify({ iv: ivB64, ciphertext: ctB64 })
|
||||
});
|
||||
const submitJson = await submitResp.json();
|
||||
if (!submitResp.ok || !submitJson.ok) {
|
||||
throw new Error(submitJson.message || '登录失败');
|
||||
}
|
||||
// Redirect to home with user_id
|
||||
if (!submitResp.ok || !submitJson.ok) throw new Error(submitJson.message || '登录失败');
|
||||
window.location.href = submitJson.redirect_url;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -4,7 +4,8 @@ app_name = "accounts"
|
||||
|
||||
urlpatterns = [
|
||||
path("login/", views.login_page, name="login"),
|
||||
path("challenge/", views.challenge, name="challenge"),
|
||||
path("login/submit/", views.login_submit, name="login_submit"),
|
||||
path("pubkey/", views.pubkey, name="pubkey"),
|
||||
path("session-key/", views.set_session_key, name="set_session_key"),
|
||||
path("login/secure-submit/", views.secure_login_submit, name="secure_login_submit"),
|
||||
path("logout/", views.logout, name="logout"),
|
||||
]
|
||||
@@ -1,7 +1,6 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import hmac
|
||||
|
||||
from django.http import JsonResponse, HttpResponseBadRequest
|
||||
from django.shortcuts import render, redirect
|
||||
@@ -10,7 +9,7 @@ from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie
|
||||
from django.conf import settings
|
||||
|
||||
from .es_client import get_user_by_username
|
||||
from .crypto import salt_for_username, hmac_sha256
|
||||
from .crypto import get_public_key_spki_b64, rsa_oaep_decrypt_b64, aes_gcm_decrypt_b64, verify_password
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -19,90 +18,72 @@ def login_page(request):
|
||||
return render(request, "accounts/login.html")
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def challenge(request):
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest("Invalid JSON")
|
||||
|
||||
username = payload.get("username", "").strip()
|
||||
if not username:
|
||||
return HttpResponseBadRequest("Username required")
|
||||
|
||||
# Generate nonce and compute per-username salt
|
||||
nonce = os.urandom(16)
|
||||
salt = salt_for_username(username)
|
||||
|
||||
# Persist challenge in session to prevent replay with mismatched user
|
||||
request.session["challenge_nonce"] = base64.b64encode(nonce).decode("ascii")
|
||||
request.session["challenge_username"] = username
|
||||
|
||||
return JsonResponse({
|
||||
"nonce": base64.b64encode(nonce).decode("ascii"),
|
||||
"salt": base64.b64encode(salt).decode("ascii"),
|
||||
})
|
||||
@require_http_methods(["GET"])
|
||||
@ensure_csrf_cookie
|
||||
def pubkey(request):
|
||||
pk_b64 = get_public_key_spki_b64()
|
||||
return JsonResponse({"public_key_spki": pk_b64})
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def login_submit(request):
|
||||
def set_session_key(request):
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest("Invalid JSON")
|
||||
|
||||
username = payload.get("username", "").strip()
|
||||
client_hmac_b64 = payload.get("hmac", "")
|
||||
if not username or not client_hmac_b64:
|
||||
enc_key_b64 = payload.get("encrypted_key", "")
|
||||
if not enc_key_b64:
|
||||
return HttpResponseBadRequest("Missing fields")
|
||||
try:
|
||||
key_bytes = rsa_oaep_decrypt_b64(enc_key_b64)
|
||||
except Exception:
|
||||
return HttpResponseBadRequest("Decrypt error")
|
||||
request.session["session_enc_key_b64"] = base64.b64encode(key_bytes).decode("ascii")
|
||||
return JsonResponse({"ok": True})
|
||||
|
||||
# Validate challenge stored in session
|
||||
session_username = request.session.get("challenge_username")
|
||||
nonce_b64 = request.session.get("challenge_nonce")
|
||||
if not session_username or not nonce_b64 or session_username != username:
|
||||
return HttpResponseBadRequest("Challenge not found or mismatched user")
|
||||
|
||||
# Lookup user in ES (placeholder)
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def secure_login_submit(request):
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest("Invalid JSON")
|
||||
iv_b64 = payload.get("iv", "")
|
||||
ct_b64 = payload.get("ciphertext", "")
|
||||
if not iv_b64 or not ct_b64:
|
||||
return HttpResponseBadRequest("Missing fields")
|
||||
key_b64 = request.session.get("session_enc_key_b64")
|
||||
if not key_b64:
|
||||
return HttpResponseBadRequest("Session key missing")
|
||||
try:
|
||||
key_bytes = base64.b64decode(key_b64)
|
||||
pt = aes_gcm_decrypt_b64(key_bytes, iv_b64, ct_b64)
|
||||
obj = json.loads(pt.decode("utf-8"))
|
||||
except Exception:
|
||||
return HttpResponseBadRequest("Decrypt error")
|
||||
username = (obj.get("username") or "").strip()
|
||||
password = (obj.get("password") or "")
|
||||
if not username or not password:
|
||||
return HttpResponseBadRequest("Missing credentials")
|
||||
user = get_user_by_username(username)
|
||||
if not user:
|
||||
return JsonResponse({"ok": False, "message": "User not found"}, status=401)
|
||||
|
||||
# Server-side HMAC verification
|
||||
try:
|
||||
nonce = base64.b64decode(nonce_b64)
|
||||
stored_derived_b64 = user.get("password", "")
|
||||
stored_derived = base64.b64decode(stored_derived_b64)
|
||||
server_hmac_b64 = base64.b64encode(hmac_sha256(stored_derived, nonce)).decode("ascii")
|
||||
except Exception:
|
||||
return HttpResponseBadRequest("Verification error")
|
||||
|
||||
if not hmac.compare_digest(server_hmac_b64, client_hmac_b64):
|
||||
if not verify_password(password, user.get("password_salt") or "", user.get("password_hash") or ""):
|
||||
return JsonResponse({"ok": False, "message": "Invalid credentials"}, status=401)
|
||||
|
||||
# Successful login: rotate session key and set user session
|
||||
try:
|
||||
request.session.cycle_key()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
request.session["user_id"] = user["user_id"]
|
||||
request.session["username"] = user["username"]
|
||||
try:
|
||||
request.session["permission"] = int(user["permission"]) if user.get("permission") is not None else 1
|
||||
except Exception:
|
||||
request.session["permission"] = 1
|
||||
|
||||
# Clear challenge to prevent reuse
|
||||
for k in ("challenge_username", "challenge_nonce"):
|
||||
if k in request.session:
|
||||
del request.session[k]
|
||||
|
||||
return JsonResponse({
|
||||
"ok": True,
|
||||
"redirect_url": f"/main/home/?user_id={user['user_id']}",
|
||||
})
|
||||
if "session_enc_key_b64" in request.session:
|
||||
del request.session["session_enc_key_b64"]
|
||||
return JsonResponse({"ok": True, "redirect_url": f"/main/home/?user_id={user['user_id']}"})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
|
||||
@@ -34,8 +34,9 @@ class UserDocument(Document):
|
||||
"""用户数据文档映射"""
|
||||
user_id = fields.LongField()
|
||||
username = fields.KeywordField()
|
||||
password = fields.KeywordField() # 还是2种权限,0为管理员,1为用户(区别在于0有全部权限,1在数据管理页面有搜索框,但是索引到的录入信息要根据其用户id查询其key,若其中之一与用户的manage_key字段匹配就显示否则不显示)
|
||||
permission = fields.IntegerField()
|
||||
password_hash = fields.KeywordField()
|
||||
password_salt = fields.KeywordField()
|
||||
permission = fields.IntegerField() # 还是2种权限,0为管理员,1为用户(区别在于0有全部权限,1在数据管理页面有搜索框,但是索引到的录入信息要根据其用户id查询其key,若其中之一与用户的manage_key字段匹配就显示否则不显示)
|
||||
key = fields.IntegerField() #表示该用户的关键字,举个例子:学生A的key为"2024届人工智能1班","2024届","计算机与人工智能学院" 班导师B的key为"计算机与人工智能学院"
|
||||
manage_key = fields.IntegerField() #表示该用户管理的关键字(非管理员)班导师B的manage_key为"2024届人工智能1班"
|
||||
#那么学生A就可以在数据管理页面搜索到自己的获奖数据,而班导师B就可以在数据管理页面搜索到所有人工智能1班的获奖数据。也就是说学生A和班导师B都其实只有用户权限
|
||||
|
||||
@@ -6,6 +6,7 @@ from elasticsearch import Elasticsearch
|
||||
from elasticsearch_dsl import connections
|
||||
import os
|
||||
from .documents import AchievementDocument, UserDocument, GlobalDocument
|
||||
from accounts.crypto import hash_password_random_salt
|
||||
from .indexes import ACHIEVEMENT_INDEX_NAME, USER_INDEX_NAME, GLOBAL_INDEX_NAME
|
||||
import hashlib
|
||||
import time
|
||||
@@ -63,10 +64,12 @@ def create_index_with_mapping():
|
||||
|
||||
# --- 4. 创建默认管理员用户(可选:也可检查用户是否已存在)---
|
||||
# 这里简单处理:每次初始化都写入(可能重复),建议加唯一性判断
|
||||
_salt_b64, _hash_b64 = hash_password_random_salt("admin")
|
||||
admin_user = {
|
||||
"user_id": 0,
|
||||
"username": "admin",
|
||||
"password": "admin", # ⚠️ 生产环境务必加密!
|
||||
"password_hash": _hash_b64,
|
||||
"password_salt": _salt_b64,
|
||||
"permission": 0
|
||||
}
|
||||
# 可选:检查 admin 是否已存在(根据 user_id 或 username)
|
||||
@@ -513,10 +516,17 @@ def write_user_data(user_data):
|
||||
perm_val = int(user_data.get('permission', 1))
|
||||
except Exception:
|
||||
perm_val = 1
|
||||
pwd = str(user_data.get('password') or '').strip()
|
||||
pwd_hash_b64 = user_data.get('password_hash')
|
||||
pwd_salt_b64 = user_data.get('password_salt')
|
||||
if pwd:
|
||||
salt_b64, hash_b64 = hash_password_random_salt(pwd)
|
||||
pwd_hash_b64, pwd_salt_b64 = hash_b64, salt_b64
|
||||
user = UserDocument(
|
||||
user_id=user_data.get('user_id'),
|
||||
username=user_data.get('username'),
|
||||
password=user_data.get('password'),
|
||||
password_hash=pwd_hash_b64,
|
||||
password_salt=pwd_salt_b64,
|
||||
permission=perm_val
|
||||
)
|
||||
user.save()
|
||||
@@ -537,7 +547,6 @@ def get_user_by_id(user_id):
|
||||
return {
|
||||
"user_id": hit.user_id,
|
||||
"username": hit.username,
|
||||
"password": hit.password,
|
||||
"permission": hit.permission
|
||||
}
|
||||
return None
|
||||
@@ -566,7 +575,8 @@ def get_user_by_username(username):
|
||||
return {
|
||||
"user_id": hit.user_id,
|
||||
"username": hit.username,
|
||||
"password": hit.password,
|
||||
"password_hash": getattr(hit, 'password_hash', None),
|
||||
"password_salt": getattr(hit, 'password_salt', None),
|
||||
"permission": int(hit.permission)
|
||||
}
|
||||
return None
|
||||
@@ -639,7 +649,9 @@ def update_user_by_id(user_id, username=None, permission=None, password=None):
|
||||
if permission is not None:
|
||||
doc.permission = int(permission)
|
||||
if password is not None:
|
||||
doc.password = password
|
||||
salt_b64, hash_b64 = hash_password_random_salt(str(password))
|
||||
doc.password_hash = hash_b64
|
||||
doc.password_salt = salt_b64
|
||||
doc.save()
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
INDEX_NAME = "wordsearch2666661"
|
||||
USER_NAME = "users11111"
|
||||
USER_NAME = "users1111166"
|
||||
ACHIEVEMENT_INDEX_NAME = INDEX_NAME
|
||||
USER_INDEX_NAME = USER_NAME
|
||||
GLOBAL_INDEX_NAME = "global11111111211"
|
||||
GLOBAL_INDEX_NAME = "global1111111121"
|
||||
|
||||
Reference in New Issue
Block a user