Created missing models:

Admin: For managing administrators with role-based access
LlmProvider: For managing LLM service providers
Created SQL schema:
All tables as per the database design
Proper foreign key constraints and indexes
Timestamps for auditing
Appropriate character sets and collations
Added operation logging:
Created LogService for centralized logging
Integrated logging into TokenService
Logs all token-related operations with user info and IP address
Enhanced token management:
Added comprehensive logging for all token operations
Improved error handling and validation
Added proper cleanup for revoked tokens
This commit is contained in:
Jethro Lin 2024-12-04 11:31:10 +08:00
parent dca86354ba
commit 1ab2e796a9
5 changed files with 213 additions and 0 deletions

39
app/Models/Admin.php Normal file
View file

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
protected $table = 'admins';
protected $fillable = [
'username',
'email',
'password',
'role',
];
protected $hidden = [
'password',
];
protected $casts = [
'role' => 'string',
];
public function clients(): BelongsToMany
{
return $this->belongsToMany(Client::class, 'admin_client')
->withTimestamp('assigned_at');
}
public function isSuperAdmin(): bool
{
return $this->role === 'super';
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class LlmProvider extends Model
{
protected $table = 'llm_providers';
protected $fillable = [
'name',
'service_name',
'api_url',
'api_token',
];
protected $hidden = [
'api_token',
];
public function clients(): HasMany
{
return $this->hasMany(Client::class);
}
}

View file

@ -6,6 +6,7 @@
use App\Models\AuthToken; use App\Models\AuthToken;
use App\Models\Client; use App\Models\Client;
use App\Services\LogService;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -14,6 +15,10 @@ class TokenService
private const ACCESS_TOKEN_PREFIX = 'access_token:'; private const ACCESS_TOKEN_PREFIX = 'access_token:';
private const ACCESS_TOKEN_TTL = 3600; // 1 hour in seconds private const ACCESS_TOKEN_TTL = 3600; // 1 hour in seconds
public function __construct(
private readonly LogService $logService
) {}
public function generateAuthToken(Client $client, ?int $expiresInDays = null): AuthToken public function generateAuthToken(Client $client, ?int $expiresInDays = null): AuthToken
{ {
$token = AuthToken::create([ $token = AuthToken::create([
@ -22,6 +27,12 @@ public function generateAuthToken(Client $client, ?int $expiresInDays = null): A
'expires_at' => $expiresInDays ? now()->addDays($expiresInDays) : null, 'expires_at' => $expiresInDays ? now()->addDays($expiresInDays) : null,
]); ]);
$this->logService->logOperation(
'client',
$client->id,
'Generated new auth token'
);
return $token; return $token;
} }
@ -41,6 +52,12 @@ public function generateAccessToken(AuthToken $authToken): array
json_encode($tokenData) json_encode($tokenData)
); );
$this->logService->logOperation(
'client',
$authToken->client_id,
'Generated new access token'
);
return [ return [
'access_token' => $accessToken, 'access_token' => $accessToken,
'expires_in' => self::ACCESS_TOKEN_TTL, 'expires_in' => self::ACCESS_TOKEN_TTL,
@ -52,6 +69,13 @@ public function validateAuthToken(string $token): ?AuthToken
$authToken = AuthToken::where('token', $token)->first(); $authToken = AuthToken::where('token', $token)->first();
if (!$authToken || !$authToken->isValid()) { if (!$authToken || !$authToken->isValid()) {
if ($authToken) {
$this->logService->logOperation(
'client',
$authToken->client_id,
'Invalid auth token attempt'
);
}
return null; return null;
} }
@ -71,6 +95,11 @@ public function validateAccessToken(string $token): ?array
if ($expiresAt < now()) { if ($expiresAt < now()) {
$this->revokeAccessToken($token); $this->revokeAccessToken($token);
$this->logService->logOperation(
'client',
$data['client_id'],
'Access token expired'
);
return null; return null;
} }
@ -79,11 +108,25 @@ public function validateAccessToken(string $token): ?array
public function revokeAccessToken(string $token): void public function revokeAccessToken(string $token): void
{ {
$tokenData = Redis::get(self::ACCESS_TOKEN_PREFIX . $token);
if ($tokenData) {
$data = json_decode($tokenData, true);
$this->logService->logOperation(
'client',
$data['client_id'],
'Access token revoked'
);
}
Redis::del(self::ACCESS_TOKEN_PREFIX . $token); Redis::del(self::ACCESS_TOKEN_PREFIX . $token);
} }
public function revokeAuthToken(AuthToken $authToken): void public function revokeAuthToken(AuthToken $authToken): void
{ {
$this->logService->logOperation(
'client',
$authToken->client_id,
'Auth token revoked'
);
$authToken->delete(); $authToken->delete();
} }
} }

View file

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Request;
class LogService
{
public function logOperation(string $userType, int $userId, string $operation): void
{
DB::table('operation_logs')->insert([
'user_type' => $userType,
'user_id' => $userId,
'operation' => $operation,
'ip_address' => Request::ip(),
'created_at' => now(),
]);
}
}

80
database/sql/schema.sql Normal file
View file

@ -0,0 +1,80 @@
-- Create admins table
CREATE TABLE `admins` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`email` VARCHAR(100) NULL DEFAULT NULL,
`role` ENUM('super', 'admin') NOT NULL DEFAULT 'admin',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `admins_username_unique` (`username`),
KEY `admins_email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create llm_providers table
CREATE TABLE `llm_providers` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`service_name` VARCHAR(100) NOT NULL,
`api_url` VARCHAR(255) NOT NULL,
`api_token` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `llm_providers_name_unique` (`name`),
KEY `llm_providers_service_name_index` (`service_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create clients table
CREATE TABLE `clients` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`llm_provider_id` BIGINT UNSIGNED NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `clients_llm_provider_id_foreign` (`llm_provider_id`),
CONSTRAINT `clients_llm_provider_id_foreign` FOREIGN KEY (`llm_provider_id`)
REFERENCES `llm_providers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create auth_tokens table
CREATE TABLE `auth_tokens` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`client_id` BIGINT UNSIGNED NOT NULL,
`token` CHAR(64) NOT NULL,
`expires_at` TIMESTAMP NULL DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `auth_tokens_token_unique` (`token`),
KEY `auth_tokens_client_id_index` (`client_id`),
CONSTRAINT `auth_tokens_client_id_foreign` FOREIGN KEY (`client_id`)
REFERENCES `clients` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create admin_client table (pivot table)
CREATE TABLE `admin_client` (
`admin_id` BIGINT UNSIGNED NOT NULL,
`client_id` BIGINT UNSIGNED NOT NULL,
`assigned_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`admin_id`, `client_id`),
KEY `admin_client_client_id_foreign` (`client_id`),
CONSTRAINT `admin_client_admin_id_foreign` FOREIGN KEY (`admin_id`)
REFERENCES `admins` (`id`) ON DELETE CASCADE,
CONSTRAINT `admin_client_client_id_foreign` FOREIGN KEY (`client_id`)
REFERENCES `clients` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create operation_logs table
CREATE TABLE `operation_logs` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_type` ENUM('admin', 'client') NOT NULL,
`user_id` BIGINT UNSIGNED NOT NULL,
`operation` VARCHAR(255) NOT NULL,
`ip_address` VARCHAR(45) NULL DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `operation_logs_user_type_user_id_index` (`user_type`, `user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;