'datetime', 'last_used_at' => 'datetime', 'revoked_at' => 'datetime', 'created_at' => 'datetime', ]; public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class); } public function audits(): HasMany { return $this->hasMany(RefreshTokenAudit::class); } public function revoke(?string $reason = null, ?int $performedBy = null, ?Request $request = null, array $context = []): bool { $result = $this->update([ 'revoked_at' => now(), 'revoked_reason' => $reason, ]); $event = match ($reason) { 'rotated' => 'rotated', 'ip_mismatch' => 'ip_mismatch', 'expired' => 'expired', 'invalid_secret' => 'invalid_secret', 'tenant_missing' => 'tenant_missing', 'max_active_limit' => 'max_active_limit', default => 'revoked', }; $this->recordAudit( $event, array_merge([ 'reason' => $reason, ], $context), $performedBy, $request ); return $result; } public function isActive(): bool { if ($this->revoked_at !== null) { return false; } return $this->expires_at === null || $this->expires_at->isFuture(); } public function scopeActive($query) { return $query ->whereNull('revoked_at') ->where(function ($inner) { $inner->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }); } public function scopeForTenant($query, string $tenantId) { return $query->where('tenant_id', $tenantId); } public function recordAudit(string $event, array $context = [], ?int $performedBy = null, ?Request $request = null): void { $request ??= request(); $this->audits()->create([ 'tenant_id' => $this->tenant_id, 'client_id' => $this->client_id, 'event' => $event, 'context' => $context ?: null, 'ip_address' => $request?->ip(), 'user_agent' => $request?->userAgent(), 'performed_by' => $performedBy, 'created_at' => now(), ]); } }