Download src/auth.py from Ilnaa/SiPerak: direct link, hf CLI and curl.
- Browser
- Download file 3.08 kB
-
https://huggingface.co/spaces/Ilnaa/SiPerak/resolve/main/src/auth.py
- Command line
-
hf download hf://spaces/Ilnaa/SiPerak/src/auth.py
-
curl -L -o auth.py https://huggingface.co/spaces/Ilnaa/SiPerak/resolve/main/src/auth.py
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) | |
| 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"]) | |