You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.5 KiB
134 lines
3.5 KiB
<?php |
|
|
|
namespace App\Models; |
|
|
|
use Illuminate\Database\Eloquent\Model; |
|
use Illuminate\Database\Eloquent\SoftDeletes; |
|
|
|
/** |
|
* ServiceToken - Bearer token management for microservices |
|
* |
|
* This model manages Bearer tokens for microservices like GeoNetAgent, |
|
* Databaselist, SQLServerCheck, etc. Tokens are stored as SHA256 hashes |
|
* with a prefix for UI display. |
|
* |
|
* Zero impact on existing GeoNetAgent agents (they use separate database). |
|
*/ |
|
class ServiceToken extends Model |
|
{ |
|
protected $fillable = [ |
|
'name', |
|
'service', |
|
'token_prefix', |
|
'token_hash', |
|
'is_active', |
|
'expires_at', |
|
'created_by', |
|
'last_used_at', |
|
]; |
|
|
|
protected function casts(): array |
|
{ |
|
return [ |
|
'is_active' => '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(); |
|
} |
|
}
|
|
|