8b43d3df52
- 报名/测评/政策/调研,独立 data/opc.db - 源自原培训后端,路径调整至 server-core
62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
"""认证:密码哈希、令牌签发/校验、短信验证码(兼容原 mock 方案)"""
|
||
import base64
|
||
import hashlib
|
||
import json
|
||
import time
|
||
import random
|
||
import re
|
||
|
||
SECRET = "pine-mock-secret-2026"
|
||
TOKEN_TTL_MS = 1000 * 60 * 60 * 12 # 12h
|
||
PHONE_RE = re.compile(r"^1\d{10}$")
|
||
CODE_TTL = 5 * 60 # 5 min
|
||
|
||
# 演示环境:内存验证码 map(phone -> {code, exp}),响应返回 debugCode
|
||
sms_codes: dict[str, dict] = {}
|
||
|
||
|
||
def hash_password(s: str) -> str:
|
||
return hashlib.sha256(str(s).encode()).hexdigest()
|
||
|
||
|
||
def b64url(data: bytes) -> str:
|
||
return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
|
||
|
||
|
||
def make_token(username: str) -> str:
|
||
payload = b64url(json.dumps({"username": username, "exp": int(time.time() * 1000) + TOKEN_TTL_MS}).encode())
|
||
sig = b64url((SECRET + "." + payload).encode())
|
||
return f"{payload}.{sig}"
|
||
|
||
|
||
def verify_token(token: str):
|
||
if not token or not isinstance(token, str):
|
||
return None
|
||
parts = token.split(".")
|
||
if len(parts) != 2:
|
||
return None
|
||
payload_part, sig_part = parts
|
||
if b64url((SECRET + "." + payload_part).encode()) != sig_part:
|
||
return None
|
||
try:
|
||
payload = json.loads(base64.urlsafe_b64decode(payload_part + "=" * (-len(payload_part) % 4)).decode())
|
||
if not payload.get("exp") or payload["exp"] < time.time() * 1000:
|
||
return None
|
||
return {"username": payload["username"]}
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
def send_sms_code(phone: str) -> str:
|
||
code = str(random.randint(100000, 999999))
|
||
sms_codes[phone] = {"code": code, "exp": time.time() + CODE_TTL}
|
||
return code
|
||
|
||
|
||
def check_sms_code(phone: str, code: str) -> bool:
|
||
rec = sms_codes.get(phone)
|
||
if not rec or rec["code"] != str(code) or time.time() > rec["exp"]:
|
||
return False
|
||
sms_codes.pop(phone, None)
|
||
return True
|