1.7.0
This commit is contained in:
tokumeikoi
2022-12-15 03:31:37 +08:00
committed by GitHub
65 changed files with 797 additions and 240 deletions

View File

@ -107,7 +107,8 @@ class ConfigController extends Controller
'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)
'register_limit_expire' => config('v2board.register_limit_expire', 60),
'secure_path' => config('v2board.secure_path', config('v2board.frontend_admin_path', crc32(config('app.key'))))
],
'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
@ -124,14 +125,11 @@ class ConfigController extends Controller
'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'),
'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'),
'frontend_background_url' => config('v2board.frontend_background_url'),
'frontend_admin_path' => config('v2board.frontend_admin_path', 'admin')
],
'server' => [
'server_token' => config('v2board.server_token'),
'server_license' => config('v2board.server_license'),
'server_log_enable' => config('v2board.server_log_enable', 0),
'server_v2ray_domain' => config('v2board.server_v2ray_domain'),
'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'),
'server_pull_interval' => config('v2board.server_pull_interval', 60),
'server_push_interval' => config('v2board.server_push_interval', 60),
],
'email' => [
'email_template' => config('v2board.email_template', 'default'),

View File

@ -41,10 +41,13 @@ class PlanController extends Controller
DB::beginTransaction();
// update user group id and transfer
try {
User::where('plan_id', $plan->id)->update([
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824
]);
if ($request->input('force_update')) {
User::where('plan_id', $plan->id)->update([
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824,
'speed_limit' => $params['speed_limit']
]);
}
$plan->update($params);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerShadowsocksSave;
use App\Http\Requests\Admin\ServerShadowsocksUpdate;
use App\Models\ServerRoute;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class RouteController extends Controller
{
public function fetch(Request $request)
{
$routes = ServerRoute::get();
return [
'data' => $routes
];
}
public function save(Request $request)
{
$params = $request->validate([
'remarks' => 'required',
'match' => 'required',
'action' => 'required',
'action_value' => 'nullable'
]);
if ($request->input('id')) {
try {
$route = ServerRoute::find($request->input('id'));
$route->update($params);
return [
'data' => true
];
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
if (!ServerRoute::create($params)) abort(500, '创建失败');
return [
'data' => true
];
}
public function drop(Request $request)
{
$route = ServerRoute::find($request->input('id'));
if (!$route) abort(500, '路由不存在');
if (!$route->delete()) abort(500, '删除失败');
return [
'data' => true
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin;
use App\Models\CommissionLog;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Models\StatUser;
@ -47,14 +48,12 @@ class StatController extends Controller
->where('created_at', '<', strtotime(date('Y-m-1')))
->whereNotIn('status', [0, 2])
->sum('total_amount'),
'commission_month_payout' => Order::where('actual_commission_balance' ,'!=', NULL)
->where('created_at', '>=', strtotime(date('Y-m-1')))
'commission_month_payout' => CommissionLog::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->sum('actual_commission_balance'),
'commission_last_month_payout' => Order::where('actual_commission_balance' ,'!=', NULL)
->where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->sum('get_amount'),
'commission_last_month_payout' => CommissionLog::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->where('created_at', '<', strtotime(date('Y-m-1')))
->sum('actual_commission_balance'),
->sum('get_amount'),
]
]);
}
@ -100,7 +99,7 @@ class StatController extends Controller
{
$servers = [
'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(),
'vmess' => ServerV2ray::where('parent_id', null)->get()->toArray(),
'v2ray' => ServerV2ray::where('parent_id', null)->get()->toArray(),
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray()
];
$startAt = strtotime('-1 day', strtotime(date('Y-m-d')));

View File

@ -33,7 +33,14 @@ class AppController extends Controller
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
array_push($proxy, Protocols\Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}

View File

@ -13,9 +13,7 @@ class ClientController extends Controller
public function subscribe(Request $request)
{
$flag = $request->input('flag')
?? (isset($_SERVER['HTTP_USER_AGENT'])
? $_SERVER['HTTP_USER_AGENT']
: '');
?? ($_SERVER['HTTP_USER_AGENT'] ?? '');
$flag = strtolower($flag);
$user = $request->user;
// account not expired and is not banned.

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Dict;
use phpDocumentor\Reflection\Types\Self_;
use Symfony\Component\Yaml\Yaml;
class Clash
@ -36,7 +38,14 @@ class Clash
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Helper;
class Shadowrocket
{
public $flag = 'shadowrocket';
@ -43,6 +45,16 @@ class Shadowrocket
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(
['+', '/', '='],

View File

@ -29,7 +29,9 @@ class Shadowsocks
$bytesRemaining = $user['transfer_enable'] - $bytesUsed;
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], ['aes-128-gcm', 'aes-256-gcm', 'aes-192-gcm'])
) {
array_push($configs, self::SIP008($item, $user));
}
}

View File

@ -36,7 +36,14 @@ class Stash
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}

View File

@ -28,7 +28,14 @@ class Surfboard
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]

View File

@ -28,7 +28,14 @@ class Surge
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]

View File

@ -23,6 +23,19 @@ 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'),
]
]);
}

View File

@ -7,6 +7,7 @@ use App\Http\Requests\Passport\AuthRegister;
use App\Http\Requests\Passport\AuthForget;
use App\Http\Requests\Passport\AuthLogin;
use App\Jobs\SendEmailJob;
use App\Services\AuthService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Plan;
@ -16,6 +17,7 @@ use App\Utils\Helper;
use App\Utils\Dict;
use App\Utils\CacheKey;
use ReCaptcha\ReCaptcha;
use Firebase\JWT\JWT;
class AuthController extends Controller
{
@ -77,7 +79,9 @@ class AuthController extends Controller
if ((int)config('v2board.register_limit_by_ip_enable', 0)) {
$registerCountByIP = Cache::get(CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip())) ?? 0;
if ((int)$registerCountByIP >= (int)config('v2board.register_limit_count', 3)) {
abort(500, __('Register frequently, please try again after 1 hour'));
abort(500, __('Register frequently, please try again after :minute minute', [
'minute' => config('v2board.register_limit_expire', 60)
]));
}
}
if ((int)config('v2board.recaptcha_enable', 0)) {
@ -163,11 +167,6 @@ class AuthController extends Controller
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
$user->last_login_at = time();
$user->save();
@ -178,8 +177,11 @@ class AuthController extends Controller
(int)config('v2board.register_limit_expire', 60) * 60
);
}
$authService = new AuthService($user);
return response()->json([
'data' => $data
'data' => $authService->generateAuthData('register')
]);
}
@ -188,6 +190,12 @@ 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.'));
}
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, __('Incorrect email or password'));
@ -198,6 +206,11 @@ class AuthController extends Controller
$password,
$user->password)
) {
Cache::put(
CacheKey::get('PASSWORD_ERROR_LIMIT', $email),
(int)$passwordErrorCount + 1,
30 * 60
);
abort(500, __('Incorrect email or password'));
}
@ -205,14 +218,9 @@ class AuthController extends Controller
abort(500, __('Your account has been suspended'));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
if ($user->is_admin) $data['is_admin'] = true;
$authService = new AuthService($user);
return response([
'data' => $data
'data' => $authService->generateAuthData('login')
]);
}
@ -241,49 +249,25 @@ class AuthController extends Controller
if ($user->banned) {
abort(500, __('Your account has been suspended'));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
Cache::forget($key);
$authService = new AuthService($user);
return response([
'data' => $data
'data' => $authService->generateAuthData('token')
]);
}
}
public function getTempToken(Request $request)
{
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
abort(500, __('Token error'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
return response([
'data' => $code
]);
}
public function getQuickLoginUrl(Request $request)
{
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!isset($authData[0]) || !isset($authData[1])) abort(403, __('Token error'));
$user = User::where('email', $authData[0])
->where('password', $authData[1])
->first();
if (!$user) {
abort(500, __('Token error'));
}
$user = AuthService::decryptAuthData($authorization);
if (!$user) abort(403, '未登录或登陆已过期');
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
Cache::put($key, $user['id'], 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (config('v2board.app_url')) {
$url = config('v2board.app_url') . $redirect;

View File

@ -84,7 +84,7 @@ class DeepbworkController extends Controller
foreach ($data as $item) {
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'vmess');
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'v2ray');
}
return response([

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use App\Utils\Helper;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerShadowsocks;
@ -12,12 +13,12 @@ use App\Models\ServerV2ray;
use App\Models\ServerTrojan;
use Illuminate\Support\Facades\Cache;
class VProxyController extends Controller
class UniProxyController extends Controller
{
private $nodeType;
private $nodeInfo;
private $nodeId;
private $token;
private $serverService;
public function __construct(Request $request)
{
@ -28,25 +29,11 @@ class VProxyController extends Controller
if ($token !== config('v2board.server_token')) {
abort(500, 'token is error');
}
$this->token = $token;
$this->nodeType = $request->input('node_type');
$this->nodeId = $request->input('node_id');
switch ($this->nodeType) {
case 'v2ray':
$this->nodeInfo = ServerV2ray::find($this->nodeId);
break;
case 'shadowsocks':
$this->nodeInfo = ServerShadowsocks::find($this->nodeId);
break;
case 'trojan':
$this->nodeInfo = ServerTrojan::find($this->nodeId);
break;
default:
break;
}
if (!$this->nodeInfo) {
abort(500, 'server not found');
}
$this->serverService = new ServerService();
$this->nodeInfo = $this->serverService->getServer($this->nodeId, $this->nodeType);
if (!$this->nodeInfo) abort(500, 'server is not exist');
}
// 后端获取用户
@ -54,21 +41,11 @@ class VProxyController extends Controller
{
ini_set('memory_limit', -1);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_CHECK_AT', $this->nodeInfo->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($this->nodeInfo->group_id);
$users = $this->serverService->getAvailableUsers($this->nodeInfo->group_id);
$users = $users->toArray();
$response['users'] = $users;
switch ($this->nodeType) {
case 'shadowsocks':
$response['server'] = [
'cipher' => $this->nodeInfo->cipher,
'server_port' => $this->nodeInfo->server_port
];
break;
}
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304);
@ -78,17 +55,17 @@ class VProxyController extends Controller
}
// 后端提交数据
public function submit(Request $request)
public function push(Request $request)
{
$data = file_get_contents('php://input');
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600);
$userService = new UserService();
foreach ($data as $item) {
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $this->nodeInfo->toArray(), $this->nodeType);
foreach (array_keys($data) as $k) {
$u = $data[$k][0];
$d = $data[$k][1];
$userService->trafficFetch($u, $d, $k, $this->nodeInfo->toArray(), $this->nodeType);
}
return response([
@ -101,28 +78,48 @@ class VProxyController extends Controller
{
switch ($this->nodeType) {
case 'shadowsocks':
die(json_encode([
$response = [
'server_port' => $this->nodeInfo->server_port,
'cipher' => $this->nodeInfo->cipher,
'obfs' => $this->nodeInfo->obfs,
'obfs_settings' => $this->nodeInfo->obfs_settings
], JSON_UNESCAPED_UNICODE));
];
if ($this->nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
$response['server_key'] = Helper::getShadowsocksServerKey($this->nodeInfo->created_at, 16);
}
if ($this->nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
$response['server_key'] = Helper::getShadowsocksServerKey($this->nodeInfo->created_at, 32);
}
break;
case 'v2ray':
die(json_encode([
$response = [
'server_port' => $this->nodeInfo->server_port,
'network' => $this->nodeInfo->network,
'cipher' => $this->nodeInfo->cipher,
'networkSettings' => $this->nodeInfo->networkSettings,
'tls' => $this->nodeInfo->tls
], JSON_UNESCAPED_UNICODE));
];
break;
case 'trojan':
die(json_encode([
$response = [
'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port
], JSON_UNESCAPED_UNICODE));
'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name
];
break;
}
$response['base_config'] = [
'push_interval' => config('v2board.server_push_interval', 60),
'pull_interval' => config('v2board.server_pull_interval', 60)
];
if ($this->nodeInfo['route_id']) {
$response['routes'] = $this->serverService->getRoutes($this->nodeInfo['route_id']);
}
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304);
}
return response($response)->header('ETag', "\"{$eTag}\"");
}
}

View File

@ -58,19 +58,21 @@ class InviteController extends Controller
if ($user->commission_rate) {
$commission_rate = $user->commission_rate;
}
$uncheck_commission_balance = (int)Order::where('status', 3)
->where('commission_status', 0)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance');
if (config('v2board.commission_distribution_enable', 0)) {
$uncheck_commission_balance = $uncheck_commission_balance * (config('v2board.commission_distribution_l1') / 100);
}
$stat = [
//已注册用户数
(int)User::where('invite_user_id', $request->user['id'])->count(),
//有效的佣金
(int)Order::where('status', 3)
->where('commission_status', 2)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance'),
(int)CommissionLog::where('invite_user_id', $request->user['id'])
->sum('get_amount'),
//确认中的佣金
(int)Order::where('status', 3)
->where('commission_status', 0)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance'),
$uncheck_commission_balance,
//佣金比例
(int)$commission_rate,
//可用佣金

View File

@ -85,7 +85,7 @@ class OrderController extends Controller
abort(500, __('Subscription plan does not exist'));
}
if (!$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
if ($user->plan_id !== $plan->id && !$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
abort(500, __('Current product is sold out'));
}

View File

@ -26,8 +26,14 @@ class ServerController extends Controller
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
}
$eTag = sha1(json_encode(array_column($servers, 'updated_at')));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304);
}
return response([
'data' => $servers
]);
])->header('ETag', "\"{$eTag}\"");
}
}

View File

@ -113,7 +113,8 @@ class UserController extends Controller
'u',
'd',
'transfer_enable',
'email'
'email',
'uuid'
])
->first();
if (!$user) {