Files
chongming-plugins/auth_oauth/router/phone.py
T

83 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""手机验证码登录端点(演示模式:验证码存共享缓存、默认随响应返回)。
生产接入真实短信通道后,将 send_code 中的 dev_code 分支关闭
(config: return_otp_in_response = false),验证码只经短信下发。
"""
import logging
import re
import secrets
from chongming_worker import rpc_method
from ..models import (
LoginPostResponse,
PhoneLoginPostRequest,
PhoneSendCodePostRequest,
PhoneSendCodePostResponse,
)
from .. import store
logger = logging.getLogger("plugins.auth_oauth.phone")
_OTP_TTL = 300 # 验证码有效期(秒)
_PHONE_RE = re.compile(r"^1\d{10}$") # 演示按中国大陆手机号校验
def _otp_key(phone: str) -> str:
return f"auth_oauth:otp:{phone}"
@rpc_method(
subject="auth_oauth.phone_send_code",
method="POST",
summary="手机验证码登录:下发验证码(演示模式:存缓存并随响应返回)",
)
async def phone_send_code(req: PhoneSendCodePostRequest) -> PhoneSendCodePostResponse:
from ..global_var import cache, plugin_config
if cache is None:
return PhoneSendCodePostResponse(result="fail", message="插件未初始化")
if not _PHONE_RE.match(req.phone):
return PhoneSendCodePostResponse(result="fail", message="手机号格式不正确")
code = f"{secrets.randbelow(1_000_000):06d}"
cache.set(_otp_key(req.phone), code, expire=_OTP_TTL)
# 生产环境在此接真实短信通道;演示模式打印并将验证码随响应返回
logger.info("[auth_oauth] 验证码已生成 phone=%s code=%s(演示模式,请勿用于生产)",
req.phone, code)
if plugin_config.get("return_otp_in_response", True):
return PhoneSendCodePostResponse(
result="success", message="验证码已发送(演示模式直接返回)", dev_code=code,
)
return PhoneSendCodePostResponse(result="success", message="验证码已发送")
@rpc_method(
subject="auth_oauth.phone_login",
method="POST",
summary="手机验证码登录:校验验证码,find-or-create 后签发平台 token",
)
async def phone_login(req: PhoneLoginPostRequest) -> LoginPostResponse:
from ..global_var import cache, db
if db is None or cache is None:
return store.fail_response("插件未初始化")
key = _otp_key(req.phone)
stored = cache.get(key)
if not stored or stored != req.code:
return store.fail_response("验证码错误或已过期")
cache.delete(key) # 一次性消费
user, message = await store.find_or_create_user(
db, "phone", req.phone,
profile={
"username": req.phone,
"email": f"{req.phone}@phone.local",
},
)
if user is None:
return store.fail_response(message)
return store.issue_login_response(user)