mirror of
https://github.com/v2board/v2board.git
synced 2024-11-10 09:39:10 +08:00
commit
6b235e592d
@ -42,6 +42,9 @@ class ResetUser extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->confirm("确定要重置所有用户安全信息吗?")) {
|
||||
return;
|
||||
}
|
||||
ini_set('memory_limit', -1);
|
||||
$users = User::all();
|
||||
foreach ($users as $user)
|
||||
|
@ -48,8 +48,8 @@ class V2boardInstall extends Command
|
||||
$this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | ");
|
||||
$this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| ");
|
||||
if (\File::exists(base_path() . '/.env')) {
|
||||
$defaultSecurePath = hash('crc32b', config('app.key'));
|
||||
$this->info("访问 http(s)://你的站点/{$defaultSecurePath} 进入管理面板,你可以在用户中心修改你的密码。");
|
||||
$securePath = config('v2board.secure_path', config('v2board.frontend_admin_path', hash('crc32b', config('app.key'))));
|
||||
$this->info("访问 http(s)://你的站点/{$securePath} 进入管理面板,你可以在用户中心修改你的密码。");
|
||||
abort(500, '如需重新安装请删除目录下.env文件');
|
||||
}
|
||||
|
||||
|
@ -57,8 +57,7 @@ class V2boardUpdate extends Command
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
$this->info('更新完毕,请重新启动队列服务。');
|
||||
\Artisan::call('cache:clear');
|
||||
\Artisan::call('config:cache');
|
||||
\Artisan::call('horizon:terminate');
|
||||
$this->info('更新完毕,队列服务已重启,你无需进行任何操作。');
|
||||
}
|
||||
}
|
||||
|
@ -87,28 +87,16 @@ class ConfigController extends Controller
|
||||
'site' => [
|
||||
'logo' => config('v2board.logo'),
|
||||
'force_https' => (int)config('v2board.force_https', 0),
|
||||
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
|
||||
'stop_register' => (int)config('v2board.stop_register', 0),
|
||||
'email_verify' => (int)config('v2board.email_verify', 0),
|
||||
'app_name' => config('v2board.app_name', 'V2Board'),
|
||||
'app_description' => config('v2board.app_description', 'V2Board is best!'),
|
||||
'app_url' => config('v2board.app_url'),
|
||||
'subscribe_url' => config('v2board.subscribe_url'),
|
||||
'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0),
|
||||
'try_out_hour' => (int)config('v2board.try_out_hour', 1),
|
||||
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
|
||||
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
|
||||
'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0),
|
||||
'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0),
|
||||
'recaptcha_key' => config('v2board.recaptcha_key'),
|
||||
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
|
||||
'tos_url' => config('v2board.tos_url'),
|
||||
'currency' => config('v2board.currency', 'CNY'),
|
||||
'currency_symbol' => config('v2board.currency_symbol', '¥'),
|
||||
'register_limit_by_ip_enable' => (int)config('v2board.register_limit_by_ip_enable', 0),
|
||||
'register_limit_count' => config('v2board.register_limit_count', 3),
|
||||
'register_limit_expire' => config('v2board.register_limit_expire', 60),
|
||||
'secure_path' => config('v2board.secure_path', config('v2board.frontend_admin_path', hash('crc32b', config('app.key'))))
|
||||
],
|
||||
'subscribe' => [
|
||||
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
|
||||
@ -152,6 +140,23 @@ class ConfigController extends Controller
|
||||
'macos_download_url' => config('v2board.macos_download_url'),
|
||||
'android_version' => config('v2board.android_version'),
|
||||
'android_download_url' => config('v2board.android_download_url')
|
||||
],
|
||||
'safe' => [
|
||||
'email_verify' => (int)config('v2board.email_verify', 0),
|
||||
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
|
||||
'secure_path' => config('v2board.secure_path', config('v2board.frontend_admin_path', hash('crc32b', config('app.key')))),
|
||||
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
|
||||
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
|
||||
'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0),
|
||||
'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0),
|
||||
'recaptcha_key' => config('v2board.recaptcha_key'),
|
||||
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
|
||||
'register_limit_by_ip_enable' => (int)config('v2board.register_limit_by_ip_enable', 0),
|
||||
'register_limit_count' => config('v2board.register_limit_count', 3),
|
||||
'register_limit_expire' => config('v2board.register_limit_expire', 60),
|
||||
'password_limit_enable' => (int)config('v2board.password_limit_enable', 1),
|
||||
'password_limit_count' => config('v2board.password_limit_count', 5),
|
||||
'password_limit_expire' => config('v2board.password_limit_expire', 60)
|
||||
]
|
||||
];
|
||||
if ($key && isset($data[$key])) {
|
||||
|
@ -15,6 +15,12 @@ class RouteController extends Controller
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
$routes = ServerRoute::get();
|
||||
// TODO: remove on 1.8.0
|
||||
foreach ($routes as $k => $route) {
|
||||
$array = json_decode($route->match, true);
|
||||
if (is_array($array)) $routes[$k]['match'] = $array;
|
||||
}
|
||||
// TODO: remove on 1.8.0
|
||||
return [
|
||||
'data' => $routes
|
||||
];
|
||||
@ -24,10 +30,19 @@ class RouteController extends Controller
|
||||
{
|
||||
$params = $request->validate([
|
||||
'remarks' => 'required',
|
||||
'match' => 'required',
|
||||
'action' => 'required',
|
||||
'match' => 'required|array',
|
||||
'action' => 'required|in:block,dns',
|
||||
'action_value' => 'nullable'
|
||||
], [
|
||||
'remarks.required' => '备注不能为空',
|
||||
'match.required' => '匹配值不能为空',
|
||||
'action.required' => '动作类型不能为空',
|
||||
'action.in' => '动作类型参数有误'
|
||||
]);
|
||||
$params['match'] = array_filter($params['match']);
|
||||
// TODO: remove on 1.8.0
|
||||
$params['match'] = json_encode($params['match']);
|
||||
// TODO: remove on 1.8.0
|
||||
if ($request->input('id')) {
|
||||
try {
|
||||
$route = ServerRoute::find($request->input('id'));
|
||||
|
@ -25,9 +25,9 @@ class ThemeController extends Controller
|
||||
{
|
||||
$themeConfigs = [];
|
||||
foreach ($this->themes as $theme) {
|
||||
$themeConfigFile = $this->path . "{$theme}/config.php";
|
||||
$themeConfigFile = $this->path . "{$theme}/config.json";
|
||||
if (!File::exists($themeConfigFile)) continue;
|
||||
$themeConfig = include($themeConfigFile);
|
||||
$themeConfig = json_decode(File::get($themeConfigFile), true);
|
||||
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) continue;
|
||||
$themeConfigs[$theme] = $themeConfig;
|
||||
if (config("theme.{$theme}")) continue;
|
||||
@ -60,9 +60,10 @@ class ThemeController extends Controller
|
||||
]);
|
||||
$payload['config'] = json_decode(base64_decode($payload['config']), true);
|
||||
if (!$payload['config'] || !is_array($payload['config'])) abort(500, '参数有误');
|
||||
$themeConfigFile = public_path("theme/{$payload['name']}/config.php");
|
||||
$themeConfigFile = public_path("theme/{$payload['name']}/config.json");
|
||||
if (!File::exists($themeConfigFile)) abort(500, '主题不存在');
|
||||
$themeConfig = include($themeConfigFile);
|
||||
$themeConfig = json_decode(File::get($themeConfigFile), true);
|
||||
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) abort(500, '主题配置文件有误');
|
||||
$validateFields = array_column($themeConfig['configs'], 'field_name');
|
||||
$config = [];
|
||||
foreach ($validateFields as $validateField) {
|
||||
|
176
app/Http/Controllers/Client/Protocols/ClashMeta.php
Normal file
176
app/Http/Controllers/Client/Protocols/ClashMeta.php
Normal file
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Client\Protocols;
|
||||
|
||||
use App\Utils\Helper;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class ClashMeta
|
||||
{
|
||||
public $flag = 'clashmeta';
|
||||
private $servers;
|
||||
private $user;
|
||||
|
||||
public function __construct($user, $servers)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->servers = $servers;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$servers = $this->servers;
|
||||
$user = $this->user;
|
||||
$appName = config('v2board.app_name', 'V2Board');
|
||||
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
|
||||
header('profile-update-interval: 24');
|
||||
header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($appName));
|
||||
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
|
||||
$customConfig = base_path() . '/resources/rules/custom.clash.yaml';
|
||||
if (\File::exists($customConfig)) {
|
||||
$config = Yaml::parseFile($customConfig);
|
||||
} else {
|
||||
$config = Yaml::parseFile($defaultConfig);
|
||||
}
|
||||
$proxy = [];
|
||||
$proxies = [];
|
||||
|
||||
foreach ($servers as $item) {
|
||||
if ($item['type'] === 'shadowsocks') {
|
||||
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === 'v2ray') {
|
||||
array_push($proxy, self::buildVmess($user['uuid'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
if ($item['type'] === 'trojan') {
|
||||
array_push($proxy, self::buildTrojan($user['uuid'], $item));
|
||||
array_push($proxies, $item['name']);
|
||||
}
|
||||
}
|
||||
|
||||
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
|
||||
foreach ($config['proxy-groups'] as $k => $v) {
|
||||
if (!is_array($config['proxy-groups'][$k]['proxies'])) $config['proxy-groups'][$k]['proxies'] = [];
|
||||
$isFilter = false;
|
||||
foreach ($config['proxy-groups'][$k]['proxies'] as $src) {
|
||||
foreach ($proxies as $dst) {
|
||||
if (!$this->isRegex($src)) continue;
|
||||
$isFilter = true;
|
||||
$config['proxy-groups'][$k]['proxies'] = array_values(array_diff($config['proxy-groups'][$k]['proxies'], [$src]));
|
||||
if ($this->isMatch($src, $dst)) {
|
||||
array_push($config['proxy-groups'][$k]['proxies'], $dst);
|
||||
}
|
||||
}
|
||||
if ($isFilter) continue;
|
||||
}
|
||||
if ($isFilter) continue;
|
||||
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
|
||||
}
|
||||
// Force the current subscription domain to be a direct rule
|
||||
$subsDomain = $_SERVER['SERVER_NAME'];
|
||||
$subsDomainRule = "DOMAIN,{$subsDomain},DIRECT";
|
||||
array_unshift($config['rules'], $subsDomainRule);
|
||||
|
||||
$yaml = Yaml::dump($config);
|
||||
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
|
||||
return $yaml;
|
||||
}
|
||||
|
||||
public static function buildShadowsocks($password, $server)
|
||||
{
|
||||
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
|
||||
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 16);
|
||||
$userKey = Helper::uuidToBase64($password, 16);
|
||||
$password = "{$serverKey}:{$userKey}";
|
||||
}
|
||||
if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
|
||||
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 32);
|
||||
$userKey = Helper::uuidToBase64($password, 32);
|
||||
$password = "{$serverKey}:{$userKey}";
|
||||
}
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'ss';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['cipher'] = $server['cipher'];
|
||||
$array['password'] = $password;
|
||||
$array['udp'] = true;
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildVmess($uuid, $server)
|
||||
{
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'vmess';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['uuid'] = $uuid;
|
||||
$array['alterId'] = 0;
|
||||
$array['cipher'] = 'auto';
|
||||
$array['udp'] = true;
|
||||
|
||||
if ($server['tls']) {
|
||||
$array['tls'] = true;
|
||||
if ($server['tlsSettings']) {
|
||||
$tlsSettings = $server['tlsSettings'];
|
||||
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
|
||||
$array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false);
|
||||
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
|
||||
$array['servername'] = $tlsSettings['serverName'];
|
||||
}
|
||||
}
|
||||
if ($server['network'] === 'ws') {
|
||||
$array['network'] = 'ws';
|
||||
if ($server['networkSettings']) {
|
||||
$wsSettings = $server['networkSettings'];
|
||||
$array['ws-opts'] = [];
|
||||
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
|
||||
$array['ws-opts']['path'] = $wsSettings['path'];
|
||||
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
||||
$array['ws-opts']['headers'] = ['Host' => $wsSettings['headers']['Host']];
|
||||
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
|
||||
$array['ws-path'] = $wsSettings['path'];
|
||||
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
|
||||
$array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']];
|
||||
}
|
||||
}
|
||||
if ($server['network'] === 'grpc') {
|
||||
$array['network'] = 'grpc';
|
||||
if ($server['networkSettings']) {
|
||||
$grpcSettings = $server['networkSettings'];
|
||||
$array['grpc-opts'] = [];
|
||||
if (isset($grpcSettings['serviceName'])) $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName'];
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function buildTrojan($password, $server)
|
||||
{
|
||||
$array = [];
|
||||
$array['name'] = $server['name'];
|
||||
$array['type'] = 'trojan';
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['password'] = $password;
|
||||
$array['udp'] = true;
|
||||
if (!empty($server['server_name'])) $array['sni'] = $server['server_name'];
|
||||
if (!empty($server['allow_insecure'])) $array['skip-cert-verify'] = ($server['allow_insecure'] ? true : false);
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function isMatch($exp, $str)
|
||||
{
|
||||
return @preg_match($exp, $str);
|
||||
}
|
||||
|
||||
private function isRegex($exp)
|
||||
{
|
||||
return @preg_match($exp, null) !== false;
|
||||
}
|
||||
}
|
@ -71,6 +71,15 @@ class Surfboard
|
||||
$config = str_replace('$subs_domain', $subsDomain, $config);
|
||||
$config = str_replace('$proxies', $proxies, $config);
|
||||
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
|
||||
|
||||
$upload = round($user['u'] / (1024*1024*1024), 2);
|
||||
$download = round($user['d'] / (1024*1024*1024), 2);
|
||||
$useTraffic = $upload + $download;
|
||||
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
|
||||
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
||||
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量:{$download}GB\\n剩余流量:{$useTraffic}GB\\n套餐流量:{$totalTraffic}GB\\n到期时间:{$expireDate}";
|
||||
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,15 @@ class Surge
|
||||
$config = str_replace('$subs_domain', $subsDomain, $config);
|
||||
$config = str_replace('$proxies', $proxies, $config);
|
||||
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
|
||||
|
||||
$upload = round($user['u'] / (1024*1024*1024), 2);
|
||||
$download = round($user['d'] / (1024*1024*1024), 2);
|
||||
$useTraffic = $upload + $download;
|
||||
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
|
||||
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
|
||||
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量:{$download}GB\\n剩余流量:{$useTraffic}GB\\n套餐流量:{$totalTraffic}GB\\n到期时间:{$expireDate}";
|
||||
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace App\Http\Controllers\Client\Protocols;
|
||||
|
||||
|
||||
use App\Utils\Helper;
|
||||
|
||||
class V2rayN
|
||||
{
|
||||
public $flag = 'v2rayn';
|
||||
@ -37,6 +39,16 @@ class V2rayN
|
||||
|
||||
public static function buildShadowsocks($password, $server)
|
||||
{
|
||||
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
|
||||
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 16);
|
||||
$userKey = Helper::uuidToBase64($password, 16);
|
||||
$password = "{$serverKey}:{$userKey}";
|
||||
}
|
||||
if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
|
||||
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 32);
|
||||
$userKey = Helper::uuidToBase64($password, 32);
|
||||
$password = "{$serverKey}:{$userKey}";
|
||||
}
|
||||
$name = rawurlencode($server['name']);
|
||||
$str = str_replace(
|
||||
['+', '/', '='],
|
||||
|
@ -23,19 +23,6 @@ class CommController extends Controller
|
||||
'app_description' => config('v2board.app_description'),
|
||||
'app_url' => config('v2board.app_url'),
|
||||
'logo' => config('v2board.logo'),
|
||||
|
||||
// TODO:REMOVE:1.7.0
|
||||
|
||||
'tosUrl' => config('v2board.tos_url'),
|
||||
'isEmailVerify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
|
||||
'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0,
|
||||
'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0)
|
||||
? $this->getEmailSuffix()
|
||||
: 0,
|
||||
'isRecaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
|
||||
'recaptchaSiteKey' => config('v2board.recaptcha_site_key'),
|
||||
'appDescription' => config('v2board.app_description'),
|
||||
'appUrl' => config('v2board.app_url'),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
@ -156,6 +156,7 @@ class AuthController extends Controller
|
||||
$user->plan_id = $plan->id;
|
||||
$user->group_id = $plan->group_id;
|
||||
$user->expired_at = time() + (config('v2board.try_out_hour', 1) * 3600);
|
||||
$user->speed_limit = $plan->speed_limit;
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,10 +190,13 @@ class AuthController extends Controller
|
||||
$email = $request->input('email');
|
||||
$password = $request->input('password');
|
||||
|
||||
$passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0);
|
||||
|
||||
if ($passwordErrorCount >= 5) {
|
||||
abort(500, __('There are too many password errors, please try again after 30 minutes.'));
|
||||
if ((int)config('v2board.password_limit_enable', 1)) {
|
||||
$passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0);
|
||||
if ($passwordErrorCount >= (int)config('v2board.password_limit_count', 5)) {
|
||||
abort(500, __('There are too many password errors, please try again after :minute minutes.', [
|
||||
'minute' => config('v2board.password_limit_expire', 60)
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
$user = User::where('email', $email)->first();
|
||||
@ -205,11 +209,13 @@ class AuthController extends Controller
|
||||
$password,
|
||||
$user->password)
|
||||
) {
|
||||
Cache::put(
|
||||
CacheKey::get('PASSWORD_ERROR_LIMIT', $email),
|
||||
(int)$passwordErrorCount + 1,
|
||||
30 * 60
|
||||
);
|
||||
if ((int)config('v2board.password_limit_enable')) {
|
||||
Cache::put(
|
||||
CacheKey::get('PASSWORD_ERROR_LIMIT', $email),
|
||||
(int)$passwordErrorCount + 1,
|
||||
60 * (int)config('v2board.password_limit_expire', 60)
|
||||
);
|
||||
}
|
||||
abort(500, __('Incorrect email or password'));
|
||||
}
|
||||
|
||||
|
@ -109,8 +109,8 @@ class UniProxyController extends Controller
|
||||
break;
|
||||
}
|
||||
$response['base_config'] = [
|
||||
'push_interval' => config('v2board.server_push_interval', 60),
|
||||
'pull_interval' => config('v2board.server_pull_interval', 60)
|
||||
'push_interval' => (int)config('v2board.server_push_interval', 60),
|
||||
'pull_interval' => (int)config('v2board.server_pull_interval', 60)
|
||||
];
|
||||
if ($this->nodeInfo['route_id']) {
|
||||
$response['routes'] = $this->serverService->getRoutes($this->nodeInfo['route_id']);
|
||||
|
@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\User\UserTransfer;
|
||||
use App\Http\Requests\User\UserUpdate;
|
||||
use App\Http\Requests\User\UserChangePassword;
|
||||
use App\Services\AuthService;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
@ -18,6 +19,30 @@ use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function getActiveSession(Request $request)
|
||||
{
|
||||
$user = User::find($request->user['id']);
|
||||
if (!$user) {
|
||||
abort(500, __('The user does not exist'));
|
||||
}
|
||||
$authService = new AuthService($user);
|
||||
return response([
|
||||
'data' => $authService->getSessions()
|
||||
]);
|
||||
}
|
||||
|
||||
public function removeActiveSession(Request $request)
|
||||
{
|
||||
$user = User::find($request->user['id']);
|
||||
if (!$user) {
|
||||
abort(500, __('The user does not exist'));
|
||||
}
|
||||
$authService = new AuthService($user);
|
||||
return response([
|
||||
'data' => $authService->delSession($request->input('session_id'))
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkLogin(Request $request)
|
||||
{
|
||||
$data = [
|
||||
|
@ -24,9 +24,7 @@ class ConfigSave extends FormRequest
|
||||
// site
|
||||
'logo' => 'nullable|url',
|
||||
'force_https' => 'in:0,1',
|
||||
'safe_mode_enable' => 'in:0,1',
|
||||
'stop_register' => 'in:0,1',
|
||||
'email_verify' => 'in:0,1',
|
||||
'app_name' => '',
|
||||
'app_description' => '',
|
||||
'app_url' => 'nullable|url',
|
||||
@ -34,19 +32,9 @@ class ConfigSave extends FormRequest
|
||||
'try_out_enable' => 'in:0,1',
|
||||
'try_out_plan_id' => 'integer',
|
||||
'try_out_hour' => 'numeric',
|
||||
'email_whitelist_enable' => 'in:0,1',
|
||||
'email_whitelist_suffix' => 'nullable|array',
|
||||
'email_gmail_limit_enable' => 'in:0,1',
|
||||
'recaptcha_enable' => 'in:0,1',
|
||||
'recaptcha_key' => '',
|
||||
'recaptcha_site_key' => '',
|
||||
'tos_url' => 'nullable|url',
|
||||
'currency' => '',
|
||||
'currency_symbol' => '',
|
||||
'register_limit_by_ip_enable' => 'in:0,1',
|
||||
'register_limit_count' => 'integer',
|
||||
'register_limit_expire' => 'integer',
|
||||
'secure_path' => '',
|
||||
// subscribe
|
||||
'plan_change_enable' => 'in:0,1',
|
||||
'reset_traffic_method' => 'in:0,1,2,3,4',
|
||||
@ -85,7 +73,23 @@ class ConfigSave extends FormRequest
|
||||
'macos_version' => '',
|
||||
'macos_download_url' => '',
|
||||
'android_version' => '',
|
||||
'android_download_url' => ''
|
||||
'android_download_url' => '',
|
||||
// safe
|
||||
'email_whitelist_enable' => 'in:0,1',
|
||||
'email_whitelist_suffix' => 'nullable|array',
|
||||
'email_gmail_limit_enable' => 'in:0,1',
|
||||
'recaptcha_enable' => 'in:0,1',
|
||||
'recaptcha_key' => '',
|
||||
'recaptcha_site_key' => '',
|
||||
'email_verify' => 'in:0,1',
|
||||
'safe_mode_enable' => 'in:0,1',
|
||||
'register_limit_by_ip_enable' => 'in:0,1',
|
||||
'register_limit_count' => 'integer',
|
||||
'register_limit_expire' => 'integer',
|
||||
'secure_path' => 'min:8|regex:/^[\w-]*$/',
|
||||
'password_limit_enable' => 'in:0,1',
|
||||
'password_limit_count' => 'integer',
|
||||
'password_limit_expire' => 'integer',
|
||||
];
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
@ -106,7 +110,9 @@ class ConfigSave extends FormRequest
|
||||
'server_token.min' => '通讯密钥长度必须大于16位',
|
||||
'tos_url.url' => '服务条款URL格式不正确,必须携带http(s)://',
|
||||
'telegram_discuss_link.url' => 'Telegram群组地址必须为URL格式,必须携带http(s)://',
|
||||
'logo.url' => 'LOGO URL格式不正确,必须携带https(s)://'
|
||||
'logo.url' => 'LOGO URL格式不正确,必须携带https(s)://',
|
||||
'secure_path.min' => '后台路径长度最小为8位',
|
||||
'secure_path.regex' => '后台路径只能为字母或数字'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,12 @@ class UserRoute
|
||||
$router->get ('/checkLogin', 'User\\UserController@checkLogin');
|
||||
$router->post('/transfer', 'User\\UserController@transfer');
|
||||
$router->post('/getQuickLoginUrl', 'User\\UserController@getQuickLoginUrl');
|
||||
$router->get ('/getActiveSession', 'User\\UserController@getActiveSession');
|
||||
$router->post('/removeActiveSession', 'User\\UserController@removeActiveSession');
|
||||
// Order
|
||||
$router->post('/order/save', 'User\\OrderController@save');
|
||||
$router->post('/order/checkout', 'User\\OrderController@checkout');
|
||||
$router->get ('/order/check', 'User\\OrderController@check');
|
||||
$router->get ('/order/details', 'User\\OrderController@detail'); // TODO: 1.7.0 remove
|
||||
$router->get ('/order/detail', 'User\\OrderController@detail');
|
||||
$router->get ('/order/fetch', 'User\\OrderController@fetch');
|
||||
$router->get ('/order/getPaymentMethod', 'User\\OrderController@getPaymentMethod');
|
||||
|
@ -28,6 +28,11 @@ class AlipayF2F {
|
||||
'label' => '支付宝公钥',
|
||||
'description' => '',
|
||||
'type' => 'input',
|
||||
],
|
||||
'product_name' => [
|
||||
'label' => '自定义商品名称',
|
||||
'description' => '将会体现在支付宝账单中',
|
||||
'type' => 'input'
|
||||
]
|
||||
];
|
||||
}
|
||||
@ -42,7 +47,7 @@ class AlipayF2F {
|
||||
$gateway->setAlipayPublicKey($this->config['public_key']); // 可以是路径,也可以是密钥内容
|
||||
$gateway->setNotifyUrl($order['notify_url']);
|
||||
$gateway->setBizContent([
|
||||
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
|
||||
'subject' => $this->config['product_name'] ?? (config('v2board.app_name', 'V2Board') . ' - 订阅'),
|
||||
'out_trade_no' => $order['trade_no'],
|
||||
'total_amount' => $order['total_amount'] / 100
|
||||
]);
|
||||
|
@ -14,7 +14,7 @@ class AuthService
|
||||
{
|
||||
private $user;
|
||||
|
||||
public function __construct($user)
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
@ -27,7 +27,9 @@ class AuthService
|
||||
'session' => $guid,
|
||||
], config('app.key'), 'HS256');
|
||||
self::addSession($this->user->id, $guid, [
|
||||
'ip' => $request->ip()
|
||||
'ip' => $request->ip(),
|
||||
'login_at' => time(),
|
||||
'ua' => $request->userAgent()
|
||||
]);
|
||||
return [
|
||||
'token' => $this->user->token,
|
||||
@ -74,10 +76,23 @@ class AuthService
|
||||
$cacheKey,
|
||||
$sessions
|
||||
)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSessions()
|
||||
{
|
||||
return (array)Cache::get(CacheKey::get("USER_SESSIONS", $this->user->id), []);
|
||||
}
|
||||
|
||||
public function delSession($sessionId)
|
||||
{
|
||||
$cacheKey = CacheKey::get("USER_SESSIONS", $this->user->id);
|
||||
$sessions = (array)Cache::get($cacheKey, []);
|
||||
unset($sessions[$sessionId]);
|
||||
if (!Cache::put(
|
||||
$cacheKey,
|
||||
$sessions
|
||||
)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +221,14 @@ class ServerService
|
||||
|
||||
public function getRoutes(array $routeIds)
|
||||
{
|
||||
return ServerRoute::select(['id', 'match', 'action', 'action_value'])->whereIn('id', $routeIds)->get();
|
||||
$routes = ServerRoute::select(['id', 'match', 'action', 'action_value'])->whereIn('id', $routeIds)->get();
|
||||
// TODO: remove on 1.8.0
|
||||
foreach ($routes as $k => $route) {
|
||||
$array = json_decode($route->match, true);
|
||||
if (is_array($array)) $routes[$k]['match'] = $array;
|
||||
}
|
||||
// TODO: remove on 1.8.0
|
||||
return $routes;
|
||||
}
|
||||
|
||||
public function getServer($serverId, $serverType)
|
||||
|
@ -18,9 +18,10 @@ class ThemeService
|
||||
|
||||
public function init()
|
||||
{
|
||||
$themeConfigFile = $this->path . "{$this->theme}/config.php";
|
||||
if (!File::exists($themeConfigFile)) return;
|
||||
$themeConfig = include($themeConfigFile);
|
||||
$themeConfigFile = $this->path . "{$this->theme}/config.json";
|
||||
if (!File::exists($themeConfigFile)) abort(500, "{$this->theme}主题不存在");
|
||||
$themeConfig = json_decode(File::get($themeConfigFile), true);
|
||||
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) abort(500, "{$this->theme}主题配置文件有误");
|
||||
$configs = $themeConfig['configs'];
|
||||
$data = [];
|
||||
foreach ($configs as $config) {
|
||||
|
@ -2,17 +2,12 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Jobs\ServerLogJob;
|
||||
use App\Jobs\StatServerJob;
|
||||
use App\Jobs\StatUserJob;
|
||||
use App\Jobs\TrafficFetchJob;
|
||||
use App\Models\InviteCode;
|
||||
use App\Models\Order;
|
||||
use App\Models\Plan;
|
||||
use App\Models\ServerV2ray;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UserService
|
||||
{
|
||||
@ -33,9 +28,9 @@ class UserService
|
||||
}
|
||||
if ((int)$day >= (int)$today) {
|
||||
return $day - $today;
|
||||
} else {
|
||||
return $lastDay - $today + $day;
|
||||
}
|
||||
|
||||
return $lastDay - $today + $day;
|
||||
}
|
||||
|
||||
private function calcResetDayByYearFirstDay(): int
|
||||
|
@ -237,5 +237,5 @@ return [
|
||||
| The only modification by laravel config
|
||||
|
|
||||
*/
|
||||
'version' => '1.7.1.1671082585916'
|
||||
'version' => '1.7.2.1671471846226'
|
||||
];
|
||||
|
2
public/assets/admin/umi.js
vendored
2
public/assets/admin/umi.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/theme/v2board/assets/umi.js
vendored
2
public/theme/v2board/assets/umi.js
vendored
File diff suppressed because one or more lines are too long
2
public/theme/v2board/assets/vendors.async.js
vendored
2
public/theme/v2board/assets/vendors.async.js
vendored
File diff suppressed because one or more lines are too long
49
public/theme/v2board/config.json
Normal file
49
public/theme/v2board/config.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "v2board",
|
||||
"description": "v2board",
|
||||
"version": "1.7.2",
|
||||
"images": "https://images.unsplash.com/photo-1515405295579-ba7b45403062?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2160&q=80",
|
||||
"configs": [{
|
||||
"label": "主题色",
|
||||
"placeholder": "请选择主题颜色",
|
||||
"field_name": "theme_color",
|
||||
"field_type": "select",
|
||||
"select_options": {
|
||||
"default": "默认(蓝色)",
|
||||
"green": "奶绿色",
|
||||
"black": "黑色",
|
||||
"darkblue": "暗蓝色"
|
||||
},
|
||||
"default_value": "default"
|
||||
}, {
|
||||
"label": "背景",
|
||||
"placeholder": "请输入背景图片URL",
|
||||
"field_name": "background_url",
|
||||
"field_type": "input"
|
||||
}, {
|
||||
"label": "边栏风格",
|
||||
"placeholder": "请选择边栏风格",
|
||||
"field_name": "theme_sidebar",
|
||||
"field_type": "select",
|
||||
"select_options": {
|
||||
"light": "亮",
|
||||
"dark": "暗"
|
||||
},
|
||||
"default_value": "light"
|
||||
}, {
|
||||
"label": "顶部风格",
|
||||
"placeholder": "请选择顶部风格",
|
||||
"field_name": "theme_header",
|
||||
"field_type": "select",
|
||||
"select_options": {
|
||||
"light": "亮",
|
||||
"dark": "暗"
|
||||
},
|
||||
"default_value": "dark"
|
||||
}, {
|
||||
"label": "自定义页脚HTML",
|
||||
"placeholder": "可以实现客服JS代码的加入等",
|
||||
"field_name": "custom_html",
|
||||
"field_type": "textarea"
|
||||
}]
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'name' => 'V2board',
|
||||
'description' => 'V2board默认主题',
|
||||
'version' => '1.5.6',
|
||||
'images' => 'https://images.unsplash.com/photo-1515405295579-ba7b45403062?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2160&q=80',
|
||||
'configs' => [
|
||||
[
|
||||
'label' => '主题色', // 标签
|
||||
'placeholder' => '请选择主题颜色', // 描述
|
||||
'field_name' => 'theme_color', // 字段名 作为数据key使用
|
||||
'field_type' => 'select', // 字段类型: select,input,switch
|
||||
'select_options' => [ // 当字段类型为select时有效
|
||||
'default' => '默认(蓝色)',
|
||||
'green' => '奶绿色',
|
||||
'black' => '黑色',
|
||||
'darkblue' => '暗蓝色',
|
||||
],
|
||||
'default_value' => 'default' // 字段默认值,将会在首次进行初始化
|
||||
], [
|
||||
'label' => '背景',
|
||||
'placeholder' => '请输入背景图片URL',
|
||||
'field_name' => 'background_url',
|
||||
'field_type' => 'input'
|
||||
], [
|
||||
'label' => '边栏风格',
|
||||
'placeholder' => '请选择边栏风格',
|
||||
'field_name' => 'theme_sidebar',
|
||||
'field_type' => 'select',
|
||||
'select_options' => [
|
||||
'light' => '亮',
|
||||
'dark' => '暗'
|
||||
],
|
||||
'default_value' => 'light'
|
||||
], [
|
||||
'label' => '顶部风格',
|
||||
'placeholder' => '请选择顶部风格',
|
||||
'field_name' => 'theme_header',
|
||||
'field_type' => 'select',
|
||||
'select_options' => [
|
||||
'light' => '亮',
|
||||
'dark' => '暗'
|
||||
],
|
||||
'default_value' => 'dark'
|
||||
], [
|
||||
'label' => '自定义页脚HTML',
|
||||
'placeholder' => '可以实现客服JS代码的加入等',
|
||||
'field_name' => 'custom_html',
|
||||
'field_type' => 'textarea'
|
||||
]
|
||||
]
|
||||
];
|
@ -59,19 +59,6 @@
|
||||
<script src="/theme/{{$theme}}/assets/vendors.async.js?v={{$version}}"></script>
|
||||
<script src="/theme/{{$theme}}/assets/components.async.js?v={{$version}}"></script>
|
||||
<script src="/theme/{{$theme}}/assets/umi.js?v={{$version}}"></script>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-P1E9Z5LRRK"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-P1E9Z5LRRK');
|
||||
</script>
|
||||
@if (file_exists(public_path("/theme/{$theme}/assets/custom.js")))
|
||||
<script src="/theme/{{$theme}}/assets/custom.js?v={{$version}}"></script>
|
||||
@endif
|
||||
|
@ -94,5 +94,5 @@
|
||||
"Login to :name": "Login to :name",
|
||||
"Sending frequently, please try again later": "Sending frequently, please try again later",
|
||||
"Current product is sold out": "Current product is sold out",
|
||||
"There are too many password errors, please try again after 30 minutes.": "There are too many password errors, please try again after 30 minutes."
|
||||
"There are too many password errors, please try again after :minute minutes.": "There are too many password errors, please try again after :minute minutes."
|
||||
}
|
||||
|
@ -94,5 +94,5 @@
|
||||
"Login to :name": "登入到 :name",
|
||||
"Sending frequently, please try again later": "发送频繁,请稍后再试",
|
||||
"Current product is sold out": "当前商品已售罄",
|
||||
"There are too many password errors, please try again after 30 minutes.": "密码错误次数过多,请 30 分钟后再试"
|
||||
"There are too many password errors, please try again after :minute minutes.": "密码错误次数过多,请 :minute 分钟后再试"
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ test-timeout = 5
|
||||
internet-test-url = http://bing.com
|
||||
proxy-test-url = http://bing.com
|
||||
|
||||
[Panel]
|
||||
SubscribeInfo = $subscribe_info, style=info
|
||||
|
||||
# Surfboard 的服务器和策略组配置方式与 Surge 类似, 可以参考 Surge 的规则配置手册: https://manual.nssurge.com/
|
||||
|
||||
[Proxy]
|
||||
|
@ -36,6 +36,9 @@ hide-crashlytics-request = true
|
||||
use-keyword-filter = false
|
||||
hide-udp = false
|
||||
|
||||
[Panel]
|
||||
SubscribeInfo = $subscribe_info, style=info
|
||||
|
||||
# -----------------------------
|
||||
# Surge 的几种策略配置规范,请参考 https://manual.nssurge.com/policy/proxy.html
|
||||
# 不同的代理策略有*很多*可选参数,请参考上方连接的 Parameters 一段,根据需求自行添加参数。
|
||||
|
@ -31,19 +31,6 @@
|
||||
<script src="/assets/admin/vendors.async.js?v={{$version}}"></script>
|
||||
<script src="/assets/admin/components.async.js?v={{$version}}"></script>
|
||||
<script src="/assets/admin/umi.js?v={{$version}}"></script>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-P1E9Z5LRRK"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-P1E9Z5LRRK');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user