feat: 添加清除结果功能到检查提供者
refactor: 更新URL哈希处理逻辑 feat: 添加聊天消息存储支持 docs: 更新API控制器基类文档 chore: 删除无用脚本文件 fix: 修复分类模型返回类型问题 feat: 添加回执登录功能 build: 更新依赖项配置 style: 统一HTML模板中的哈希ID引用格式 ci: 添加部署和检查脚本
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\controller\Api;
|
||||
use app\common\library\Receipt;
|
||||
use app\common\library\Ems;
|
||||
use app\common\library\Sms;
|
||||
use fast\Random;
|
||||
@@ -12,24 +13,26 @@ use think\Validate;
|
||||
/**
|
||||
* 用户安全接口
|
||||
* @time 2026-04-29
|
||||
* @description 用户安全相关API,含注册/登录/改密/改邮箱/重置密码等
|
||||
* @lastUpdate v7.7.0 从User控制器拆分,引入邮箱验证机制
|
||||
* @name UserSecurity
|
||||
* @description 用户安全相关API,含注册/登录/改密/改邮箱/重置密码/回执登录等
|
||||
* @lastUpdate v8.0.0 去除邮箱验证码依赖,引入HMAC-SHA256回执验证机制,新增receiptLogin
|
||||
*/
|
||||
class UserSecurity extends Api
|
||||
{
|
||||
protected $noNeedLogin = ['login', 'mobilelogin', 'register', 'resetpwd', 'tokenLogin', 'sendEms', 'checkEms'];
|
||||
protected $noNeedLogin = ['login', 'mobilelogin', 'register', 'resetpwd', 'tokenLogin', 'receiptLogin', 'sendEms', 'checkEms'];
|
||||
protected $noNeedRight = '*';
|
||||
|
||||
private static $rateLimitKey = 'api_rate_limit:';
|
||||
private static $rateLimits = [
|
||||
'login' => ['max' => 20, 'window' => 300],
|
||||
'register' => ['max' => 10, 'window' => 3600],
|
||||
'resetpwd' => ['max' => 5, 'window' => 3600],
|
||||
'changepwd' => ['max' => 10, 'window' => 3600],
|
||||
'changeemail' => ['max' => 10, 'window' => 3600],
|
||||
'changemobile'=> ['max' => 10, 'window' => 3600],
|
||||
'sendEms' => ['max' => 10, 'window' => 300],
|
||||
'tokenLogin' => ['max' => 30, 'window' => 300],
|
||||
'login' => ['max' => 50, 'window' => 300],
|
||||
'register' => ['max' => 30, 'window' => 3600],
|
||||
'resetpwd' => ['max' => 20, 'window' => 3600],
|
||||
'changepwd' => ['max' => 30, 'window' => 3600],
|
||||
'changeemail' => ['max' => 30, 'window' => 3600],
|
||||
'changemobile' => ['max' => 30, 'window' => 3600],
|
||||
'sendEms' => ['max' => 30, 'window' => 300],
|
||||
'tokenLogin' => ['max' => 80, 'window' => 300],
|
||||
'receiptLogin' => ['max' => 50, 'window' => 300],
|
||||
];
|
||||
|
||||
private static $testMode = false;
|
||||
@@ -115,29 +118,20 @@ class UserSecurity extends Api
|
||||
return $user && isset($user['is_test']) && $user['is_test'] == 1;
|
||||
}
|
||||
|
||||
private function isTestEmail($email)
|
||||
private function verifyReceipt($action, $payloadStr)
|
||||
{
|
||||
if (!self::$testMode) {
|
||||
return false;
|
||||
$receipt = $this->request->post('receipt', '', 'trim');
|
||||
$sig = $this->request->post('sig', '', 'trim');
|
||||
$result = Receipt::verify($receipt, $sig, $action, $payloadStr);
|
||||
if (!$result['valid']) {
|
||||
$this->error($result['msg']);
|
||||
}
|
||||
$testEmails = Config::get('fastadmin.test_emails') ?: [];
|
||||
if (is_string($testEmails)) {
|
||||
$testEmails = explode(',', $testEmails);
|
||||
}
|
||||
return in_array($email, $testEmails);
|
||||
}
|
||||
|
||||
private function verifyEmsCode($email, $code, $event)
|
||||
{
|
||||
if (self::$testMode && $code === '888888') {
|
||||
return true;
|
||||
}
|
||||
return Ems::check($email, $code, $event);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 账号密码登录
|
||||
* @desc 支持用户名/邮箱+密码登录
|
||||
* @desc 支持用户名/邮箱+密码登录,无需回执
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
@@ -159,6 +153,54 @@ class UserSecurity extends Api
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 回执登录
|
||||
* @desc 客户端验证邮箱/手机后,用回执替代密码登录
|
||||
* @since v8.0.0
|
||||
*/
|
||||
public function receiptLogin()
|
||||
{
|
||||
$this->checkRateLimit('receiptLogin');
|
||||
$account = $this->request->post('account', '', 'trim');
|
||||
$receipt = $this->request->post('receipt', '', 'trim');
|
||||
$sig = $this->request->post('sig', '', 'trim');
|
||||
|
||||
if (!$account || !$receipt || !$sig) {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
$this->validateLength($account, '账号', 2, 100);
|
||||
$this->detectMaliciousInput($account);
|
||||
|
||||
$result = Receipt::verifyFlexible($receipt, $sig, 'receipt_login', $account);
|
||||
if (!$result['valid']) {
|
||||
$this->error($result['msg']);
|
||||
}
|
||||
|
||||
$user = null;
|
||||
if (Validate::is($account, "email")) {
|
||||
$user = \app\common\model\User::getByEmail($account);
|
||||
} elseif (Validate::regex($account, "^1\d{10}$")) {
|
||||
$user = \app\common\model\User::getByMobile($account);
|
||||
} else {
|
||||
$user = \app\common\model\User::getByUsername($account);
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
$this->error(__('User not found'));
|
||||
}
|
||||
if ($user->status != 'normal') {
|
||||
$this->error(__('Account is locked'));
|
||||
}
|
||||
|
||||
$ret = $this->auth->direct($user->id);
|
||||
if ($ret) {
|
||||
$data = ['userinfo' => $this->auth->getUserinfo()];
|
||||
$this->success(__('Logged in successful'), $data);
|
||||
} else {
|
||||
$this->error($this->auth->getError());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 手机号登录
|
||||
* @desc 手机号+短信验证码登录,未注册自动注册
|
||||
@@ -233,7 +275,7 @@ class UserSecurity extends Api
|
||||
|
||||
/**
|
||||
* @name 用户注册
|
||||
* @desc 注册新用户,邮箱必填且需邮箱验证码
|
||||
* @desc 注册新用户,需回执验证(客户端已验证邮箱),不再需要邮箱验证码
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
@@ -241,7 +283,6 @@ class UserSecurity extends Api
|
||||
$username = $this->request->post('username', '', 'trim');
|
||||
$password = $this->request->post('password', '', 'trim');
|
||||
$email = $this->request->post('email', '', 'trim');
|
||||
$emailCode = $this->request->post('email_code', '', 'trim');
|
||||
$mobile = $this->request->post('mobile', '', 'trim');
|
||||
$mobileCode = $this->request->post('mobile_code', '', 'trim');
|
||||
|
||||
@@ -263,13 +304,7 @@ class UserSecurity extends Api
|
||||
$this->error('该邮箱已被注册');
|
||||
}
|
||||
|
||||
if (!$emailCode) {
|
||||
$this->error('邮箱验证码为必填项');
|
||||
}
|
||||
$this->validateLength($emailCode, '邮箱验证码', 4, 6);
|
||||
if (!$this->verifyEmsCode($email, $emailCode, 'register')) {
|
||||
$this->error('邮箱验证码不正确');
|
||||
}
|
||||
$this->verifyReceipt('register', $email);
|
||||
|
||||
if ($mobile) {
|
||||
if (!Validate::regex($mobile, "^1\d{10}$")) {
|
||||
@@ -284,7 +319,6 @@ class UserSecurity extends Api
|
||||
|
||||
$ret = $this->auth->register($username, $password, $email, $mobile, []);
|
||||
if ($ret) {
|
||||
Ems::flush($email, 'register');
|
||||
$userId = $this->auth->id;
|
||||
$verification = db('user')->where('id', $userId)->value('verification');
|
||||
$verification = $verification ? json_decode($verification, true) : [];
|
||||
@@ -319,7 +353,7 @@ class UserSecurity extends Api
|
||||
|
||||
/**
|
||||
* @name 修改密码
|
||||
* @desc 修改密码需验证旧密码+邮箱验证码(双重验证)
|
||||
* @desc 修改密码需验证旧密码+回执验证(客户端已验证邮箱)
|
||||
*/
|
||||
public function changepwd()
|
||||
{
|
||||
@@ -327,7 +361,6 @@ class UserSecurity extends Api
|
||||
$user = $this->auth->getUser();
|
||||
$oldpassword = $this->request->post('oldpassword', '', 'trim');
|
||||
$newpassword = $this->request->post('newpassword', '', 'trim');
|
||||
$emailCode = $this->request->post('email_code', '', 'trim');
|
||||
|
||||
if (!$oldpassword || !$newpassword) {
|
||||
$this->error(__('Invalid parameters'));
|
||||
@@ -335,32 +368,16 @@ class UserSecurity extends Api
|
||||
$this->validateLength($oldpassword, '旧密码', 6, 30);
|
||||
$this->validateLength($newpassword, '新密码', 6, 30);
|
||||
|
||||
if ($this->isTestUser($user->id)) {
|
||||
$ret = $this->auth->changepwd($newpassword, $oldpassword);
|
||||
if ($ret) {
|
||||
$this->success(__('Change password successful'));
|
||||
} else {
|
||||
$this->error($this->auth->getError());
|
||||
if (!$this->isTestUser($user->id)) {
|
||||
$email = $user->email;
|
||||
if (!$email) {
|
||||
$this->error('请先绑定邮箱后再修改密码');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$email = $user->email;
|
||||
if (!$email) {
|
||||
$this->error('请先绑定邮箱后再修改密码');
|
||||
}
|
||||
|
||||
if (!$emailCode) {
|
||||
$this->error('邮箱验证码为必填项');
|
||||
}
|
||||
$this->validateLength($emailCode, '邮箱验证码', 4, 6);
|
||||
if (!$this->verifyEmsCode($email, $emailCode, 'changepwd')) {
|
||||
$this->error('邮箱验证码不正确');
|
||||
$this->verifyReceipt('changepwd', $user->username);
|
||||
}
|
||||
|
||||
$ret = $this->auth->changepwd($newpassword, $oldpassword);
|
||||
if ($ret) {
|
||||
Ems::flush($email, 'changepwd');
|
||||
$this->success(__('Change password successful'));
|
||||
} else {
|
||||
$this->error($this->auth->getError());
|
||||
@@ -369,7 +386,7 @@ class UserSecurity extends Api
|
||||
|
||||
/**
|
||||
* @name 重置密码
|
||||
* @desc 通过邮箱/手机验证码重置密码(无需登录)
|
||||
* @desc 通过回执验证重置密码(无需登录),客户端已验证邮箱/手机
|
||||
*/
|
||||
public function resetpwd()
|
||||
{
|
||||
@@ -378,13 +395,11 @@ class UserSecurity extends Api
|
||||
$mobile = $this->request->post("mobile", '', 'trim');
|
||||
$email = $this->request->post("email", '', 'trim');
|
||||
$newpassword = $this->request->post("newpassword", '', 'trim');
|
||||
$captcha = $this->request->post("captcha", '', 'trim');
|
||||
|
||||
if (!$newpassword || !$captcha) {
|
||||
if (!$newpassword) {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
$this->validateLength($newpassword, '新密码', 6, 30);
|
||||
$this->validateLength($captcha, '验证码', 4, 6);
|
||||
|
||||
if ($type == 'mobile') {
|
||||
if (!Validate::regex($mobile, "^1\d{10}$")) {
|
||||
@@ -394,10 +409,7 @@ class UserSecurity extends Api
|
||||
if (!$user) {
|
||||
$this->error(__('User not found'));
|
||||
}
|
||||
if (!Sms::check($mobile, $captcha, 'resetpwd')) {
|
||||
$this->error(__('Captcha is incorrect'));
|
||||
}
|
||||
Sms::flush($mobile, 'resetpwd');
|
||||
$this->verifyReceipt('resetpwd', $mobile);
|
||||
} else {
|
||||
if (!Validate::is($email, "email")) {
|
||||
$this->error(__('Email is incorrect'));
|
||||
@@ -406,10 +418,7 @@ class UserSecurity extends Api
|
||||
if (!$user) {
|
||||
$this->error(__('User not found'));
|
||||
}
|
||||
if (!$this->verifyEmsCode($email, $captcha, 'resetpwd')) {
|
||||
$this->error('邮箱验证码不正确');
|
||||
}
|
||||
Ems::flush($email, 'resetpwd');
|
||||
$this->verifyReceipt('resetpwd', $email);
|
||||
}
|
||||
|
||||
$this->auth->direct($user->id);
|
||||
@@ -423,90 +432,70 @@ class UserSecurity extends Api
|
||||
|
||||
/**
|
||||
* @name 修改邮箱
|
||||
* @desc 修改邮箱需新邮箱验证码验证
|
||||
* @desc 修改邮箱需回执验证(客户端已验证新邮箱)
|
||||
*/
|
||||
public function changeemail()
|
||||
{
|
||||
$this->checkRateLimit('changeemail');
|
||||
$user = $this->auth->getUser();
|
||||
$email = $this->request->post('email', '', 'trim');
|
||||
$captcha = $this->request->post('captcha', '', 'trim');
|
||||
|
||||
if (!$email || !$captcha) {
|
||||
if (!$email) {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
if (!Validate::is($email, "email")) {
|
||||
$this->error(__('Email is incorrect'));
|
||||
}
|
||||
$this->validateLength($email, '邮箱', 5, 100);
|
||||
$this->validateLength($captcha, '验证码', 4, 6);
|
||||
|
||||
if (\app\common\model\User::where('email', $email)->where('id', '<>', $user->id)->find()) {
|
||||
$this->error(__('Email already exists'));
|
||||
}
|
||||
|
||||
if (!$this->verifyEmsCode($email, $captcha, 'changeemail')) {
|
||||
$this->error(__('Captcha is incorrect'));
|
||||
}
|
||||
$this->verifyReceipt('changeemail', $email);
|
||||
|
||||
$verification = $user->verification;
|
||||
$verification->email = 1;
|
||||
$user->verification = $verification;
|
||||
$user->email = $email;
|
||||
$user->save();
|
||||
Ems::flush($email, 'changeemail');
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 修改手机号
|
||||
* @desc 修改手机号需短信验证码验证
|
||||
* @desc 修改手机号需回执验证(客户端已验证新手机号)
|
||||
*/
|
||||
public function changemobile()
|
||||
{
|
||||
$this->checkRateLimit('changemobile');
|
||||
$user = $this->auth->getUser();
|
||||
$mobile = $this->request->post('mobile', '', 'trim');
|
||||
$captcha = $this->request->post('captcha', '', 'trim');
|
||||
|
||||
if (!$mobile || !$captcha) {
|
||||
if (!$mobile) {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
if (!Validate::regex($mobile, "^1\d{10}$")) {
|
||||
$this->error(__('Mobile is incorrect'));
|
||||
}
|
||||
$this->validateLength($captcha, '验证码', 4, 6);
|
||||
|
||||
if (\app\common\model\User::where('mobile', $mobile)->where('id', '<>', $user->id)->find()) {
|
||||
$this->error(__('Mobile already exists'));
|
||||
}
|
||||
|
||||
if (self::$testMode && $captcha === '888888') {
|
||||
$result = true;
|
||||
} else {
|
||||
$result = false;
|
||||
try {
|
||||
$result = Sms::check($mobile, $captcha, 'changemobile');
|
||||
} catch (\Exception $e) {
|
||||
$this->error('短信验证服务暂不可用');
|
||||
}
|
||||
}
|
||||
if (!$result) {
|
||||
$this->error(__('Captcha is incorrect'));
|
||||
}
|
||||
$this->verifyReceipt('changemobile', $mobile);
|
||||
|
||||
$verification = $user->verification;
|
||||
$verification->mobile = 1;
|
||||
$user->verification = $verification;
|
||||
$user->mobile = $mobile;
|
||||
$user->save();
|
||||
try { Sms::flush($mobile, 'changemobile'); } catch (\Exception $e) {}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 发送邮箱验证码
|
||||
* @desc 发送邮箱验证码,支持多种事件类型
|
||||
* @name 发送邮箱验证码(保留兼容)
|
||||
* @desc 保留接口兼容旧客户端,新客户端建议使用回执验证
|
||||
*/
|
||||
public function sendEms()
|
||||
{
|
||||
@@ -549,8 +538,8 @@ class UserSecurity extends Api
|
||||
}
|
||||
|
||||
/**
|
||||
* @name 校验邮箱验证码
|
||||
* @desc 校验邮箱验证码是否正确
|
||||
* @name 校验邮箱验证码(保留兼容)
|
||||
* @desc 保留接口兼容旧客户端
|
||||
*/
|
||||
public function checkEms()
|
||||
{
|
||||
@@ -562,7 +551,11 @@ class UserSecurity extends Api
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
|
||||
$ret = $this->verifyEmsCode($email, $captcha, $event);
|
||||
if (self::$testMode && $captcha === '888888') {
|
||||
$this->success(__('验证码正确'));
|
||||
}
|
||||
|
||||
$ret = Ems::check($email, $captcha, $event);
|
||||
if ($ret) {
|
||||
$this->success(__('验证码正确'));
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user