|
@@ -6,10 +6,12 @@ import json
|
|
|
import uuid
|
|
|
from subprocess import Popen, CalledProcessError, check_output
|
|
|
from typing import Optional
|
|
|
+import hashlib
|
|
|
+import base64
|
|
|
|
|
|
import redis
|
|
|
from fastapi import FastAPI, APIRouter, HTTPException, Cookie, Request
|
|
|
-from fastapi.responses import Response, JSONResponse, RedirectResponse
|
|
|
+from fastapi.responses import Response, RedirectResponse
|
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
from pydantic import BaseModel, BaseSettings
|
|
|
|
|
@@ -201,22 +203,47 @@ async def logout(session_id: str = Cookie(None)) -> BaseModel:
|
|
|
delete_session(session_id)
|
|
|
return BaseModel()
|
|
|
|
|
|
+KDF_SALT_SIZE = 16
|
|
|
+
|
|
|
+
|
|
|
+def kdf_gen(password, salt=None) -> str:
|
|
|
+ if salt is None:
|
|
|
+ salt = random.randbytes(KDF_SALT_SIZE)
|
|
|
+ if hasattr(password, 'encode'):
|
|
|
+ password = password.encode('utf8')
|
|
|
+ raw = hashlib.scrypt(password, n=2, r=1, p=1, salt=salt)
|
|
|
+ with_salt = salt + raw
|
|
|
+ return base64.b64encode(with_salt).decode('ascii')
|
|
|
+
|
|
|
+def kdf_get_salt(hashed: str):
|
|
|
+ hashed_str = hashed.decode('ascii') if hasattr(hashed, 'decode') else hashed
|
|
|
+ with_salt = base64.b64decode(hashed_str)
|
|
|
+ salt = with_salt[:KDF_SALT_SIZE]
|
|
|
+ return salt
|
|
|
+
|
|
|
+def kdf_verify(hashed: str, password: str) -> bool:
|
|
|
+ salt = kdf_get_salt(hashed)
|
|
|
+ hashed2 = kdf_gen(password, salt=salt)
|
|
|
+ return hashed == hashed2
|
|
|
+
|
|
|
|
|
|
@router.post("/generate", tags=["password"])
|
|
|
async def generate(session_id: str = Cookie(None)):
|
|
|
session = get_session(session_id)
|
|
|
- session["proposed_password"] = password_generate()
|
|
|
+ proposed_password = password_generate()
|
|
|
+ session["proposed_password_hash"] = kdf_gen(proposed_password)
|
|
|
set_session(session_id, session)
|
|
|
|
|
|
- return ChangeData(password=session["proposed_password"])
|
|
|
+ return ChangeData(password=proposed_password)
|
|
|
|
|
|
|
|
|
@router.post("/change", tags=["password"])
|
|
|
async def change(req: ChangeData, session_id: str = Cookie(None)) -> SuccessData:
|
|
|
session = get_session(session_id)
|
|
|
- if "proposed_password" not in session:
|
|
|
+ if "proposed_password_hash" not in session:
|
|
|
raise HTTPException(status_code=400, detail="You must generate it first")
|
|
|
- if req.password != session["proposed_password"]:
|
|
|
+ hashed = session["proposed_password_hash"]
|
|
|
+ if not kdf_verify(hashed, req.password):
|
|
|
raise HTTPException(status_code=409)
|
|
|
|
|
|
success = change_password(session["username"], req.password)
|