feat(server): modularize backend and admin console
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import base64
|
||||
import json
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.repositories import client_update_repository
|
||||
from app.services.signing_service import load_manifest_private_key
|
||||
|
||||
|
||||
def verify_device_credential(encoded: str) -> dict:
|
||||
try:
|
||||
wrapper = json.loads(base64.b64decode(encoded, validate=True).decode("utf-8"))
|
||||
identity_text = wrapper["identity_text"]
|
||||
signature = base64.b64decode(wrapper["signature"], validate=True)
|
||||
load_manifest_private_key().public_key().verify(signature, identity_text.encode(), padding.PKCS1v15(), hashes.SHA256())
|
||||
identity = json.loads(identity_text)
|
||||
if any(not identity.get(k) for k in ("device_id", "license_id", "app_id", "channel", "installation_id", "credential_seq", "valid_until")):
|
||||
raise ValueError("missing field")
|
||||
except Exception:
|
||||
raise HTTPException(status_code=401, detail={"error": "invalid_device_credential", "msg": "设备凭证格式或签名无效"})
|
||||
|
||||
row = client_update_repository.get_device(identity["device_id"])
|
||||
if not row:
|
||||
raise HTTPException(status_code=401, detail={"error": "device_not_registered", "msg": "设备未登记"})
|
||||
if row["disabled"]:
|
||||
reason = row["disabled_reason"] or "设备已被管理员禁用"
|
||||
raise HTTPException(status_code=403, detail={"error": "device_disabled", "msg": reason})
|
||||
if row["app_id"] != identity["app_id"] or row["installation_id"] != identity["installation_id"] or int(row["credential_seq"]) != int(identity["credential_seq"]):
|
||||
raise HTTPException(status_code=401, detail={"error": "device_credential_revoked", "msg": "设备凭证已失效"})
|
||||
|
||||
license_row = client_update_repository.get_license(identity.get("license_id", ""))
|
||||
now = datetime.now(timezone.utc)
|
||||
try:
|
||||
license_expiry = datetime.fromisoformat((license_row["valid_until"] if license_row else "").replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
license_expiry = now - timedelta(seconds=1)
|
||||
if not license_row or license_row["status"] != "active" or license_expiry <= now:
|
||||
raise HTTPException(status_code=403, detail={"error": "license_invalid", "msg": "授权不存在、已禁用或已过期"})
|
||||
if license_row["app_id"] != identity["app_id"] or license_row["channel_code"] != identity.get("channel"):
|
||||
raise HTTPException(status_code=403, detail={"error": "license_scope_mismatch", "msg": "授权应用或渠道不匹配"})
|
||||
client_update_repository.touch_device(identity["device_id"])
|
||||
return identity
|
||||
Reference in New Issue
Block a user