From 7194c019568f5663c2824f93574057646dfa68c5 Mon Sep 17 00:00:00 2001 From: Ujang Sopiyan Date: Thu, 28 Aug 2025 15:07:45 +0700 Subject: [PATCH 1/4] feat: modul token api --- app/Filament/Resources/TokenResource.php | 117 ++++++++++++++++++ .../TokenResource/Pages/CreateToken.php | 42 +++++++ .../TokenResource/Pages/EditToken.php | 42 +++++++ .../TokenResource/Pages/ListTokens.php | 25 ++++ app/Models/Token.php | 43 +++++++ ..._plain_token_to_personal_access_tokens.php | 32 +++++ .../token-resource/copy-token.blade.php | 5 + .../livewire/copy-token-action.blade.php | 7 ++ 8 files changed, 313 insertions(+) create mode 100644 app/Filament/Resources/TokenResource.php create mode 100644 app/Filament/Resources/TokenResource/Pages/CreateToken.php create mode 100644 app/Filament/Resources/TokenResource/Pages/EditToken.php create mode 100644 app/Filament/Resources/TokenResource/Pages/ListTokens.php create mode 100644 app/Models/Token.php create mode 100644 database/migrations/2025_08_28_041441_add_plain_token_to_personal_access_tokens.php create mode 100644 resources/views/filament/resources/token-resource/copy-token.blade.php create mode 100644 resources/views/livewire/copy-token-action.blade.php diff --git a/app/Filament/Resources/TokenResource.php b/app/Filament/Resources/TokenResource.php new file mode 100644 index 00000000..79fe396b --- /dev/null +++ b/app/Filament/Resources/TokenResource.php @@ -0,0 +1,117 @@ +schema([ + Forms\Components\Card::make() + ->schema([ + Forms\Components\Grid::make() + ->schema([ + Forms\Components\TextInput::make('name') + ->label('Token Name') + ->required(), + + Forms\Components\Select::make('tokenable_id') + ->label('User') + ->searchable() + ->options(fn() => \App\Models\User::pluck('name', 'id')) + ->required(), + + // penting: tambahkan ini biar tokenable_type otomatis ke User + Forms\Components\Hidden::make('tokenable_type') + ->default(\App\Models\User::class) + ->required(), + + Forms\Components\DateTimePicker::make('expires_at') + ->label('Expired At') + ->default(now()->addYear()) + ->required(), + ]) + ]) + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('name')->sortable(), + Tables\Columns\TextColumn::make('tokenable.name') + ->label('User') + ->sortable() + ->searchable(), + + Tables\Columns\TextColumn::make('expires_at') + ->dateTime() + ->sortable(), + Tables\Columns\TextColumn::make('last_used_at') + ->dateTime() + ->sortable(), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->label('Created At'), + ]) + ->filters([]) + ->actions([ + Tables\Actions\Action::make('copy_token') + ->label('Copy Token') + ->icon('heroicon-o-clipboard') + ->color('success') // hijau + ->action(function (Token $record, $livewire) { + $livewire->dispatchBrowserEvent('copy-token', [ + 'token' => $record->plain_token, + ]); + + Notification::make() + ->success() + ->title('Token copied!') + ->send(); + }), + Tables\Actions\EditAction::make(), + Tables\Actions\DeleteAction::make(), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListTokens::route('/'), + 'create' => Pages\CreateToken::route('/create'), + 'edit' => Pages\EditToken::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/TokenResource/Pages/CreateToken.php b/app/Filament/Resources/TokenResource/Pages/CreateToken.php new file mode 100644 index 00000000..b8101ae9 --- /dev/null +++ b/app/Filament/Resources/TokenResource/Pages/CreateToken.php @@ -0,0 +1,42 @@ +createToken( + $data['name'], // nama token + ['*'], // abilities + Carbon::parse($data['expires_at']) // expired + ); + + // simpan plain token di DB + $accessToken = $token->accessToken; + $accessToken->plain_token = $token->plainTextToken; + $accessToken->save(); + + session()->flash('generated_token', $token->plainTextToken); + + return $token->accessToken; + } + + protected function getRedirectUrl(): string + { + // setelah simpan, redirect ke list + return $this->getResource()::getUrl('index'); + } +} diff --git a/app/Filament/Resources/TokenResource/Pages/EditToken.php b/app/Filament/Resources/TokenResource/Pages/EditToken.php new file mode 100644 index 00000000..7b067b1f --- /dev/null +++ b/app/Filament/Resources/TokenResource/Pages/EditToken.php @@ -0,0 +1,42 @@ +update([ + 'name' => $data['name'], + 'expires_at' => $data['expires_at'], + 'tokenable_id' => $data['tokenable_id'], + 'tokenable_type' => $data['tokenable_type'], + ]); + + return $record; + } + + protected function getRedirectUrl(): string + { + // setelah simpan, redirect ke list + return $this->getResource()::getUrl('index'); + } +} diff --git a/app/Filament/Resources/TokenResource/Pages/ListTokens.php b/app/Filament/Resources/TokenResource/Pages/ListTokens.php new file mode 100644 index 00000000..507e7e8a --- /dev/null +++ b/app/Filament/Resources/TokenResource/Pages/ListTokens.php @@ -0,0 +1,25 @@ + 'datetime', + 'expires_at' => 'datetime', + ]; + + /** + * Relasi ke User (atau model lain yg bisa punya token). + */ + public function user() + { + return $this->morphTo('tokenable'); + } + + public function tokenable() + { + return $this->morphTo(); + } +} diff --git a/database/migrations/2025_08_28_041441_add_plain_token_to_personal_access_tokens.php b/database/migrations/2025_08_28_041441_add_plain_token_to_personal_access_tokens.php new file mode 100644 index 00000000..ff1c8333 --- /dev/null +++ b/database/migrations/2025_08_28_041441_add_plain_token_to_personal_access_tokens.php @@ -0,0 +1,32 @@ +text('plain_token')->nullable()->after('token'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('personal_access_tokens', function (Blueprint $table) { + $table->dropColumn('plain_token'); + }); + } +}; diff --git a/resources/views/filament/resources/token-resource/copy-token.blade.php b/resources/views/filament/resources/token-resource/copy-token.blade.php new file mode 100644 index 00000000..e2a0a81f --- /dev/null +++ b/resources/views/filament/resources/token-resource/copy-token.blade.php @@ -0,0 +1,5 @@ + diff --git a/resources/views/livewire/copy-token-action.blade.php b/resources/views/livewire/copy-token-action.blade.php new file mode 100644 index 00000000..53ff49ff --- /dev/null +++ b/resources/views/livewire/copy-token-action.blade.php @@ -0,0 +1,7 @@ +
+ + Copy Token + + + +
From 75dad46ea32182c24127463d556d87f36e82bd4c Mon Sep 17 00:00:00 2001 From: Ujang Sopiyan Date: Fri, 29 Aug 2025 07:45:26 +0700 Subject: [PATCH 2/4] modify: tampilkan token --- app/Filament/Resources/TokenResource.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Filament/Resources/TokenResource.php b/app/Filament/Resources/TokenResource.php index 79fe396b..43d05e1b 100644 --- a/app/Filament/Resources/TokenResource.php +++ b/app/Filament/Resources/TokenResource.php @@ -63,6 +63,12 @@ public static function table(Table $table): Table return $table ->columns([ Tables\Columns\TextColumn::make('name')->sortable(), + + Tables\Columns\TextColumn::make('plain_token') + ->label('Token') + ->copyable() + ->toggleable(isToggledHiddenByDefault: false), + Tables\Columns\TextColumn::make('tokenable.name') ->label('User') ->sortable() From 524dbe2eaad938583b22b91916275f681a93b21d Mon Sep 17 00:00:00 2001 From: Ujang Sopiyan Date: Fri, 29 Aug 2025 11:10:59 +0700 Subject: [PATCH 3/4] fix: validasi token api --- app/Filament/Resources/TokenResource.php | 14 +++++++++-- app/Http/Kernel.php | 3 ++- app/Http/Middleware/CheckTokenExpiry.php | 30 ++++++++++++++++++++++++ app/Models/Token.php | 6 ++--- config/app.php | 2 +- routes/api.php | 10 +++++++- 6 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 app/Http/Middleware/CheckTokenExpiry.php diff --git a/app/Filament/Resources/TokenResource.php b/app/Filament/Resources/TokenResource.php index 43d05e1b..baa9332f 100644 --- a/app/Filament/Resources/TokenResource.php +++ b/app/Filament/Resources/TokenResource.php @@ -51,7 +51,7 @@ public static function form(Form $form): Form Forms\Components\DateTimePicker::make('expires_at') ->label('Expired At') - ->default(now()->addYear()) + ->default(now('Asia/Jakarta')->addYear()) ->required(), ]) ]) @@ -75,8 +75,18 @@ public static function table(Table $table): Table ->searchable(), Tables\Columns\TextColumn::make('expires_at') + ->label('Expired At') ->dateTime() - ->sortable(), + ->sortable() + ->formatStateUsing(function ($record) { + if ($record->expires_at && $record->expires_at->isPast()) { + return 'Token expired'; + } + return $record->expires_at ? $record->expires_at->format('M d, Y H:i:s') : '-'; + }) + ->color(function ($record) { + return $record->expires_at && $record->expires_at->isPast() ? 'danger' : null; + }), Tables\Columns\TextColumn::make('last_used_at') ->dateTime() ->sortable(), diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 00796881..27669f22 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -39,7 +39,8 @@ class Kernel extends HttpKernel ], 'api' => [ - // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + \App\Http\Middleware\CheckTokenExpiry::class, // middleware untuk cek token expired + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], diff --git a/app/Http/Middleware/CheckTokenExpiry.php b/app/Http/Middleware/CheckTokenExpiry.php new file mode 100644 index 00000000..c81a975e --- /dev/null +++ b/app/Http/Middleware/CheckTokenExpiry.php @@ -0,0 +1,30 @@ +bearerToken(); + + if ($token) { + // Cari token berdasarkan plain_token + $accessToken = \App\Models\Token::where('plain_token', $token)->first(); + + // Jika token ditemukan tetapi kadaluarsa + if ($accessToken && $accessToken->expires_at && $accessToken->expires_at->isPast()) { + return response()->json([ + 'message' => 'Token expired' + ], Response::HTTP_UNAUTHORIZED); + } + } + + return $next($request); + } +} diff --git a/app/Models/Token.php b/app/Models/Token.php index 4807eb1f..569cfd02 100644 --- a/app/Models/Token.php +++ b/app/Models/Token.php @@ -10,13 +10,13 @@ class Token extends Model protected $fillable = [ 'name', - 'tokenable_id', - 'tokenable_type', + 'token', 'abilities', 'last_used_at', 'expires_at', 'plain_token', - 'token', + 'tokenable_id', + 'tokenable_type', ]; protected $hidden = [ diff --git a/config/app.php b/config/app.php index ba0214db..3d44961b 100644 --- a/config/app.php +++ b/config/app.php @@ -79,7 +79,7 @@ | */ - 'timezone' => 'UTC', + 'timezone' => 'Asia/Jakarta', /* |-------------------------------------------------------------------------- diff --git a/routes/api.php b/routes/api.php index 0a3e046b..d3e8a4b5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -22,4 +22,12 @@ * Route API untuk webhook GitHub. * - Menggunakan controller GitHubWebhookController untuk menangani webhook. */ -Route::post('/github/webhook', [App\Http\Controllers\GitHubWebhookController::class, 'handle'])->name('github.webhook'); \ No newline at end of file +Route::post('/github/webhook', [App\Http\Controllers\GitHubWebhookController::class, 'handle'])->name('github.webhook'); + +// # test token api +// Route::middleware('auth:sanctum')->get('/test', function (Request $request) { +// return response()->json([ +// 'message' => 'Token valid!', +// 'user' => $request->user(), +// ]); +// }); From 2453fda21e3c07025fa3ed2c124013c22add8029 Mon Sep 17 00:00:00 2001 From: Afila Date: Fri, 29 Aug 2025 21:23:00 +0700 Subject: [PATCH 4/4] Update catatan_rilis.md --- catatan_rilis.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/catatan_rilis.md b/catatan_rilis.md index 4e6afaec..1fc39166 100644 --- a/catatan_rilis.md +++ b/catatan_rilis.md @@ -9,7 +9,8 @@ Di rilis v2507.0.0 berisi penambahan fitur dan perbaikan lain sesuai dengan pela 6. [#12](https://github.com/OpenSID/help-desk/issues/12) Fitur : Master Milestone 7. [#22](https://github.com/OpenSID/help-desk/issues/22) Fitur: Menambahkan username github pada data user 8. [#12](https://github.com/OpenSID/help-desk/issues/21) Fitur: Sinkron tiket ke github service issue dan project -98. [#13](https://github.com/OpenSID/help-desk/issues/13) Fitur: Laporan Tiket Dalam grafik +9. [#13](https://github.com/OpenSID/help-desk/issues/13) Fitur: Laporan Tiket Dalam grafik +10. [#96](https://github.com/OpenSID/DukunganTeknis/issues/96) Fitur: Modul token untuk API #### Perbaikan BUG