Fix tenant event form package selector so it no longer renders empty-value options, handles loading/empty
states, and pulls data from the authenticated /api/v1/tenant/packages endpoint.
(resources/js/admin/pages/EventFormPage.tsx, resources/js/admin/api.ts)
- Harden tenant-admin auth flow: prevent PKCE state loss, scope out StrictMode double-processing, add SPA
routes for /event-admin/login and /event-admin/logout, and tighten token/session clearing semantics (resources/js/admin/auth/{context,tokens}.tsx, resources/js/admin/pages/{AuthCallbackPage,LogoutPage}.tsx,
resources/js/admin/router.tsx, routes/web.php)
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$addedTokenHashColumn = false;
|
||||
|
||||
Schema::table('event_join_tokens', function (Blueprint $table) use (&$addedTokenHashColumn) {
|
||||
if (!Schema::hasColumn('event_join_tokens', 'token_hash')) {
|
||||
$table->string('token_hash', 128)->nullable()->after('token');
|
||||
$table->index('token_hash', 'event_join_tokens_token_hash_index');
|
||||
$addedTokenHashColumn = true;
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('event_join_tokens', 'token_encrypted')) {
|
||||
$table->text('token_encrypted')->nullable()->after('token_hash');
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('event_join_tokens', 'token_preview')) {
|
||||
$table->string('token_preview', 32)->nullable()->after('token_encrypted');
|
||||
}
|
||||
});
|
||||
|
||||
DB::table('event_join_tokens')
|
||||
->whereNull('token_hash')
|
||||
->whereNotNull('token')
|
||||
->orderBy('id')
|
||||
->chunkById(200, function ($tokens) {
|
||||
foreach ($tokens as $token) {
|
||||
$hash = hash('sha256', $token->token);
|
||||
$preview = $this->previewToken($token->token);
|
||||
|
||||
DB::table('event_join_tokens')
|
||||
->where('id', $token->id)
|
||||
->update([
|
||||
'token_hash' => $hash,
|
||||
'token_encrypted' => Crypt::encryptString($token->token),
|
||||
'token_preview' => $preview,
|
||||
'token' => $hash,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
if ($addedTokenHashColumn) {
|
||||
Schema::table('event_join_tokens', function (Blueprint $table) {
|
||||
$table->unique('token_hash', 'event_join_tokens_token_hash_unique');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('event_join_tokens', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('event_join_tokens', 'token_hash')) {
|
||||
try {
|
||||
$table->dropUnique('event_join_tokens_token_hash_unique');
|
||||
} catch (\Throwable $e) {
|
||||
// Unique index might already be absent (e.g. partial rollback).
|
||||
}
|
||||
|
||||
try {
|
||||
$table->dropIndex('event_join_tokens_token_hash_index');
|
||||
} catch (\Throwable $e) {
|
||||
// Index might already be absent.
|
||||
}
|
||||
}
|
||||
|
||||
$columns = collect(['token_hash', 'token_encrypted', 'token_preview'])
|
||||
->filter(fn ($column) => Schema::hasColumn('event_join_tokens', $column))
|
||||
->all();
|
||||
|
||||
if (!empty($columns)) {
|
||||
$table->dropColumn($columns);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function previewToken(string $token): string
|
||||
{
|
||||
$length = strlen($token);
|
||||
|
||||
if ($length <= 10) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
return substr($token, 0, 6).'…'.substr($token, -4);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('photos', function (Blueprint $table) {
|
||||
$table->string('security_scan_status')->default('pending')->after('metadata');
|
||||
$table->text('security_scan_message')->nullable()->after('security_scan_status');
|
||||
$table->timestamp('security_scanned_at')->nullable()->after('security_scan_message');
|
||||
$table->json('security_meta')->nullable()->after('security_scanned_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('photos', function (Blueprint $table) {
|
||||
$table->dropColumn(['security_scan_status', 'security_scan_message', 'security_scanned_at', 'security_meta']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('refresh_token_audits', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('refresh_token_id');
|
||||
$table->string('tenant_id')->index();
|
||||
$table->string('client_id')->nullable()->index();
|
||||
$table->string('event', 64)->index();
|
||||
$table->json('context')->nullable();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->foreignId('performed_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
|
||||
$table->foreign('refresh_token_id')
|
||||
->references('id')
|
||||
->on('refresh_tokens')
|
||||
->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('refresh_token_audits');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('refresh_tokens', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('refresh_tokens', 'last_used_at')) {
|
||||
$table->timestamp('last_used_at')->nullable()->after('expires_at');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('refresh_tokens', 'revoked_reason')) {
|
||||
$table->string('revoked_reason', 64)->nullable()->after('revoked_at');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('refresh_tokens', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('refresh_tokens', 'last_used_at')) {
|
||||
$table->dropColumn('last_used_at');
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('refresh_tokens', 'revoked_reason')) {
|
||||
$table->dropColumn('revoked_reason');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('event_join_token_events', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('event_join_token_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->unsignedBigInteger('event_id')->nullable()->index();
|
||||
$table->unsignedBigInteger('tenant_id')->nullable()->index();
|
||||
$table->string('token_hash', 64)->nullable()->index();
|
||||
$table->string('token_preview', 32)->nullable();
|
||||
$table->string('event_type', 32);
|
||||
$table->string('route', 100)->nullable()->index();
|
||||
$table->string('http_method', 16)->nullable();
|
||||
$table->unsignedSmallInteger('http_status')->nullable();
|
||||
$table->string('device_id', 64)->nullable();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->json('context')->nullable();
|
||||
$table->timestamp('occurred_at')->useCurrent();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['event_join_token_id', 'occurred_at'], 'event_join_token_events_token_time_idx');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('event_join_token_events');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ class OAuthClientSeeder extends Seeder
|
||||
$serviceConfig = config('services.oauth.tenant_admin', []);
|
||||
|
||||
$clientId = $serviceConfig['id'] ?? 'tenant-admin-app';
|
||||
$tenantId = Tenant::where('slug', 'demo')->value('id')
|
||||
$tenantId = Tenant::where('slug', 'demo-tenant')->value('id')
|
||||
?? Tenant::query()->orderBy('id')->value('id');
|
||||
|
||||
$redirectUris = Arr::wrap($serviceConfig['redirects'] ?? []);
|
||||
|
||||
Reference in New Issue
Block a user