140 lines
3.3 KiB
PHP
140 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
|
|
class EventJoinToken extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $fillable = [
|
|
'event_id',
|
|
'token',
|
|
'token_hash',
|
|
'token_encrypted',
|
|
'token_preview',
|
|
'label',
|
|
'usage_limit',
|
|
'usage_count',
|
|
'expires_at',
|
|
'revoked_at',
|
|
'created_by',
|
|
'metadata',
|
|
];
|
|
|
|
protected $casts = [
|
|
'metadata' => 'array',
|
|
'expires_at' => 'datetime',
|
|
'revoked_at' => 'datetime',
|
|
'usage_limit' => 'integer',
|
|
'usage_count' => 'integer',
|
|
];
|
|
|
|
protected $hidden = [
|
|
'token',
|
|
'token_encrypted',
|
|
'token_hash',
|
|
];
|
|
|
|
protected $appends = [
|
|
'token_preview',
|
|
];
|
|
|
|
public function event(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Event::class);
|
|
}
|
|
|
|
public function creator(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'created_by');
|
|
}
|
|
|
|
public function analytics(): HasMany
|
|
{
|
|
return $this->hasMany(EventJoinTokenEvent::class);
|
|
}
|
|
|
|
public function isActive(): bool
|
|
{
|
|
if ($this->revoked_at !== null) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->expires_at !== null && $this->expires_at->isPast()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->usage_limit !== null && $this->usage_count >= $this->usage_limit) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function getTokenAttribute(?string $value): ?string
|
|
{
|
|
$encrypted = $this->attributes['token_encrypted'] ?? null;
|
|
|
|
if (! empty($encrypted)) {
|
|
try {
|
|
return Crypt::decryptString($encrypted);
|
|
} catch (\Throwable $e) {
|
|
try {
|
|
return Crypt::decrypt($encrypted);
|
|
} catch (\Throwable $e) {
|
|
// Fall back to stored hash if both decrypt strategies fail.
|
|
}
|
|
}
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
public function setTokenAttribute(?string $value): void
|
|
{
|
|
if ($value === null) {
|
|
$this->attributes['token'] = null;
|
|
$this->attributes['token_hash'] = null;
|
|
$this->attributes['token_encrypted'] = null;
|
|
$this->attributes['token_preview'] = null;
|
|
|
|
return;
|
|
}
|
|
|
|
$hash = hash('sha256', $value);
|
|
|
|
$this->attributes['token'] = $hash;
|
|
$this->attributes['token_hash'] = $hash;
|
|
$this->attributes['token_encrypted'] = Crypt::encryptString($value);
|
|
$this->attributes['token_preview'] = $this->buildPreview($value);
|
|
}
|
|
|
|
public function getTokenPreviewAttribute(?string $value): ?string
|
|
{
|
|
if ($value) {
|
|
return $value;
|
|
}
|
|
|
|
$token = $this->token;
|
|
|
|
return $token ? $this->buildPreview($token) : null;
|
|
}
|
|
|
|
private function buildPreview(string $token): string
|
|
{
|
|
$length = strlen($token);
|
|
|
|
if ($length <= 10) {
|
|
return $token;
|
|
}
|
|
|
|
return substr($token, 0, 6).'…'.substr($token, -4);
|
|
}
|
|
}
|