Implemented login page and new jwt-based sessions. Watching cleanup.
This commit is contained in:
@@ -1,26 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hmac
|
||||
import re
|
||||
import secrets
|
||||
from functools import wraps
|
||||
from hashlib import sha256
|
||||
from pathlib import Path, PurePath
|
||||
from time import time
|
||||
from unicodedata import normalize
|
||||
|
||||
import argon2
|
||||
import msgspec
|
||||
|
||||
_argon = argon2.PasswordHasher()
|
||||
_droppyhash = re.compile(r'^([a-f0-9]{64})\$([a-f0-9]{8})$')
|
||||
|
||||
class Config(msgspec.Struct):
|
||||
path: Path = Path.cwd()
|
||||
secret: str = secrets.token_hex(12)
|
||||
public: bool = False
|
||||
users: dict[str, User] = {}
|
||||
sessions: dict[str, Session] = {}
|
||||
links: dict[str, Link] = {}
|
||||
|
||||
class User(msgspec.Struct, omit_defaults=True):
|
||||
@@ -28,13 +21,6 @@ class User(msgspec.Struct, omit_defaults=True):
|
||||
hash: str = ""
|
||||
lastSeen: int = 0
|
||||
|
||||
def set_password(self, password: str):
|
||||
self.hash = _argon.hash(_pwnorm(password))
|
||||
|
||||
class Session(msgspec.Struct):
|
||||
username: str
|
||||
lastSeen: int
|
||||
|
||||
class Link(msgspec.Struct, omit_defaults=True):
|
||||
location: str
|
||||
creator: str = ""
|
||||
@@ -54,39 +40,6 @@ def derived_secret(*params, len=8) -> bytes:
|
||||
# Output a bytes of the desired length
|
||||
return sha256(combined).digest()[:len]
|
||||
|
||||
def _pwnorm(password):
|
||||
return normalize('NFC', password).strip().encode()
|
||||
|
||||
def login(username: str, password: str):
|
||||
un = _pwnorm(username)
|
||||
pw = _pwnorm(password)
|
||||
try:
|
||||
u = config.users[un.decode()]
|
||||
except KeyError:
|
||||
raise ValueError("Invalid username")
|
||||
# Verify password
|
||||
if not u.hash:
|
||||
raise ValueError("Account disabled")
|
||||
if (m := _droppyhash.match(u.hash)) is not None:
|
||||
h, s = m.groups()
|
||||
h2 = hmac.digest(pw + s.encode() + un, b"", "sha256").hex()
|
||||
if not hmac.compare_digest(h, h2):
|
||||
raise ValueError("Invalid password")
|
||||
# Droppy hashes are weak, do a hash update
|
||||
u.set_password(password)
|
||||
else:
|
||||
try:
|
||||
_argon.verify(u.hash, pw)
|
||||
except Exception:
|
||||
raise ValueError("Invalid password")
|
||||
if _argon.check_needs_rehash(u.hash):
|
||||
u.set_password(password)
|
||||
# Login successful
|
||||
now = int(time())
|
||||
u.lastSeen = now
|
||||
sid = secrets.token_urlsafe(12)
|
||||
config.sessions[sid] = Session(username, now)
|
||||
return u, sid
|
||||
|
||||
def enc_hook(obj):
|
||||
if isinstance(obj, PurePath):
|
||||
|
||||
Reference in New Issue
Block a user