263 lines
8.9 KiB
PHP
Executable File
263 lines
8.9 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Models\Config;
|
|
use App\Models\User;
|
|
use App\Services\Cache;
|
|
use App\Utils\ResponseHelper;
|
|
use App\Utils\Tools;
|
|
use Exception;
|
|
use GuzzleHttp\Client;
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
use Lcobucci\JWT\Encoding\JoseEncoder;
|
|
use Lcobucci\JWT\Token\Parser;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use RedisException;
|
|
use Slim\Http\Response;
|
|
use Slim\Http\ServerRequest;
|
|
use Smarty\Exception as SmartyException;
|
|
use function hash;
|
|
use function hash_hmac;
|
|
use function implode;
|
|
use function json_decode;
|
|
use function strcmp;
|
|
use function time;
|
|
|
|
final class OAuthController extends BaseController
|
|
{
|
|
private static string $err_msg = 'OAuth 请求失败';
|
|
|
|
/**
|
|
* @throws SmartyException
|
|
* @throws GuzzleException
|
|
* @throws RedisException
|
|
*/
|
|
public function index(ServerRequest $request, Response $response, array $args): ResponseInterface
|
|
{
|
|
return match ($args['type']) {
|
|
'slack' => $this->slack($request, $response, $args),
|
|
'discord' => $this->discord($request, $response, $args),
|
|
'telegram' => $this->telegram($request, $response, $args),
|
|
default => $response->withStatus(404)->write($this->view()->fetch('404.tpl')),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @throws GuzzleException
|
|
* @throws RedisException
|
|
* @throws Exception
|
|
*/
|
|
public function slack(ServerRequest $request, Response $response, array $args): ResponseInterface
|
|
{
|
|
$user = $this->user;
|
|
$redis = (new Cache())->initRedis();
|
|
|
|
if ($request->getParam('code') === null) {
|
|
$state = Tools::genRandomChar(16);
|
|
$redis->setex('slack_state:' . $user->id, 300, $state);
|
|
$client_id = Config::obtain('slack_client_id');
|
|
$team_id = Config::obtain('slack_team_id');
|
|
$redirect_uri = $_ENV['baseUrl'] . '/oauth/slack';
|
|
|
|
return $response->withJson([
|
|
'ret' => 1,
|
|
'redir' => 'https://slack.com/openid/connect/authorize?response_type=code&scope=openid profile&client_id='
|
|
. $client_id . '&state=' . $state . '&team=' . $team_id .
|
|
'&nonce=' . $state . '&redirect_uri=' . $redirect_uri,
|
|
]);
|
|
}
|
|
|
|
$code = $request->getParam('code');
|
|
$state = $request->getParam('state');
|
|
|
|
if ($state !== $redis->get('slack_state:' . $user->id)) {
|
|
return ResponseHelper::error($response, self::$err_msg);
|
|
}
|
|
|
|
$client = new Client();
|
|
$slack_api_url = 'https://slack.com/api/openid.connect.token';
|
|
|
|
$code_headers = [
|
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
];
|
|
|
|
$code_body = [
|
|
'client_id' => Config::obtain('slack_client_id'),
|
|
'client_secret' => Config::obtain('slack_client_secret'),
|
|
'grant_type' => 'authorization_code',
|
|
'code' => $code,
|
|
'redirect_uri' => $_ENV['baseUrl'] . '/oauth/slack',
|
|
];
|
|
|
|
$code_response = $client->post($slack_api_url, [
|
|
'headers' => $code_headers,
|
|
'form_params' => $code_body,
|
|
'timeout' => 3,
|
|
]);
|
|
|
|
if (! json_decode($code_response->getBody()->__toString())->ok) {
|
|
return ResponseHelper::error($response, self::$err_msg);
|
|
}
|
|
|
|
$parser = new Parser(new JoseEncoder());
|
|
$id_token = $parser->parse(json_decode($code_response->getBody()->__toString())->id_token);
|
|
|
|
$slack_user_id = $id_token->claims()->get('https://slack.com/user_id');
|
|
|
|
if ((new User())->where('im_type', 1)->where('im_value', $slack_user_id)->first() !== null ||
|
|
($user->im_type === 1 && $user->im_value === $slack_user_id)) {
|
|
return ResponseHelper::error($response, 'Slack 账户已绑定');
|
|
}
|
|
|
|
$user->im_type = 1;
|
|
$user->im_value = $slack_user_id;
|
|
$user->save();
|
|
|
|
return $response->withRedirect($_ENV['baseUrl'] . '/user/edit');
|
|
}
|
|
|
|
/**
|
|
* @throws GuzzleException
|
|
* @throws RedisException
|
|
*/
|
|
public function discord(ServerRequest $request, Response $response, array $args): ResponseInterface
|
|
{
|
|
$user = $this->user;
|
|
$redis = (new Cache())->initRedis();
|
|
|
|
if ($request->getParam('code') === null) {
|
|
$state = Tools::genRandomChar(16);
|
|
$redis->setex('discord_state:' . $user->id, 300, $state);
|
|
$client_id = Config::obtain('discord_client_id');
|
|
$redirect_uri = $_ENV['baseUrl'] . '/oauth/discord';
|
|
|
|
return $response->withJson([
|
|
'ret' => 1,
|
|
'redir' => 'https://discord.com/api/oauth2/authorize?client_id=' . $client_id .
|
|
'&redirect_uri=' . $redirect_uri .
|
|
'&response_type=code&scope=guilds.join identify&state=' . $state,
|
|
]);
|
|
}
|
|
|
|
$code = $request->getParam('code');
|
|
$state = $request->getParam('state');
|
|
|
|
if ($state !== $redis->get('discord_state:' . $user->id)) {
|
|
return ResponseHelper::error($response, self::$err_msg);
|
|
}
|
|
|
|
$client = new Client();
|
|
$discord_api_url = 'https://discord.com/api/oauth2/token';
|
|
|
|
$code_headers = [
|
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
];
|
|
|
|
$code_body = [
|
|
'client_id' => Config::obtain('discord_client_id'),
|
|
'client_secret' => Config::obtain('discord_client_secret'),
|
|
'grant_type' => 'authorization_code',
|
|
'code' => $code,
|
|
'redirect_uri' => $_ENV['baseUrl'] . '/oauth/discord',
|
|
];
|
|
|
|
$code_response = $client->post($discord_api_url, [
|
|
'headers' => $code_headers,
|
|
'form_params' => $code_body,
|
|
'timeout' => 3,
|
|
]);
|
|
|
|
if ($code_response->getStatusCode() !== 200) {
|
|
return ResponseHelper::error($response, self::$err_msg);
|
|
}
|
|
|
|
$access_token = json_decode($code_response->getBody()->getContents())->access_token;
|
|
$discord_user_url = 'https://discord.com/api/users/@me';
|
|
|
|
$user_headers = [
|
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
'Authorization' => 'Bearer ' . $access_token,
|
|
];
|
|
|
|
$user_response = $client->get($discord_user_url, [
|
|
'headers' => $user_headers,
|
|
]);
|
|
|
|
if ($user_response->getStatusCode() !== 200) {
|
|
return ResponseHelper::error($response, self::$err_msg);
|
|
}
|
|
|
|
$discord_user_id = json_decode($user_response->getBody()->getContents(), true)['id'];
|
|
|
|
if ((new User())->where('im_type', 2)->where('im_value', $discord_user_id)->first() !== null ||
|
|
($user->im_type === 2 && $user->im_value === $discord_user_id)) {
|
|
return ResponseHelper::error($response, 'Discord 账户已绑定');
|
|
}
|
|
|
|
$user->im_type = 2;
|
|
$user->im_value = $discord_user_id;
|
|
$user->save();
|
|
|
|
if (Config::obtain('discord_guild_id') !== 0) {
|
|
$discord_guild_url = 'https://discord.com/api/guilds/' . Config::obtain('discord_guild_id') .
|
|
'/members/' . $user->im_value;
|
|
|
|
$guild_headers = [
|
|
'Content-Type' => 'application/json',
|
|
'Authorization' => 'Bot ' . Config::obtain('discord_bot_token'),
|
|
];
|
|
|
|
$guild_body = [
|
|
'access_token' => $access_token,
|
|
];
|
|
|
|
$client->put($discord_guild_url, [
|
|
'headers' => $guild_headers,
|
|
'json' => $guild_body,
|
|
]);
|
|
}
|
|
|
|
return $response->withRedirect($_ENV['baseUrl'] . '/user/edit');
|
|
}
|
|
|
|
public function telegram(ServerRequest $request, Response $response, array $args): ResponseInterface
|
|
{
|
|
$user_auth = json_decode($request->getParam('user'), true);
|
|
|
|
$check_hash = $user_auth['hash'];
|
|
unset($user_auth['hash']);
|
|
$data_check_arr = [];
|
|
|
|
foreach ($user_auth as $key => $value) {
|
|
$data_check_arr[] = $key . '=' . $value;
|
|
}
|
|
|
|
sort($data_check_arr);
|
|
$data_check_string = implode("\n", $data_check_arr);
|
|
$secret_key = hash('sha256', Config::obtain('telegram_token'), true);
|
|
$hash = hash_hmac('sha256', $data_check_string, $secret_key);
|
|
|
|
if (strcmp($hash, $check_hash) !== 0 || (time() - $user_auth['auth_date']) > 86400) {
|
|
return ResponseHelper::error($response, self::$err_msg);
|
|
}
|
|
|
|
$telegram_id = $this->antiXss->xss_clean($user_auth['id']);
|
|
$user = $this->user;
|
|
|
|
if ((new User())->where('im_type', 4)->where('im_value', $telegram_id)->first() !== null ||
|
|
($user->im_type === 4 && $user->im_value === $telegram_id)) {
|
|
return ResponseHelper::error($response, 'Telegram 账户已绑定');
|
|
}
|
|
|
|
$user->im_type = 4;
|
|
$user->im_value = $telegram_id;
|
|
|
|
$user->save();
|
|
|
|
return ResponseHelper::success($response, '绑定成功');
|
|
}
|
|
}
|