SiPerak / src /auth.py
Ilnaa's picture
Upload folder using huggingface_hub
42fd126 verified
Raw History Blame Contribute Delete
3.08 kB
"""Autentikasi: registrasi (UC-01) & login dengan password hashing.
Memakai hashlib + salt (PBKDF2) dari pustaka standar agar tidak menambah
dependency eksternal. Logika validasi dipisah agar dapat diuji langsung.
"""
from __future__ import annotations
import hashlib
import hmac
import os
import re
from dataclasses import dataclass
from src import db
from src.constants import ROLE_MASYARAKAT
_EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
_PBKDF2_ROUNDS = 200_000
def hash_password(password: str) -> str:
"""Hash password dengan PBKDF2-HMAC-SHA256 + salt acak. Format: salt$hash (hex)."""
salt = os.urandom(16)
dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, _PBKDF2_ROUNDS)
return f"{salt.hex()}${dk.hex()}"
def verify_password(password: str, stored: str) -> bool:
try:
salt_hex, hash_hex = stored.split("$", 1)
except ValueError:
return False
salt = bytes.fromhex(salt_hex)
dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, _PBKDF2_ROUNDS)
return hmac.compare_digest(dk.hex(), hash_hex)
@dataclass
class AuthResult:
ok: bool
message: str
user_id: int | None = None
role: str | None = None
nama: str | None = None
def validate_registration(nama, email, telepon, password, konfirmasi) -> str | None:
"""Validasi field pendaftaran. Kembalikan pesan error, atau None bila valid."""
if not all([nama, email, telepon, password, konfirmasi]):
return "Semua field wajib diisi."
if not _EMAIL_RE.match(email):
return "Format email tidak valid."
if not telepon.isdigit() or not (8 <= len(telepon) <= 15):
return "Nomor telepon harus 8-15 digit angka."
if len(password) < 6:
return "Password minimal 6 karakter."
if password != konfirmasi:
return "Password dan konfirmasi tidak cocok."
return None
def register(conn, nama, email, telepon, password, konfirmasi, role=ROLE_MASYARAKAT) -> AuthResult:
error = validate_registration(nama, email, telepon, password, konfirmasi)
if error:
return AuthResult(False, error)
if db.email_exists(conn, email):
return AuthResult(False, "Email sudah terdaftar.") # UC-01 eksepsi A.2
if db.telepon_exists(conn, telepon):
return AuthResult(False, "Nomor sudah terdaftar.") # UC-01 eksepsi A.3
user_id = db.create_user(conn, nama.strip(), email.strip().lower(),
telepon.strip(), hash_password(password), role)
return AuthResult(True, "Pendaftaran berhasil. Silakan masuk.", user_id=user_id, role=role, nama=nama)
def login(conn, email, password) -> AuthResult:
if not email or not password:
return AuthResult(False, "Email dan password wajib diisi.")
user = db.get_user_by_email(conn, email.strip().lower())
if user is None or not verify_password(password, user["password_hash"]):
return AuthResult(False, "Email atau password salah.")
return AuthResult(True, "Login berhasil.", user_id=user["id"], role=user["role"], nama=user["nama"])