usercp token management

This commit is contained in:
xiaomlove
2025-03-29 14:32:31 +07:00
parent 4b2f933806
commit edc9e56d7d
21 changed files with 218 additions and 122 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
namespace App\Auth;
use App\Enums\PermissionEnum;
use App\Enums\Permission\PermissionEnum;
class Permission
{
+3 -57
View File
@@ -2,59 +2,8 @@
namespace App\Console\Commands;
use App\Enums\PermissionEnum;
use App\Events\TorrentUpdated;
use App\Filament\Resources\System\AgentAllowResource;
use App\Http\Resources\TagResource;
use App\Models\AgentAllow;
use App\Models\Attendance;
use App\Models\Category;
use App\Models\Exam;
use App\Models\ExamProgress;
use App\Models\ExamUser;
use App\Models\HitAndRun;
use App\Models\Invite;
use App\Models\LoginLog;
use App\Models\Medal;
use App\Models\Peer;
use App\Models\Post;
use App\Models\SearchBox;
use App\Models\Setting;
use App\Models\Snatch;
use App\Models\Tag;
use App\Models\Torrent;
use App\Models\TorrentOperationLog;
use App\Models\User;
use App\Models\UserBanLog;
use App\Repositories\AgentAllowRepository;
use App\Repositories\AttendanceRepository;
use App\Repositories\CleanupRepository;
use App\Repositories\ExamRepository;
use App\Repositories\HitAndRunRepository;
use App\Repositories\MeiliSearchRepository;
use App\Repositories\PluginRepository;
use App\Repositories\SearchBoxRepository;
use App\Repositories\SearchRepository;
use App\Repositories\TagRepository;
use App\Repositories\ToolRepository;
use App\Repositories\TorrentRepository;
use App\Repositories\UserRepository;
use Carbon\Carbon;
use Filament\Notifications\Notification;
use GeoIp2\Database\Reader;
use GuzzleHttp\Client;
use App\Models\PersonalAccessToken;
use Illuminate\Console\Command;
use Illuminate\Encryption\Encrypter;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Imdb\Cache;
use League\Flysystem\StorageAttributes;
use Nexus\Database\NexusDB;
use Nexus\Imdb\Imdb;
use NexusPlugin\Menu\Filament\MenuItemResource\Pages\ManageMenuItems;
use NexusPlugin\Menu\MenuRepository;
use NexusPlugin\Menu\Models\MenuItem;
@@ -66,9 +15,6 @@ use NexusPlugin\StickyPromotion\Models\StickyPromotionParticipator;
use NexusPlugin\Tracker\TrackerRepository;
use NexusPlugin\Work\Models\RoleWork;
use NexusPlugin\Work\WorkRepository;
use PhpIP\IP;
use PhpIP\IPBlock;
use Rhilip\Bencode\Bencode;
class Test extends Command
{
@@ -103,9 +49,9 @@ class Test extends Command
*/
public function handle()
{
$r = microtime();
$r = PersonalAccessToken::query()->find(11);
// $r = SearchBox::query()->find(4)->ss()->orWhere("mode", 0)->get();
dd($r);
dd($r->abilitiesText);
}
}
@@ -1,8 +1,11 @@
<?php
namespace App\Enums;
namespace App\Enums\Permission;
enum PermissionEnum: string {
case UPLOAD_TO_SPECIAL_SECTION = 'uploadspecial';
case BE_ANONYMOUS = 'beanonymous';
case TORRENT_LIST = 'torrent:list';
case UPLOAD = 'upload';
}
@@ -126,45 +126,7 @@ class AuthenticateController extends Controller
}
}
public function addToken(Request $request)
{
try {
$request->validate([
'name' => 'required|string',
]);
$user = Auth::user();
$count = $user->tokens()->count();
if ($count >= 5) {
throw new NexusException("Token limit exceeded");
}
$newAccessToken = $user->createToken($request->name);
PersonalAccessTokenPlain::query()->create([
'access_token_id' => $newAccessToken->accessToken->getKey(),
'plain_text_token' => $newAccessToken->plainTextToken,
]);
return $this->success(true);
} catch (\Exception $exception) {
return $this->fail(false, $exception->getMessage());
}
}
public function delToken(Request $request)
{
try {
$request->validate([
'id' => 'required|integer',
]);
$user = Auth::user();
$token = $user->tokens()->where("id", $request->id)->first();
if ($token) {
PersonalAccessTokenPlain::query()->where("access_token_id", $token->id)->delete();
$token->delete();
}
return $this->success(true);
} catch (\Exception $exception) {
return $this->fail(false, $exception->getMessage());
}
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace App\Http\Controllers;
use App\Exceptions\NexusException;
use App\Models\PersonalAccessTokenPlain;
use App\Repositories\TokenRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class TokenController extends Controller
{
private $repository;
public function __construct(TokenRepository $repository)
{
$this->repository = $repository;
}
public function addToken(Request $request)
{
try {
$request->validate([
'name' => 'required|string',
'permissions' => 'required|array|min:1',
]);
$user = Auth::user();
$count = $user->tokens()->count();
if ($count >= 5) {
throw new NexusException("Token limit exceeded");
}
$newAccessToken = $user->createToken($request->name, $request->permissions);
PersonalAccessTokenPlain::query()->create([
'access_token_id' => $newAccessToken->accessToken->getKey(),
'plain_text_token' => $newAccessToken->plainTextToken,
]);
return $this->success(true);
} catch (\Exception $exception) {
return $this->fail(false, $exception->getMessage());
}
}
public function delToken(Request $request)
{
try {
$request->validate([
'id' => 'required|integer',
]);
$user = Auth::user();
$token = $user->tokens()->where("id", $request->id)->first();
if ($token) {
PersonalAccessTokenPlain::query()->where("access_token_id", $token->id)->delete();
$token->delete();
}
return $this->success(true);
} catch (\Exception $exception) {
return $this->fail(false, $exception->getMessage());
}
}
public function getPlainText(Request $request)
{
try {
$request->validate([
'id' => 'required|integer',
]);
$user = Auth::user();
$token = $user->tokens()->where("id", $request->id)->first();
if (!$token) {
throw new NexusException("Token not found");
}
$plainRecord = PersonalAccessTokenPlain::query()->where("access_token_id", $token->id)->first();
if (!$plainRecord) {
throw new NexusException("Plain record not found");
}
return $this->success($plainRecord->plain_text_token);
} catch (\Exception $exception) {
return $this->fail(false, $exception->getMessage());
}
}
}
@@ -26,4 +26,10 @@ class UploadController extends Controller
return $this->success($resource);
}
public function upload(Request $request)
{
$user = $request->user();
return $this->success("OK");
}
}
+5
View File
@@ -78,4 +78,9 @@ class Kernel extends HttpKernel
'locale' => \App\Http\Middleware\Locale::class,
'user' => \App\Http\Middleware\User::class,
];
protected $middlewareAliases = [
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
];
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
class PersonalAccessToken extends SanctumPersonalAccessToken
{
public function getAbilitiesTextAttribute(): string
{
if (in_array('*', $this->abilities)) {
return 'ALL';
}
$result = [];
foreach ($this->abilities as $ability) {
if ($ability != '*') {
$result[] = nexus_trans("permission.{$ability}.text");
}
}
return implode(', ', $result);
}
}
+2
View File
@@ -13,6 +13,7 @@ use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\JsonResource;
use Laravel\Passport\Passport;
use Nexus\Database\NexusDB;
use Nexus\Nexus;
use Filament\Facades\Filament;
@@ -37,6 +38,7 @@ class AppServiceProvider extends ServiceProvider
{
global $plugin;
$plugin->start();
NexusDB::customModel();
DB::connection(config('database.default'))->enableQueryLog();
$forceScheme = strtolower(env('FORCE_SCHEME'));
if (env('APP_ENV') == "production" && in_array($forceScheme, ['https', 'http'])) {
-7
View File
@@ -9,7 +9,6 @@ use App\Models\Category;
use App\Models\Codec;
use App\Models\Icon;
use App\Models\Media;
use App\Models\OauthClient;
use App\Models\Plugin;
use App\Models\Processing;
use App\Models\SearchBox;
@@ -22,7 +21,6 @@ use App\Policies\CodecPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
@@ -55,11 +53,6 @@ class AuthServiceProvider extends ServiceProvider
*/
public function boot()
{
// $this->registerPolicies();
if (class_exists(Passport::class)) {
Passport::useClientModel(OauthClient::class);
}
Auth::viaRequest('nexus-cookie', function (Request $request) {
return $this->getUserByCookie($request->cookie());
});
+1 -1
View File
@@ -39,7 +39,7 @@ class RouteServiceProvider extends ServiceProvider
$this->configureRateLimiting();
$this->routes(function () {
Route::prefix('api')
Route::prefix('api/v1')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Repositories;
use App\Enums\Permission\PermissionEnum;
class TokenRepository extends BaseRepository
{
private static array $userTokenPermissions = [
PermissionEnum::TORRENT_LIST,
PermissionEnum::UPLOAD,
];
public function listUserTokenPermissions(): array
{
$result = [];
foreach (self::$userTokenPermissions as $permission) {
$result[$permission->value] = nexus_trans("permission.{$permission->value}.text");
}
return $result;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
<?php
defined('VERSION_NUMBER') || define('VERSION_NUMBER', '1.9.0');
defined('RELEASE_DATE') || define('RELEASE_DATE', '2025-02-15');
defined('RELEASE_DATE') || define('RELEASE_DATE', '2025-03-29');
defined('IN_TRACKER') || define('IN_TRACKER', false);
defined('PROJECTNAME') || define("PROJECTNAME","NexusPHP");
defined('NEXUSPHPURL') || define("NEXUSPHPURL","https://nexusphp.org");
+4
View File
@@ -1366,3 +1366,7 @@ function send_admin_success_notification(string $msg = ""): void {
function send_admin_fail_notification(string $msg = ""): void {
\Filament\Notifications\Notification::make()->danger()->title($msg ?: "Fail!")->send();
}
function ability(\App\Enums\Permission\PermissionEnum $permission): string {
return sprintf("ability:%s", $permission->value);
}
+15
View File
@@ -2,12 +2,16 @@
namespace Nexus\Database;
use App\Models\OauthClient;
use App\Models\PersonalAccessToken;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Laravel\Passport\Passport;
use Laravel\Sanctum\Sanctum;
class NexusDB
{
@@ -260,6 +264,7 @@ class NexusDB
$capsule->bootEloquent();
$connection = self::$eloquentConnection = $capsule->getConnection($connectionName);
$connection->enableQueryLog();
self::customModel();
}
private static function schema(): \Illuminate\Database\Schema\Builder
@@ -439,5 +444,15 @@ class NexusDB
return false;
}
public static function customModel(): void
{
if (class_exists(Passport::class)) {
Passport::useClientModel(OauthClient::class);
}
if (class_exists(Sanctum::class)) {
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
}
}
+32 -3
View File
@@ -1045,10 +1045,13 @@ width: 80px
.form-control-row .field {
}
.form-control-row input,textarea {
.form-control-row input[type=text],textarea {
width: 300px;
padding: 4px;
}
.form-control-row input[type=checkbox] {
vertical-align: sub;
}
CSS;
$seedBoxForm = <<<FORM
@@ -1122,23 +1125,32 @@ JS;
//end seed box
//token start
$tokenRep = new \App\Repositories\TokenRepository();
$permissions = $tokenRep->listUserTokenPermissions();
$permissionOptions = [];
foreach ($permissions as $name => $label) {
$permissionOptions[] = sprintf('<label><input type="checkbox" name="permissions[]" value="%s">%s</label>', $name, $label);
}
$permissionCheckbox = implode("", $permissionOptions);
$token = '';
$tokenLabel = nexus_trans("token.label");
$columnName = nexus_trans('label.name');
$columnPermission = nexus_trans('token.permission');
$columnCreatedAt = nexus_trans('label.created_at');
$actionCreate = nexus_trans('label.create');
$actionLabel = nexus_trans('label.action');
$res = $userInfo->tokens()->orderBy("id", "desc")->get();
if ($res->count() > 0)
{
$token .= "<table border='1' cellspacing='0' cellpadding='5' id='token-table'><tr><td class='colhead'>ID</td><td class='colhead'>{$columnName}</td><td class='colhead'>{$columnCreatedAt}</td><td class='colhead'>{$actionLabel}</td></tr>";
$token .= "<table border='1' cellspacing='0' cellpadding='5' id='token-table'><tr><td class='colhead'>ID</td><td class='colhead'>{$columnName}</td><td class='colhead'>{$columnPermission}</td><td class='colhead'>{$columnCreatedAt}</td><td class='colhead'>{$actionLabel}</td></tr>";
foreach ($res as $tokenRecord)
{
$token .= "<tr>";
$token .= sprintf('<td>%s</td>', $tokenRecord->id);
$token .= sprintf('<td>%s</td>', $tokenRecord->name);
$token .= sprintf('<td>%s</td>', $tokenRecord->abilitiesText);
$token .= sprintf('<td>%s</td>', $tokenRecord->created_at);
$token .= sprintf('<td><span style="cursor: pointer;margin-right: 10px" class="token-get">获取</span><span style="cursor: pointer" title="%s" data-id="%s" class="token-del">删除</span></td>', $lang_functions['text_delete'], $tokenRecord->id);
$token .= sprintf('<td><span style="cursor: pointer;margin-right: 10px" class="token-get" data-id="%s">获取</span><span style="cursor: pointer" title="%s" data-id="%s" class="token-del">删除</span></td>', $tokenRecord->id, $lang_functions['text_delete'], $tokenRecord->id);
$token .= "</tr>";
}
$token .= '</table>';
@@ -1152,6 +1164,10 @@ $tokenFoxForm = <<<FORM
<div class="label">{$columnName}</div>
<div class="field"><input type="text" name="name"></div>
</div>
<div class="form-control-row">
<div class="label">{$columnPermission}</div>
<div class="field">$permissionCheckbox</div>
</div>
</form>
</div>
FORM;
@@ -1195,6 +1211,19 @@ jQuery('#token-table').on('click', '.token-del', function () {
}, 'json')
})
});
jQuery('#token-table').on('click', '.token-get', function () {
let params = {id: jQuery(this).attr("data-id")}
jQuery('body').loading({stoppable: false});
jQuery.post('/web/token/get-plain', params, function (response) {
console.log(response)
jQuery('body').loading('stop');
if (response.ret != 0) {
layer.alert(response.msg)
} else {
layer.alert(response.data)
}
}, 'json')
});
JS;
\Nexus\Nexus::js($tokenBoxJs, 'footer', false);
+6 -8
View File
@@ -217,12 +217,10 @@ return [
'text' => '允许个性条',
'desc' => '允许用户使用个性条',
],
// 'not-counting-downloaded' => [
// 'text' => '不计下载量',
// 'desc' => '用户下载量不增加',
// ],
// 'not-counting-hit-and-run' => [
// 'text' => '不计 H&R',
// 'desc' => '下载带 H&R 标记的种子不记 H&R。注意:等级为 VIP 是固定不计的',
// ],
//新加
'torrent:list' => [
'text' => '获取种子列表',
'desc' => '获取种子列表',
],
];
+1
View File
@@ -2,4 +2,5 @@
return [
"label" => "访问令牌",
"permission" => "权限",
];
+2 -3
View File
@@ -2,6 +2,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Enums\Permission\PermissionEnum;
/*
|--------------------------------------------------------------------------
@@ -48,6 +49,7 @@ Route::group(['middleware' => ['auth:sanctum', 'locale']], function () {
Route::resource('topics', \App\Http\Controllers\TopicController::class);
Route::get('sections', [\App\Http\Controllers\UploadController::class, 'sections']);
Route::post('upload', [\App\Http\Controllers\UploadController::class, 'upload'])->middleware(ability(PermissionEnum::UPLOAD));
});
Route::group(['middleware' => ['admin']], function () {
@@ -99,6 +101,3 @@ Route::group(['middleware' => ['auth:sanctum', 'locale']], function () {
Route::post('login', [\App\Http\Controllers\AuthenticateController::class, 'login']);
Route::group(['middleware' => ['auth.nexus:passkey', 'locale']], function () {
Route::post("pieces-hash", [\App\Http\Controllers\TorrentController::class, "queryByPiecesHash"])->name("torrent.pieces_hash.query");
});
+5
View File
@@ -1,6 +1,11 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['middleware' => ['auth.nexus:passkey', 'locale']], function () {
Route::post("pieces-hash", [\App\Http\Controllers\TorrentController::class, "queryByPiecesHash"])->name("torrent.pieces_hash.query");
});
Route::post('nastools/approve', [\App\Http\Controllers\AuthenticateController::class, 'nasToolsApprove']);
Route::get('iyuu/approve', [\App\Http\Controllers\AuthenticateController::class, 'iyuuApprove']);
Route::post('ammds/approve', [\App\Http\Controllers\AuthenticateController::class, 'ammdsApprove']);
+3 -2
View File
@@ -21,8 +21,9 @@ Route::group(['prefix' => 'web', 'middleware' => ['auth.nexus:nexus-web', 'local
Route::get('torrent-approval-page', [\App\Http\Controllers\TorrentController::class, 'approvalPage']);
Route::get('torrent-approval-logs', [\App\Http\Controllers\TorrentController::class, 'approvalLogs']);
Route::post('torrent-approval', [\App\Http\Controllers\TorrentController::class, 'approval']);
Route::post('token/add', [\App\Http\Controllers\AuthenticateController::class, 'addToken']);
Route::post('token/del', [\App\Http\Controllers\AuthenticateController::class, 'delToken']);
Route::post('token/add', [\App\Http\Controllers\TokenController::class, 'addToken']);
Route::post('token/del', [\App\Http\Controllers\TokenController::class, 'delToken']);
Route::post('token/get-plain', [\App\Http\Controllers\TokenController::class, 'getPlainText']);
});
if (!isRunningInConsole()) {