<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types=1);

namespace app\common\middleware;

use app\common\model\Config;
use Closure;
use think\facade\Cache;
class ApiAuth
{
    protected $apps;
    public function handle($request, Closure $next)
    {
        $this->apps = Config::whereIn('name', ['app_id', 'app_secret'])->where('group', 'basics')->column('value', 'name');
        $params = $request->param();
        $appKey = $request->header('X-Api-AppKey');
        $timestamp = $request->header('X-Api-Timestamp');
        $nonce = $request->header('X-Api-Nonce');
        $signature = $request->header('X-Api-Signature');

        // 1. 基础检查
        if (!$signature || !$appKey || !$timestamp || !$nonce) {
            return response('缺少必要的签名参数', 401);
        }

        // 2. 检查 AppKey 是否合法
        if ($this->apps['app_id'] != $appKey) {
            return response('无效的 AppKey', 401);
        }

        // 3. 检查时间戳（防重放攻击，允许 60 秒误差）
        if (abs(time() - $timestamp) > 60) {
            return response('请求已超时', 401);
        }

        // 4. 检查随机字符串
        $cacheKey = "api_nonce:" . $nonce;
        if (Cache::has($cacheKey)) {
            return response('重复请求', 401);
        }
        Cache::setex($cacheKey, 60, 1);

        // 5. 验证签名
        if (!$this->checkSignature($params, $timestamp, $nonce, $signature)) {
            return response('签名验证失败', 401);
        }

        return $next($request);
    }

    protected function checkSignature($params, $timestamp, $nonce, $signature)
    {
        $appSecret = $this->apps['app_secret'];
        unset($params['/']);
        ksort($params);
        $stringToBeSigned = http_build_query($params) . '&app_secret=' . $appSecret . '&timestamp=' . $timestamp . '&nonce=' . $nonce;
        $serverSig = hash('sha256', $stringToBeSigned);
        return hash_equals($serverSig, $signature);
    }
}
