'boolean', 'expires_at' => 'datetime', 'created_at' => 'datetime', 'last_used_at' => 'datetime', ]; } /** * Generate a new service token * * @param string $name Token name (e.g., "GeoNetAgent-Production") * @param string $service Service name (e.g., "GeoNetAgent") * @param string|null $createdBy User who created the token * @param int|null $expiresInDays Expiration in days (null = never expires) * @return array ['token' => string, 'model' => ServiceToken] */ public static function generate( string $name, string $service, ?string $createdBy = null, ?int $expiresInDays = null ): array { $token = bin2hex(random_bytes(32)); // 64 chars hex $tokenPrefix = substr($token, 0, 16); $tokenHash = hash('sha256', $token); $expiresAt = $expiresInDays ? now()->addDays($expiresInDays) : null; $model = self::create([ 'id' => (string) \Illuminate\Support\Str::uuid(), 'name' => $name, 'service' => $service, 'token_prefix' => $tokenPrefix, 'token_hash' => $tokenHash, 'is_active' => true, 'expires_at' => $expiresAt, 'created_by' => $createdBy, ]); return [ 'token' => $token, 'model' => $model, ]; } /** * Verify a token against the hash * * @param string $token Plain token * @return ServiceToken|null */ public static function verify(string $token): ?self { $tokenHash = hash('sha256', $token); return self::where('token_hash', $tokenHash) ->where('is_active', true) ->where(function ($query) { $query->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }) ->first(); } /** * Update last used timestamp */ public function touchLastUsed(): void { $this->update(['last_used_at' => now()]); } /** * Scope for active tokens */ public function scopeActive($query) { return $query->where('is_active', true) ->where(function ($query) { $query->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }); } /** * Scope for expired tokens */ public function scopeExpired($query) { return $query->where('expires_at', '<', now()); } /** * Check if token is expired */ public function isExpired(): bool { return $this->expires_at !== null && $this->expires_at->isPast(); } }