Kaynağa Gözat

Migrate from Passport to Sanctum for API

Will Browning 3 yıl önce
ebeveyn
işleme
5bfc5e5e9f
44 değiştirilmiş dosya ile 764 ekleme ve 1209 silme
  1. 1 5
      .env.example
  2. 1 0
      .gitignore
  3. 1 33
      SELF-HOSTING.md
  4. 0 63
      app/Console/Commands/EmailUsersWithTokenExpiringSoon.php
  5. 0 1
      app/Console/Kernel.php
  6. 3 3
      app/Http/Controllers/Api/AliasController.php
  7. 34 0
      app/Http/Controllers/Auth/PersonalAccessTokenController.php
  8. 1 1
      app/Http/Controllers/BrowserSessionController.php
  9. 1 1
      app/Http/Kernel.php
  10. 34 0
      app/Http/Requests/StorePersonalAccessTokenRequest.php
  11. 1 1
      app/Http/Resources/AliasResource.php
  12. 3 3
      app/Http/Resources/DomainResource.php
  13. 1 1
      app/Http/Resources/FailedDeliveryResource.php
  14. 21 0
      app/Http/Resources/PersonalAccessTokenResource.php
  15. 1 1
      app/Http/Resources/RecipientResource.php
  16. 4 1
      app/Models/EmailData.php
  17. 17 0
      app/Models/PersonalAccessToken.php
  18. 1 1
      app/Models/User.php
  19. 5 1
      app/Providers/AppServiceProvider.php
  20. 0 9
      app/Providers/AuthServiceProvider.php
  21. 3 2
      composer.json
  22. 127 638
      composer.lock
  23. 0 5
      config/auth.php
  24. 67 0
      config/sanctum.php
  25. 91 0
      database/migrations/2022_07_19_000001_create_personal_access_tokens_table.php
  26. 273 270
      package-lock.json
  27. 2 2
      resources/js/app.js
  28. 38 20
      resources/js/components/sanctum/PersonalAccessTokens.vue
  29. 0 13
      resources/views/mail/token_expiring_soon.blade.php
  30. 1 1
      resources/views/settings/show.blade.php
  31. 1 1
      routes/api.php
  32. 7 0
      routes/web.php
  33. 1 1
      tests/Feature/Api/AccountDetailsTest.php
  34. 1 1
      tests/Feature/Api/AliasRecipientsTest.php
  35. 1 1
      tests/Feature/Api/AliasesTest.php
  36. 12 34
      tests/Feature/Api/ApiTokensTest.php
  37. 1 1
      tests/Feature/Api/AppVersionTest.php
  38. 1 1
      tests/Feature/Api/DomainsTest.php
  39. 1 1
      tests/Feature/Api/FailedDeliveriesTest.php
  40. 1 1
      tests/Feature/Api/RecipientsTest.php
  41. 1 1
      tests/Feature/Api/RulesTest.php
  42. 1 1
      tests/Feature/Api/UsernamesTest.php
  43. 3 27
      tests/TestCase.php
  44. 0 62
      tests/Unit/EmailUsersWithTokenExpiringSoonTest.php

+ 1 - 5
.env.example

@@ -62,8 +62,4 @@ ANONADDY_ADDITIONAL_USERNAME_LIMIT=10
 ANONADDY_SIGNING_KEY_FINGERPRINT=
 # This is only needed if you will be adding any custom domains. If you do not need it then leave it blank. ANONADDY_DKIM_SIGNING_KEY=/etc/opendkim/keys/example.com/default.private
 ANONADDY_DKIM_SIGNING_KEY=
-ANONADDY_DKIM_SELECTOR=default
-
-# These details will be displayed after you run php artisan passport:install, you should update accordingly
-PASSPORT_PERSONAL_ACCESS_CLIENT_ID=client-id-value
-PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=unhashed-client-secret-value
+ANONADDY_DKIM_SELECTOR=default

+ 1 - 0
.gitignore

@@ -17,3 +17,4 @@ yarn-error.log
 .env
 .php-cs-fixer.cache
 .phpunit.result.cache
+ray.php

+ 1 - 33
SELF-HOSTING.md

@@ -1086,7 +1086,7 @@ Then update your `.env` file.
 ANONADDY_DKIM_SIGNING_KEY=/var/lib/rspamd/dkim/example.com.default.key
 ```
 
-Then we will generate an app key, migrate the database, link the storage directory, restart the queue and install laravel passport.
+Then we will generate an app key, migrate the database, link the storage directory, clear the cache and restart the queue.
 
 ```bash
 php artisan key:generate
@@ -1097,39 +1097,7 @@ php artisan config:cache
 php artisan view:cache
 php artisan route:cache
 php artisan queue:restart
-
-php artisan passport:install
-```
-
-Running `passport:install` will output details about a new personal access client, e.g.
-
-```bash
-Encryption keys generated successfully.
-Personal access client created successfully.
-Client ID: 1
-Client secret: MlVp37PNqtN9efBTw2wuenjMnMIlDuKBWK3GZQoJ
-Password grant client created successfully.
-Client ID: 2
-Client secret: ZTvhZCRZMdKUvmwqSmNAfWzAoaRatVWgbCVN2cR2
-```
-
-You need to update your `.env` file and add the details for the personal access client:
-
 ```
-PASSPORT_PERSONAL_ACCESS_CLIENT_ID=client-id-value
-PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=unhashed-client-secret-value
-```
-
-So I would enter:
-
-```
-PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1
-PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=MlVp37PNqtN9efBTw2wuenjMnMIlDuKBWK3GZQoJ
-```
-
-More information can be found in the Laravel documentation for Passport - [https://laravel.com/docs/8.x/passport](https://laravel.com/docs/8.x/passport)
-
-Then run `php artisan config:cache` again to reflect the changes.
 
 We also need to add a cronjob in order to run Laravel's schedules commands.
 

+ 0 - 63
app/Console/Commands/EmailUsersWithTokenExpiringSoon.php

@@ -1,63 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Mail\TokenExpiringSoon;
-use App\Models\User;
-use Exception;
-use Illuminate\Console\Command;
-use Illuminate\Support\Facades\Mail;
-
-class EmailUsersWithTokenExpiringSoon extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'anonaddy:email-users-with-token-expiring-soon';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = 'Send an email to users who have an API token that is expiring soon';
-
-    /**
-     * Create a new command instance.
-     *
-     * @return void
-     */
-    public function __construct()
-    {
-        parent::__construct();
-    }
-
-    /**
-     * Execute the console command.
-     *
-     * @return int
-     */
-    public function handle()
-    {
-        User::whereHas('tokens', function ($query) {
-            $query->whereDate('expires_at', now()->addWeek())
-                ->where('revoked', false);
-        })
-        ->get()
-        ->each(function (User $user) {
-            $this->sendTokenExpiringSoonMail($user);
-        });
-    }
-
-    protected function sendTokenExpiringSoonMail(User $user)
-    {
-        try {
-            Mail::to($user->email)->send(new TokenExpiringSoon($user));
-        } catch (Exception $exception) {
-            $this->error("exception when sending mail to user: {$user->username}", $exception);
-            report($exception);
-        }
-    }
-}

+ 0 - 1
app/Console/Kernel.php

@@ -25,7 +25,6 @@ class Kernel extends ConsoleKernel
     protected function schedule(Schedule $schedule)
     {
         $schedule->command('anonaddy:reset-bandwidth')->monthlyOn(1, '00:00');
-        $schedule->command('anonaddy:email-users-with-token-expiring-soon')->dailyAt('12:00');
         $schedule->command('anonaddy:check-domains-sending-verification')->daily();
         $schedule->command('anonaddy:check-domains-mx-validation')->daily();
         $schedule->command('anonaddy:clear-failed-deliveries')->daily();

+ 3 - 3
app/Http/Controllers/Api/AliasController.php

@@ -21,9 +21,9 @@ class AliasController extends Controller
                 $direction = strpos($sort, '-') === 0 ? 'desc' : 'asc';
 
                 return $query->orderBy(ltrim($sort, '-'), $direction);
-                }, function ($query) {
-                    return $query->latest();
-                })
+            }, function ($query) {
+                return $query->latest();
+            })
             ->when($request->input('filter.active'), function ($query, $value) {
                 $active = $value === 'true' ? true : false;
 

+ 34 - 0
app/Http/Controllers/Auth/PersonalAccessTokenController.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\StorePersonalAccessTokenRequest;
+use App\Http\Resources\PersonalAccessTokenResource;
+
+class PersonalAccessTokenController extends Controller
+{
+    public function index()
+    {
+        return PersonalAccessTokenResource::collection(user()->tokens);
+    }
+
+    public function store(StorePersonalAccessTokenRequest $request)
+    {
+        $token = user()->createToken($request->name);
+
+        return [
+            'token' => new PersonalAccessTokenResource($token->accessToken),
+            'accessToken' => explode('|', $token->plainTextToken, 2)[1]
+        ];
+    }
+
+    public function destroy($id)
+    {
+        $token = user()->tokens()->findOrFail($id);
+
+        $token->delete();
+
+        return response('', 204);
+    }
+}

+ 1 - 1
app/Http/Controllers/BrowserSessionController.php

@@ -10,7 +10,7 @@ class BrowserSessionController extends Controller
     public function destroy(Request $request)
     {
         $request->validate([
-            'current_password_sesssions' => 'password',
+            'current_password_sesssions' => 'current_password',
         ]);
 
         Auth::logoutOtherDevices($request->current_password_sesssions);

+ 1 - 1
app/Http/Kernel.php

@@ -37,10 +37,10 @@ class Kernel extends HttpKernel
             \Illuminate\View\Middleware\ShareErrorsFromSession::class,
             \App\Http\Middleware\VerifyCsrfToken::class,
             \Illuminate\Routing\Middleware\SubstituteBindings::class,
-            \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
         ],
 
         'api' => [
+            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
             'throttle:api',
             \Illuminate\Routing\Middleware\SubstituteBindings::class,
         ],

+ 34 - 0
app/Http/Requests/StorePersonalAccessTokenRequest.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class StorePersonalAccessTokenRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, mixed>
+     */
+    public function rules()
+    {
+        return [
+            'name' => [
+                'required',
+                'string',
+                'max:50'
+            ]
+        ];
+    }
+}

+ 1 - 1
app/Http/Resources/AliasResource.php

@@ -26,7 +26,7 @@ class AliasResource extends JsonResource
             'recipients' => RecipientResource::collection($this->whenLoaded('recipients')),
             'created_at' => $this->created_at->toDateTimeString(),
             'updated_at' => $this->updated_at->toDateTimeString(),
-            'deleted_at' => $this->deleted_at ? $this->deleted_at->toDateTimeString() : null,
+            'deleted_at' => $this->deleted_at?->toDateTimeString(),
         ];
     }
 }

+ 3 - 3
app/Http/Resources/DomainResource.php

@@ -17,9 +17,9 @@ class DomainResource extends JsonResource
             'default_recipient' => new RecipientResource($this->whenLoaded('defaultRecipient')),
             'active' => $this->active,
             'catch_all' => $this->catch_all,
-            'domain_verified_at' => $this->domain_verified_at ? $this->domain_verified_at->toDateTimeString() : null,
-            'domain_mx_validated_at' => $this->domain_mx_validated_at ? $this->domain_mx_validated_at->toDateTimeString() : null,
-            'domain_sending_verified_at' => $this->domain_sending_verified_at ? $this->domain_sending_verified_at->toDateTimeString() : null,
+            'domain_verified_at' => $this->domain_verified_at?->toDateTimeString(),
+            'domain_mx_validated_at' => $this->domain_mx_validated_at?->toDateTimeString(),
+            'domain_sending_verified_at' => $this->domain_sending_verified_at?->toDateTimeString(),
             'created_at' => $this->created_at->toDateTimeString(),
             'updated_at' => $this->updated_at->toDateTimeString(),
         ];

+ 1 - 1
app/Http/Resources/FailedDeliveryResource.php

@@ -21,7 +21,7 @@ class FailedDeliveryResource extends JsonResource
             'email_type' => $this->email_type,
             'status' => $this->status,
             'code' => $this->code,
-            'attempted_at' => $this->attempted_at ? $this->attempted_at->toDateTimeString() : null,
+            'attempted_at' => $this->attempted_at?->toDateTimeString(),
             'created_at' => $this->created_at->toDateTimeString(),
             'updated_at' => $this->updated_at->toDateTimeString(),
         ];

+ 21 - 0
app/Http/Resources/PersonalAccessTokenResource.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class PersonalAccessTokenResource extends JsonResource
+{
+    public function toArray($request)
+    {
+        return [
+            'id' => $this->id,
+            'user_id' => $this->tokenable_id,
+            'name' => $this->name,
+            'abilities' => $this->abilities,
+            'last_used_at' => $this->last_used_at?->toDateTimeString(),
+            'created_at' => $this->created_at->toDateTimeString(),
+            'updated_at' => $this->updated_at->toDateTimeString(),
+        ];
+    }
+}

+ 1 - 1
app/Http/Resources/RecipientResource.php

@@ -15,7 +15,7 @@ class RecipientResource extends JsonResource
             'can_reply_send' => $this->can_reply_send,
             'should_encrypt' => $this->should_encrypt,
             'fingerprint' => $this->fingerprint,
-            'email_verified_at' => $this->email_verified_at ? $this->email_verified_at->toDateTimeString() : null,
+            'email_verified_at' => $this->email_verified_at?->toDateTimeString(),
             'aliases' => AliasResource::collection($this->whenLoaded('aliases')),
             'created_at' => $this->created_at->toDateTimeString(),
             'updated_at' => $this->updated_at->toDateTimeString(),

+ 4 - 1
app/Models/EmailData.php

@@ -43,7 +43,10 @@ class EmailData
             $this->encryptedParts = $parser->getAttachments();
         } else {
             foreach ($parser->getAttachments() as $attachment) {
-                if ($attachment->getContentDisposition() === 'inline') {
+                // Incorrect content type "text", set as text/plain
+                if ($attachment->getContentType() === 'text') {
+                    $this->text = base64_encode(stream_get_contents($attachment->getStream()));
+                } elseif ($attachment->getContentDisposition() === 'inline') {
                     $this->inlineAttachments[] = [
                         'stream' => base64_encode(stream_get_contents($attachment->getStream())),
                         'file_name' => base64_encode($attachment->getFileName()),

+ 17 - 0
app/Models/PersonalAccessToken.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Models;
+
+use App\Traits\HasUuid;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
+
+class PersonalAccessToken extends SanctumPersonalAccessToken
+{
+    use HasFactory;
+    use HasUuid;
+
+    public $incrementing = false;
+
+    protected $keyType = 'string';
+}

+ 1 - 1
app/Models/User.php

@@ -12,7 +12,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
 use Illuminate\Notifications\Notifiable;
 use Illuminate\Support\Facades\App;
 use Illuminate\Support\Str;
-use Laravel\Passport\HasApiTokens;
+use Laravel\Sanctum\HasApiTokens;
 
 class User extends Authenticatable implements MustVerifyEmail
 {

+ 5 - 1
app/Providers/AppServiceProvider.php

@@ -2,12 +2,14 @@
 
 namespace App\Providers;
 
+use App\Models\PersonalAccessToken;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Pagination\LengthAwarePaginator;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Facades\Blade;
 use Illuminate\Support\ServiceProvider;
+use Laravel\Sanctum\Sanctum;
 
 class AppServiceProvider extends ServiceProvider
 {
@@ -18,7 +20,7 @@ class AppServiceProvider extends ServiceProvider
      */
     public function register()
     {
-        //
+        Sanctum::ignoreMigrations();
     }
 
     /**
@@ -30,6 +32,8 @@ class AppServiceProvider extends ServiceProvider
     {
         Blade::withoutComponentTags();
 
+        Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
+
         Builder::macro('jsonPaginate', function (int $maxResults = null, int $defaultSize = null) {
             $maxResults = $maxResults ?? 100;
             $defaultSize = $defaultSize ?? 100;

+ 0 - 9
app/Providers/AuthServiceProvider.php

@@ -3,7 +3,6 @@
 namespace App\Providers;
 
 use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
-use Laravel\Passport\Passport;
 
 class AuthServiceProvider extends ServiceProvider
 {
@@ -24,13 +23,5 @@ class AuthServiceProvider extends ServiceProvider
     public function boot()
     {
         $this->registerPolicies();
-
-        Passport::routes(function ($router) {
-            $router->forPersonalAccessTokens();
-        }, ['middleware' => ['web', 'auth', '2fa']]);
-
-        Passport::cookie('anonaddy_token');
-
-        Passport::personalAccessTokensExpireIn(now()->addYears(5));
     }
 }

+ 3 - 2
composer.json

@@ -13,7 +13,7 @@
         "doctrine/dbal": "^3.0",
         "guzzlehttp/guzzle": "^7.2",
         "laravel/framework": "^9.11",
-        "laravel/passport": "^10.0",
+        "laravel/sanctum": "^2.15",
         "laravel/tinker": "^2.7",
         "laravel/ui": "^3.0",
         "maatwebsite/excel": "^3.1",
@@ -28,7 +28,8 @@
         "mockery/mockery": "^1.4.4",
         "nunomaduro/collision": "^6.1",
         "phpunit/phpunit": "^9.5.10",
-        "spatie/laravel-ignition": "^1.0"
+        "spatie/laravel-ignition": "^1.0",
+        "spatie/laravel-ray": "^1.29"
     },
     "config": {
         "optimize-autoloader": true,

Dosya farkı çok büyük olduğundan ihmal edildi
+ 127 - 638
composer.lock


+ 0 - 5
config/auth.php

@@ -40,11 +40,6 @@ return [
             'driver' => 'session',
             'provider' => 'users',
         ],
-
-        'api' => [
-            'driver' => 'passport',
-            'provider' => 'users',
-        ],
     ],
 
     /*

+ 67 - 0
config/sanctum.php

@@ -0,0 +1,67 @@
+<?php
+
+use Laravel\Sanctum\Sanctum;
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Stateful Domains
+    |--------------------------------------------------------------------------
+    |
+    | Requests from the following domains / hosts will receive stateful API
+    | authentication cookies. Typically, these should include your local
+    | and production domains which access your API via a frontend SPA.
+    |
+    */
+
+    'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
+        '%s%s',
+        'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
+        Sanctum::currentApplicationUrlWithPort()
+    ))),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Sanctum Guards
+    |--------------------------------------------------------------------------
+    |
+    | This array contains the authentication guards that will be checked when
+    | Sanctum is trying to authenticate a request. If none of these guards
+    | are able to authenticate the request, Sanctum will use the bearer
+    | token that's present on an incoming request for authentication.
+    |
+    */
+
+    'guard' => ['web'],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Expiration Minutes
+    |--------------------------------------------------------------------------
+    |
+    | This value controls the number of minutes until an issued token will be
+    | considered expired. If this value is null, personal access tokens do
+    | not expire. This won't tweak the lifetime of first-party sessions.
+    |
+    */
+
+    'expiration' => null,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Sanctum Middleware
+    |--------------------------------------------------------------------------
+    |
+    | When authenticating your first-party SPA with Sanctum you may need to
+    | customize some of the middleware Sanctum uses while processing the
+    | request. You may change the middleware listed below as required.
+    |
+    */
+
+    'middleware' => [
+        'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
+        'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
+    ],
+
+];

+ 91 - 0
database/migrations/2022_07_19_000001_create_personal_access_tokens_table.php

@@ -0,0 +1,91 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreatePersonalAccessTokensTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('personal_access_tokens', function (Blueprint $table) {
+            $table->uuid('id');
+            $table->uuidMorphs('tokenable');
+            $table->string('name');
+            $table->string('token', 64)->unique();
+            $table->text('abilities')->nullable();
+            $table->timestamp('last_used_at')->nullable();
+            $table->timestamps();
+
+            $table->primary('id');
+        });
+
+        // Drop Laravel Passport tables
+        Schema::dropIfExists('oauth_auth_codes');
+        Schema::dropIfExists('oauth_access_tokens');
+        Schema::dropIfExists('oauth_refresh_tokens');
+        Schema::dropIfExists('oauth_clients');
+        Schema::dropIfExists('oauth_personal_access_clients');
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('personal_access_tokens');
+
+        // Re-create Laravel Passport tables
+        Schema::create('oauth_auth_codes', function (Blueprint $table) {
+            $table->string('id', 100)->primary();
+            $table->uuid('user_id');
+            $table->unsignedInteger('client_id');
+            $table->text('scopes')->nullable();
+            $table->boolean('revoked');
+            $table->dateTime('expires_at')->nullable();
+        });
+
+        Schema::create('oauth_access_tokens', function (Blueprint $table) {
+            $table->string('id', 100)->primary();
+            $table->uuid('user_id')->index()->nullable();
+            $table->unsignedInteger('client_id');
+            $table->string('name')->nullable();
+            $table->text('scopes')->nullable();
+            $table->boolean('revoked');
+            $table->timestamps();
+            $table->dateTime('expires_at')->nullable();
+        });
+
+        Schema::create('oauth_refresh_tokens', function (Blueprint $table) {
+            $table->string('id', 100)->primary();
+            $table->string('access_token_id', 100)->index();
+            $table->boolean('revoked');
+            $table->dateTime('expires_at')->nullable();
+        });
+
+        Schema::create('oauth_clients', function (Blueprint $table) {
+            $table->increments('id');
+            $table->uuid('user_id')->index()->nullable();
+            $table->string('name');
+            $table->string('secret', 100);
+            $table->text('redirect');
+            $table->boolean('personal_access_client');
+            $table->boolean('password_client');
+            $table->boolean('revoked');
+            $table->timestamps();
+        });
+
+        Schema::create('oauth_personal_access_clients', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('client_id')->index();
+            $table->timestamps();
+        });
+    }
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 273 - 270
package-lock.json


+ 2 - 2
resources/js/app.js

@@ -33,8 +33,8 @@ Vue.component('rules', require('./pages/Rules.vue').default)
 Vue.component('failed-deliveries', require('./pages/FailedDeliveries.vue').default)
 
 Vue.component(
-  'passport-personal-access-tokens',
-  require('./components/passport/PersonalAccessTokens.vue').default
+  'personal-access-tokens',
+  require('./components/sanctum/PersonalAccessTokens.vue').default
 )
 Vue.component('webauthn-keys', require('./components/WebauthnKeys.vue').default)
 

+ 38 - 20
resources/js/components/passport/PersonalAccessTokens.vue → resources/js/components/sanctum/PersonalAccessTokens.vue

@@ -29,16 +29,17 @@
         class="text-indigo-700"
         >Chrome / Brave</a
       >
-      to generate new aliases. Simply paste the token generated below into the browser extension to
-      get started. Your API access tokens are secret and should be treated like your password. For
-      more information please see the <a href="/docs" class="text-indigo-700">API documentation</a>.
+      to create new aliases. They can also be used with the mobile apps. Simply paste a token you've
+      created into the browser extension or mobile apps to get started. Your API access tokens are
+      secret and should be treated like your password. For more information please see the
+      <a href="/docs" class="text-indigo-700">API documentation</a>.
     </p>
 
     <button
       @click="openCreateTokenModal"
       class="bg-cyan-400 w-full hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
     >
-      Generate New Token
+      Create New Token
     </button>
 
     <div class="mt-6">
@@ -47,8 +48,8 @@
       <div class="my-4 w-24 border-b-2 border-grey-200"></div>
 
       <p class="my-6">
-        Tokens you have generated that can be used to access the API. To revoke an access token
-        simply click the delete button next to it.
+        Tokens you have created that can be used to access the API. To revoke an access token simply
+        click the delete button next to it.
       </p>
 
       <div>
@@ -60,7 +61,7 @@
           <div class="table-row">
             <div class="table-cell p-1 md:p-4 font-semibold">Name</div>
             <div class="table-cell p-1 md:p-4 font-semibold">Created</div>
-            <div class="table-cell p-1 md:p-4 font-semibold">Expires</div>
+            <div class="table-cell p-1 md:p-4 font-semibold">Last Used</div>
             <div class="table-cell p-1 md:p-4"></div>
           </div>
           <div
@@ -70,7 +71,10 @@
           >
             <div class="table-cell p-1 md:p-4">{{ token.name }}</div>
             <div class="table-cell p-1 md:p-4">{{ token.created_at | timeAgo }}</div>
-            <div class="table-cell p-1 md:p-4">{{ token.expires_at | timeAgo }}</div>
+            <div v-if="token.last_used_at" class="table-cell p-1 md:p-4">
+              {{ token.last_used_at | timeAgo }}
+            </div>
+            <div v-else class="table-cell p-1 md:p-4">Not used yet</div>
             <div class="table-cell p-1 md:p-4 text-right">
               <a
                 class="text-red-500 font-bold cursor-pointer focus:outline-none"
@@ -118,7 +122,7 @@
             :class="loading ? 'cursor-not-allowed' : ''"
             :disabled="loading"
           >
-            Generate Token
+            Create Token
             <loader v-if="loading" />
           </button>
           <button
@@ -141,8 +145,10 @@
         </p>
         <textarea
           v-model="accessToken"
-          class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 text-sm"
-          rows="10"
+          @click="selectTokenTextArea"
+          id="token-text-area"
+          class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 text-md break-all"
+          rows="1"
           readonly
         >
         </textarea>
@@ -226,8 +232,8 @@ export default {
 
   methods: {
     getTokens() {
-      axios.get('/oauth/personal-access-tokens').then(response => {
-        this.tokens = response.data
+      axios.get('/settings/personal-access-tokens').then(response => {
+        this.tokens = response.data.data
       })
     },
     store() {
@@ -236,7 +242,7 @@ export default {
       this.form.errors = []
 
       axios
-        .post('/oauth/personal-access-tokens', this.form)
+        .post('/settings/personal-access-tokens', this.form)
         .then(response => {
           this.loading = false
           this.form.name = ''
@@ -261,12 +267,19 @@ export default {
     revoke() {
       this.revokeTokenLoading = true
 
-      axios.delete(`/oauth/personal-access-tokens/${this.tokenToRevoke.id}`).then(response => {
-        this.revokeTokenLoading = false
-        this.revokeTokenModalOpen = false
-        this.tokenToRevoke = null
-        this.getTokens()
-      })
+      axios
+        .delete(`/settings/personal-access-tokens/${this.tokenToRevoke.id}`)
+        .then(response => {
+          this.revokeTokenLoading = false
+          this.revokeTokenModalOpen = false
+          this.tokenToRevoke = null
+          this.getTokens()
+        })
+        .catch(error => {
+          this.revokeTokenLoading = false
+          this.revokeTokenModalOpen = false
+          this.error()
+        })
     },
     openCreateTokenModal() {
       this.accessToken = null
@@ -278,6 +291,11 @@ export default {
     closeRevokeTokenModal() {
       this.revokeTokenModalOpen = false
     },
+    selectTokenTextArea() {
+      let textArea = document.getElementById('token-text-area')
+      textArea.focus()
+      textArea.select()
+    },
     clipboardSuccess() {
       this.success('Copied to clipboard')
     },

+ 0 - 13
resources/views/mail/token_expiring_soon.blade.php

@@ -1,13 +0,0 @@
-@component('mail::message')
-
-# Your API token expires soon
-
-One of the API tokens on your AnonAddy account will expire in **one weeks time**.</p>
-
-If you are not using this API token for the browser extension or to access the API then you do not need to take any action.
-
-If you **are using the token** for the browser extension please log into your account and generate a new API token and add this to the browser extension before your current one expires.
-
-Once an API token has expired it can no longer be used to access the API.
-
-@endcomponent

+ 1 - 1
resources/views/settings/show.blade.php

@@ -679,7 +679,7 @@
 
         <div class="px-6 py-8 md:p-10 bg-white rounded-lg shadow mb-10">
 
-            <passport-personal-access-tokens />
+            <personal-access-tokens />
 
         </div>
 

+ 1 - 1
routes/api.php

@@ -36,7 +36,7 @@ use Illuminate\Support\Facades\Route;
 */
 
 Route::group([
-  'middleware' => ['auth:api', 'verified'],
+  'middleware' => ['auth:sanctum', 'verified'],
   'prefix' => 'v1'
 ], function () {
     Route::controller(AliasController::class)->group(function () {

+ 7 - 0
routes/web.php

@@ -3,6 +3,7 @@
 use App\Http\Controllers\AliasExportController;
 use App\Http\Controllers\Auth\BackupCodeController;
 use App\Http\Controllers\Auth\ForgotUsernameController;
+use App\Http\Controllers\Auth\PersonalAccessTokenController;
 use App\Http\Controllers\Auth\TwoFactorAuthController;
 use App\Http\Controllers\Auth\WebauthnController;
 use App\Http\Controllers\Auth\WebauthnEnabledKeyController;
@@ -129,5 +130,11 @@ Route::group([
 
     Route::post('/2fa/new-backup-code', [BackupCodeController::class, 'update'])->name('settings.new_backup_code');
 
+    Route::controller(PersonalAccessTokenController::class)->group(function () {
+        Route::get('/personal-access-tokens', 'index')->name('personal_access_tokens.index');
+        Route::post('/personal-access-tokens', 'store')->name('personal_access_tokens.store');
+        Route::delete('/personal-access-tokens/{id}', 'destroy')->name('personal_access_tokens.destroy');
+    });
+
     Route::get('/aliases/export', [AliasExportController::class, 'export'])->name('aliases.export');
 });

+ 1 - 1
tests/Feature/Api/AccountDetailsTest.php

@@ -12,7 +12,7 @@ class AccountDetailsTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
     }
 
     /** @test */

+ 1 - 1
tests/Feature/Api/AliasRecipientsTest.php

@@ -15,7 +15,7 @@ class AliasRecipientsTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
     }
 
     /** @test */

+ 1 - 1
tests/Feature/Api/AliasesTest.php

@@ -16,7 +16,7 @@ class AliasesTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
 
         $this->user->recipients()->save($this->user->defaultRecipient);
         $this->user->usernames()->save($this->user->defaultUsername);

+ 12 - 34
tests/Feature/Api/ApiTokensTest.php

@@ -4,9 +4,7 @@ namespace Tests\Feature\Api;
 
 use App\Models\User;
 use Illuminate\Foundation\Testing\RefreshDatabase;
-use Illuminate\Support\Facades\DB;
-use Laravel\Passport\ClientRepository;
-use Laravel\Passport\Passport;
+use Laravel\Sanctum\Sanctum;
 use Tests\TestCase;
 
 class ApiTokensTest extends TestCase
@@ -20,60 +18,40 @@ class ApiTokensTest extends TestCase
         parent::setUp();
 
         $this->user = User::factory()->create();
-        Passport::actingAs($this->user, [], 'web');
+        Sanctum::actingAs($this->user, [], 'web');
         $this->user->recipients()->save($this->user->defaultRecipient);
-
-        $clientRepository = new ClientRepository();
-        $client = $clientRepository->createPersonalAccessClient(
-            null,
-            'Test Personal Access Client',
-            config('app.url')
-        );
-        DB::table('oauth_personal_access_clients')->insert([
-            'client_id' => $client->id,
-            'created_at' => now(),
-            'updated_at' => now(),
-        ]);
     }
 
     /** @test */
-    public function user_can_generate_api_token()
+    public function user_can_create_api_token()
     {
-        $response = $this->post('/oauth/personal-access-tokens', [
+        $response = $this->post('/settings/personal-access-tokens', [
             'name' => 'New'
         ]);
 
         $response->assertStatus(200);
 
         $this->assertNotNull($response->getData()->accessToken);
-        $this->assertDatabaseHas('oauth_access_tokens', [
+        $this->assertDatabaseHas('personal_access_tokens', [
             'name' => 'New',
-            'user_id' => $this->user->id
+            'tokenable_id' => $this->user->id
         ]);
     }
 
     /** @test */
     public function user_can_revoke_api_token()
     {
-        DB::table('oauth_access_tokens')->insert([
-            'id' => '1830c31e8e17dc4e871aa21ebe82e6cbfdd0d5781bec42631dd381119f355a911075f7e1a3dc2240',
-            'name' => 'New',
-            'user_id' => $this->user->id,
-            'revoked' => false,
-            'client_id' => 1,
-            'created_at' => now(),
-            'updated_at' => now(),
-        ]);
+        $token = $this->user->createToken('New');
 
-        $response = $this->delete('/oauth/personal-access-tokens/1830c31e8e17dc4e871aa21ebe82e6cbfdd0d5781bec42631dd381119f355a911075f7e1a3dc2240');
+        $response = $this->delete("/settings/personal-access-tokens/{$token->accessToken->id}");
 
         $response->assertStatus(204);
 
-        $this->assertDatabaseMissing('oauth_access_tokens', [
+        $this->assertEmpty($this->user->tokens);
+        $this->assertDatabaseMissing('personal_access_tokens', [
             'name' => 'New',
-            'user_id' => $this->user->id,
-            'id' => '1830c31e8e17dc4e871aa21ebe82e6cbfdd0d5781bec42631dd381119f355a911075f7e1a3dc2240',
-            'revoke' => true
+            'tokenable_id' => $this->user->id,
+            'id' => $token->accessToken->id
         ]);
     }
 }

+ 1 - 1
tests/Feature/Api/AppVersionTest.php

@@ -13,7 +13,7 @@ class AppVersionTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
     }
 
     /** @test */

+ 1 - 1
tests/Feature/Api/DomainsTest.php

@@ -14,7 +14,7 @@ class DomainsTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
     }
 
     /** @test */

+ 1 - 1
tests/Feature/Api/FailedDeliveriesTest.php

@@ -13,7 +13,7 @@ class FailedDeliveriesTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
 
         $this->user->recipients()->save($this->user->defaultRecipient);
     }

+ 1 - 1
tests/Feature/Api/RecipientsTest.php

@@ -14,7 +14,7 @@ class RecipientsTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
     }
 
     /** @test */

+ 1 - 1
tests/Feature/Api/RulesTest.php

@@ -18,7 +18,7 @@ class RulesTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
 
         $this->user->recipients()->save($this->user->defaultRecipient);
         $this->user->usernames()->save($this->user->defaultUsername);

+ 1 - 1
tests/Feature/Api/UsernamesTest.php

@@ -16,7 +16,7 @@ class UsernamesTest extends TestCase
     protected function setUp(): void
     {
         parent::setUp();
-        parent::setUpPassport();
+        parent::setUpSanctum();
 
         $this->user->recipients()->save($this->user->defaultRecipient);
         $this->user->usernames()->save($this->user->defaultUsername);

+ 3 - 27
tests/TestCase.php

@@ -5,10 +5,8 @@ namespace Tests;
 use App\Models\User;
 use Illuminate\Database\Eloquent\Collection as EloquentCollection;
 use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
-use Illuminate\Support\Facades\DB;
 use Illuminate\Testing\TestResponse;
-use Laravel\Passport\ClientRepository;
-use Laravel\Passport\Passport;
+use Laravel\Sanctum\Sanctum;
 use PHPUnit\Framework\Assert;
 
 abstract class TestCase extends BaseTestCase
@@ -41,31 +39,9 @@ abstract class TestCase extends BaseTestCase
         });
     }
 
-    protected function setUpPassport(): void
+    protected function setUpSanctum(): void
     {
         $this->user = User::factory()->create();
-        Passport::actingAs($this->user, []);
-
-        $clientRepository = new ClientRepository();
-        $client = $clientRepository->createPersonalAccessClient(
-            null,
-            'Test Personal Access Client',
-            config('app.url')
-        );
-        DB::table('oauth_personal_access_clients')->insert([
-            'client_id' => $client->id,
-            'created_at' => now(),
-            'updated_at' => now(),
-        ]);
-
-        DB::table('oauth_access_tokens')->insert([
-            'id' => '1830c31e8e17dc4e871aa21ebe82e6cbfdd0d5781bec42631dd381119f355a911075f7e1a3dc2240',
-            'name' => 'New',
-            'user_id' => $this->user->id,
-            'revoked' => false,
-            'client_id' => 1,
-            'created_at' => now(),
-            'updated_at' => now(),
-        ]);
+        Sanctum::actingAs($this->user, []);
     }
 }

+ 0 - 62
tests/Unit/EmailUsersWithTokenExpiringSoonTest.php

@@ -1,62 +0,0 @@
-<?php
-
-namespace Tests\Unit;
-
-use App\Mail\TokenExpiringSoon;
-use Carbon\Carbon;
-use Illuminate\Foundation\Testing\RefreshDatabase;
-use Illuminate\Support\Facades\Mail;
-use Tests\TestCase;
-
-class EmailUsersWithTokenExpiringSoonTest extends TestCase
-{
-    use RefreshDatabase;
-
-    protected $user;
-
-    protected function setUp(): void
-    {
-        parent::setUp();
-        parent::setUpPassport();
-        $this->user->tokens()->first()->update(['expires_at' => Carbon::create(2019, 1, 31)]);
-
-        Mail::fake();
-    }
-
-    /** @test */
-    public function it_can_send_a_mail_concerning_a_token_expiring_soon()
-    {
-        $this->setNow(2019, 1, 28);
-        $this->artisan('anonaddy:email-users-with-token-expiring-soon');
-        Mail::assertNotQueued(TokenExpiringSoon::class);
-
-        $this->setNow(2019, 1, 29);
-        $this->artisan('anonaddy:email-users-with-token-expiring-soon');
-        Mail::assertNotQueued(TokenExpiringSoon::class);
-
-        $this->setNow(2019, 1, 24);
-        $this->artisan('anonaddy:email-users-with-token-expiring-soon');
-        Mail::assertQueued(TokenExpiringSoon::class, 1);
-        Mail::assertQueued(TokenExpiringSoon::class, function (TokenExpiringSoon $mail) {
-            return $mail->hasTo($this->user->email);
-        });
-    }
-
-    /** @test */
-    public function it_does_not_send_a_mail_for_revoked_tokens()
-    {
-        $this->user->tokens()->first()->revoke();
-        $this->setNow(2019, 1, 24);
-        $this->artisan('anonaddy:email-users-with-token-expiring-soon');
-        Mail::assertNotQueued(TokenExpiringSoon::class);
-    }
-
-    protected function setNow(int $year, int $month, int $day)
-    {
-        $newNow = Carbon::create($year, $month, $day)->startOfDay();
-
-        Carbon::setTestNow($newNow);
-
-        return $this;
-    }
-}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor