feat: tg/ws/security/

This commit is contained in:
hashbro
2026-10-03 05:40:19 +08:00
parent 6e4f7e6020
commit afac799588
32 changed files with 2038 additions and 18 deletions
+40
View File
@@ -14,6 +14,8 @@ class Admin extends Authenticatable
'google_auth_open',
'google_secret',
'last_ip',
'login_attempts',
'locked_at',
];
protected $hidden = ['password', 'remember_token', 'google_secret'];
@@ -25,6 +27,8 @@ class Admin extends Authenticatable
'is_super' => 'integer',
'status' => 'integer',
'google_auth_open' => 'integer',
'login_attempts' => 'integer',
'locked_at' => 'datetime',
];
}
@@ -38,6 +42,42 @@ class Admin extends Authenticatable
return (int) $this->status === 1;
}
public function isLocked(): bool
{
return $this->locked_at !== null;
}
/**
* Increment the consecutive failed-login counter; auto-lock when the
* count reaches $maxAttempts (default 5). Returns true when the call
* triggers a lock.
*/
public function recordFailedLogin(int $maxAttempts = 5): bool
{
$this->login_attempts = (int) $this->login_attempts + 1;
$justLocked = false;
if ($this->login_attempts >= $maxAttempts && ! $this->isLocked()) {
$this->locked_at = now();
$justLocked = true;
}
$this->save();
return $justLocked;
}
/**
* Reset the failed-login counter (called after a successful login or
* when an admin manually unlocks the account).
*/
public function clearLoginAttempts(): void
{
if ((int) $this->login_attempts !== 0 || $this->locked_at !== null) {
$this->login_attempts = 0;
$this->locked_at = null;
$this->save();
}
}
public function hasGoogleBound(): bool
{
return filled($this->google_secret);
+12
View File
@@ -12,6 +12,14 @@ class SystemLog extends Model
public const ACTION_MNEMONIC_CREATE = 'mnemonic_create';
public const ACTION_ADMIN_LOCKED = 'admin_locked';
public const ACTION_ADMIN_UNLOCKED = 'admin_unlocked';
public const ACTION_AGENT_LOCKED = 'agent_locked';
public const ACTION_AGENT_UNLOCKED = 'agent_unlocked';
public const UPDATED_AT = null;
protected $fillable = [
@@ -30,6 +38,10 @@ class SystemLog extends Model
return [
self::ACTION_MNEMONIC_REVEAL => '查看助记词',
self::ACTION_MNEMONIC_CREATE => '手动添加助记词',
self::ACTION_ADMIN_LOCKED => '账号封锁',
self::ACTION_ADMIN_UNLOCKED => '账号解锁',
self::ACTION_AGENT_LOCKED => '代理账号封锁',
self::ACTION_AGENT_UNLOCKED => '代理账号解锁',
];
}
+40
View File
@@ -17,6 +17,8 @@ class User extends Authenticatable
'auto_transfer_threshold_bnb',
'album_storage_default',
'can_reveal_mnemonics',
'login_attempts',
'locked_at',
];
protected $hidden = [
@@ -46,6 +48,8 @@ class User extends Authenticatable
'album_storage_default' => 'boolean',
'can_reveal_mnemonics' => 'boolean',
'google_auth_open' => 'integer',
'login_attempts' => 'integer',
'locked_at' => 'datetime',
];
}
@@ -59,6 +63,42 @@ class User extends Authenticatable
return (int) $this->status === 1;
}
public function isLocked(): bool
{
return $this->locked_at !== null;
}
/**
* Increment the consecutive failed-login counter; auto-lock when the
* count reaches $maxAttempts (default 5). Returns true when the call
* triggers a lock.
*/
public function recordFailedLogin(int $maxAttempts = 5): bool
{
$this->login_attempts = (int) $this->login_attempts + 1;
$justLocked = false;
if ($this->login_attempts >= $maxAttempts && ! $this->isLocked()) {
$this->locked_at = now();
$justLocked = true;
}
$this->save();
return $justLocked;
}
/**
* Reset the failed-login counter (called after a successful login or
* when an admin manually unlocks the account).
*/
public function clearLoginAttempts(): void
{
if ((int) $this->login_attempts !== 0 || $this->locked_at !== null) {
$this->login_attempts = 0;
$this->locked_at = null;
$this->save();
}
}
public function channels(): HasMany
{
return $this->hasMany(Channel::class, 'user_id');