改进 token 认证

This commit is contained in:
Twilight 2024-07-24 00:40:56 +08:00
parent ecf813f1f6
commit f7ebda06f7
25 changed files with 677 additions and 102 deletions

View File

@ -10,10 +10,7 @@ class AssistantController extends Controller
/** /**
* Display a listing of the resource. * Display a listing of the resource.
*/ */
public function index(Request $request) public function index(Request $request) {}
{
}
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Web; namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Logic\OpenIDLogic;
use App\Models\User; use App\Models\User;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ClientException;
@ -18,11 +19,7 @@ class AuthController extends Controller
{ {
public string $scopes = 'profile email realname openid'; public string $scopes = 'profile email realname openid';
protected string $auth_url; protected OpenIDLogic $openIDLogic;
protected string $token_url;
protected string $user_url;
protected string $callback_url; protected string $callback_url;
@ -33,24 +30,7 @@ class AuthController extends Controller
*/ */
public function __construct() public function __construct()
{ {
$cache_key = 'oauth_discovery'; $this->openIDLogic = app(OpenIDLogic::class);
if (cache()->has($cache_key)) {
$oauth_discovery = cache()->get($cache_key);
} else {
// lock 防止重复请求
$oauth_discovery = cache()->remember($cache_key, 3600, function () {
$client = new Client();
$response = $client->get(config('oauth.discovery'));
return json_decode($response->getBody(), true);
});
}
$this->auth_url = $oauth_discovery['authorization_endpoint'];
$this->token_url = $oauth_discovery['token_endpoint'];
$this->user_url = $oauth_discovery['userinfo_endpoint'];
$this->callback_url = route('oauth.callback');
} }
public function redirect(Request $request) public function redirect(Request $request)
@ -65,7 +45,7 @@ public function redirect(Request $request)
'state' => $state, 'state' => $state,
]); ]);
return redirect()->to($this->auth_url.'?'.$query); return redirect()->to($this->openIDLogic->auth_url.'?'.$query);
} }
/** /**
@ -87,7 +67,7 @@ public function callback(Request $request)
$http = new Client; $http = new Client;
try { try {
$authorize = $http->post($this->token_url, [ $authorize = $http->post($this->openIDLogic->token_url, [
'form_params' => [ 'form_params' => [
'grant_type' => 'authorization_code', 'grant_type' => 'authorization_code',
'client_id' => config('oauth.client_id'), 'client_id' => config('oauth.client_id'),
@ -102,7 +82,7 @@ public function callback(Request $request)
$authorize = json_decode($authorize->getBody()); $authorize = json_decode($authorize->getBody());
$oauth_user = $http->get($this->user_url, [ $oauth_user = $http->get($this->openIDLogic->user_url, [
'headers' => [ 'headers' => [
'Accept' => 'application/json', 'Accept' => 'application/json',
'Authorization' => 'Bearer '.$authorize->access_token, 'Authorization' => 'Bearer '.$authorize->access_token,

View File

@ -8,9 +8,7 @@
use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\exceptions\TypeErrorException;
use cebe\openapi\exceptions\UnresolvableReferenceException; use cebe\openapi\exceptions\UnresolvableReferenceException;
use cebe\openapi\json\InvalidJsonPointerSyntaxException; use cebe\openapi\json\InvalidJsonPointerSyntaxException;
use cebe\openapi\Reader;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ToolController extends Controller class ToolController extends Controller
{ {
@ -28,12 +26,12 @@ public function index()
* @throws UnresolvableReferenceException * @throws UnresolvableReferenceException
* @throws InvalidJsonPointerSyntaxException * @throws InvalidJsonPointerSyntaxException
*/ */
public function getOpenAPI() { public function getOpenAPI()
$url = "http://127.0.0.1:8081/openapi.yml"; {
$url = 'http://127.0.0.1:8081/openapi.yml';
$file = file_get_contents($url); $file = file_get_contents($url);
// $openAPI = new OpenAPI(); // $openAPI = new OpenAPI();
// //
// $document = $openAPI->parse($file); // $document = $openAPI->parse($file);

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Middleware;
use App\Http\Controllers\Web\AuthController;
use App\Models\User;
use Closure;
use Exception;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use stdClass;
use Symfony\Component\HttpFoundation\Response;
class JWTMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string $token_type = 'id_token'): Response
{
try {
$jwks = (new AuthController)->getJwks();
} catch (Exception $e) {
return response()->json(['error' => 'Failed to fetch JWKS data'], 500);
}
$keys = JWK::parseKeySet($jwks);
$jwt = $request->bearerToken();
if (empty($jwt)) {
return response()->json(['error' => 'No token provided'], 401);
}
$headers = new stdClass();
try {
// 解码并验证 JWT
$decoded = JWT::decode($jwt, $keys, $headers);
if ($headers->typ != $token_type) {
return response()->json(['error' => 'Invalid token type, must be '.$token_type.', got '.$headers->typ], 401);
}
$user = User::where('external_id', $decoded->sub)->firstOrCreate([
'external_id' => $decoded->sub,
'name' => $decoded->name,
]);
Auth::guard('api')->loginUsingId($user->id, true);
} catch (Exception $e) {
return response()->json(['error' => 'Invalid token, '.$e->getMessage()], 401);
}
return $next($request);
}
}

View File

@ -2,7 +2,4 @@
namespace App\LLM; namespace App\LLM;
class Base class Base {}
{
}

56
app/Logic/OpenIDLogic.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace App\Logic;
use GuzzleHttp\Client;
class OpenIDLogic
{
public string $auth_url;
public string $token_url;
public string $user_url;
public string $jwks_url;
public array $jwks = [];
/**
* Create a new class instance.
*/
public function __construct()
{
$http = new Client();
$cache_key = 'oauth_discovery';
if (cache()->has($cache_key)) {
$oauth_discovery = cache()->get($cache_key);
} else {
// lock 防止重复请求
$oauth_discovery = cache()->remember($cache_key, 3600, function () use ($http) {
$response = $http->get(config('oauth.discovery'));
return json_decode($response->getBody(), true);
});
}
$this->auth_url = $oauth_discovery['authorization_endpoint'];
$this->token_url = $oauth_discovery['token_endpoint'];
$this->user_url = $oauth_discovery['userinfo_endpoint'];
$this->jwks_url = $oauth_discovery['jwks_uri'];
$cache_key = 'oauth_discovery_jwks';
if (cache()->has($cache_key)) {
$this->jwks = cache()->get($cache_key);
} else {
$this->jwks = cache()->remember($cache_key, 3600, function () use ($http) {
$response = $http->get($this->jwks_url);
return json_decode($response->getBody(), true);
});
}
}
}

View File

@ -2,10 +2,36 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Assistant extends Model class Assistant extends Model
{ {
use HasFactory; protected $fillable = [
'name',
'description',
'user_id',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function tools(): BelongsToMany
{
return $this->belongsToMany(Tool::class, 'assistant_tools');
}
public function chats(): HasMany
{
return $this->hasMany(Chat::class);
}
public function chatHistories(): HasMany
{
return $this->hasMany(ChatHistory::class);
}
} }

View File

@ -2,10 +2,12 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class AssistantTool extends Model class AssistantTool extends Model
{ {
use HasFactory; protected $fillable = [
'assistant_id',
'tool_id',
];
} }

31
app/Models/Chat.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Chat extends Model
{
protected $fillable = [
'name',
'assistant_id',
'user_id',
];
public function assistant(): BelongsTo
{
return $this->belongsTo(Assistant::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function histories(): HasMany
{
return $this->hasMany(ChatHistory::class);
}
}

View File

@ -2,10 +2,22 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ChatHistory extends Model class ChatHistory extends Model
{ {
use HasFactory; protected $fillable = [
'chat_id',
'content',
'role',
'input_tokens',
'output_tokens',
'total_tokens',
];
public function chat(): BelongsTo
{
return $this->belongsTo(Chat::class);
}
} }

View File

@ -2,10 +2,41 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Http;
class Tool extends Model class Tool extends Model
{ {
use HasFactory; protected $fillable = [
'name',
'description',
'url',
'api_key',
'callback_url',
'user_id',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function functions(): HasMany
{
return $this->hasMany(ToolFunction::class);
}
public function fetchFunctions(): void
{
$url = $this->url;
$json = Http::get($url);
$json = $json->json();
$this->callback_url = $json['callback_url'];
}
} }

View File

@ -2,10 +2,41 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ToolFunction extends Model class ToolFunction extends Model
{ {
use HasFactory; protected $fillable = [
'tool_id',
'name',
'description',
'parameters',
'required',
];
public function tool(): BelongsTo
{
return $this->belongsTo(Tool::class);
}
public function getParametersAttribute($value)
{
return json_decode($value, true);
}
public function getRequiredAttribute($value)
{
return json_decode($value, true);
}
public function setParametersAttribute($value): void
{
$this->attributes['parameters'] = json_encode($value);
}
public function setRequiredAttribute($value): void
{
$this->attributes['required'] = json_encode($value);
}
} }

View File

@ -18,30 +18,31 @@ class User extends Authenticatable
*/ */
protected $fillable = [ protected $fillable = [
'name', 'name',
'email', 'external_id',
'password', // 'email',
// 'password',
]; ];
/** // /**
* The attributes that should be hidden for serialization. // * The attributes that should be hidden for serialization.
* // *
* @var array<int, string> // * @var array<int, string>
*/ // */
protected $hidden = [ // protected $hidden = [
'password', // 'password',
'remember_token', // 'remember_token',
]; // ];
/** // /**
* Get the attributes that should be cast. // * Get the attributes that should be cast.
* // *
* @return array<string, string> // * @return array<string, string>
*/ // */
protected function casts(): array // protected function casts(): array
{ // {
return [ // return [
'email_verified_at' => 'datetime', // 'email_verified_at' => 'datetime',
'password' => 'hashed', // 'password' => 'hashed',
]; // ];
} // }
} }

View File

@ -2,7 +2,15 @@
namespace App\Providers; namespace App\Providers;
use App\Logic\OpenIDLogic;
use App\Models\User;
use Exception;
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use stdClass;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -19,6 +27,43 @@ public function register(): void
*/ */
public function boot(): void public function boot(): void
{ {
// $this->setJWTGuard();
}
private function setJWTGuard(): void
{
Auth::viaRequest('jwt', function (Request $request) {
$logic = app(OpenIDLogic::class);
$keys = JWK::parseKeySet($logic->jwks);
$jwt = $request->bearerToken();
if (empty($jwt)) {
return response()->json(['error' => 'No token provided'], 401);
}
$headers = new stdClass();
try {
$decoded = JWT::decode($jwt, $keys, $headers);
$request->attributes->add(['token_type' => $headers->typ]);
} catch (Exception $e) {
return response()->json(['error' => 'Invalid token, '.$e->getMessage()], 401);
}
if (! in_array($decoded->aud, config('oauth.trusted_aud'))) {
return response()->json(['error' => 'The application rejected the token, token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401);
}
if (config('oauth.force_aud') && $decoded->aud != config('oauth.client_id')) {
return response()->json(['error' => 'The token not match the application, '.' token aud is '.$decoded->aud.', app aud is '.config('oauth.client_id')], 401);
}
return User::where('external_id', $decoded->sub)->firstOrCreate([
'external_id' => $decoded->sub,
'name' => $decoded->name,
]);
});
} }
} }

View File

@ -0,0 +1,49 @@
<?php
namespace App\Repositories\Tool;
use Exception;
class Tool
{
public string $name;
public string $url;
public string $description;
public string $api_key;
public string $user_id;
/**
* @throws Exception
*/
public function parse(array $data): void
{
// 验证数据
if (! $this->validate($data)) {
throw new Exception('Invalid data');
}
$this->name = $data['name'];
$this->url = $data['url'];
$this->description = $data['description'];
$this->api_key = $data['api_key'];
$this->user_id = $data['user_id'];
}
public function validate(array $data): bool
{
// all fields are required
if (empty($data['name']) ||
empty($data['url']) ||
empty($data['description']) ||
empty($data['api_key']) ||
empty($data['user_id'])) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Repositories\Tool;
class ToolFunction
{
/**
* Create a new class instance.
*/
public function __construct()
{
//
}
}

View File

@ -8,7 +8,9 @@
"php": "^8.2", "php": "^8.2",
"apiboard/php-openapi": "^2.1", "apiboard/php-openapi": "^2.1",
"cebe/php-openapi": "^1.7", "cebe/php-openapi": "^1.7",
"firebase/php-jwt": "^6.10",
"laravel/framework": "^11.9", "laravel/framework": "^11.9",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9" "laravel/tinker": "^2.9"
}, },
"require-dev": { "require-dev": {

129
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5d6d988753b4895d093e83b35b767cfb", "content-hash": "1934fe35f6ca19ea328e64d7a0773787",
"packages": [ "packages": [
{ {
"name": "apiboard/php-openapi", "name": "apiboard/php-openapi",
@ -618,6 +618,69 @@
], ],
"time": "2023-10-06T06:47:41+00:00" "time": "2023-10-06T06:47:41+00:00"
}, },
{
"name": "firebase/php-jwt",
"version": "v6.10.1",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "500501c2ce893c824c801da135d02661199f60c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5",
"reference": "500501c2ce893c824c801da135d02661199f60c5",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.4",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
"psr/cache": "^2.0||^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"suggest": {
"ext-sodium": "Support EdDSA (Ed25519) signatures",
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"type": "library",
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"jwt",
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v6.10.1"
},
"time": "2024-05-18T18:05:11+00:00"
},
{ {
"name": "fruitcake/php-cors", "name": "fruitcake/php-cors",
"version": "v1.3.0", "version": "v1.3.0",
@ -1491,6 +1554,70 @@
}, },
"time": "2024-06-17T13:58:22+00:00" "time": "2024-06-17T13:58:22+00:00"
}, },
{
"name": "laravel/sanctum",
"version": "v4.0.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
"reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/console": "^11.0",
"illuminate/contracts": "^11.0",
"illuminate/database": "^11.0",
"illuminate/support": "^11.0",
"php": "^8.2",
"symfony/console": "^7.0"
},
"require-dev": {
"mockery/mockery": "^1.6",
"orchestra/testbench": "^9.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Sanctum\\SanctumServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Sanctum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
"keywords": [
"auth",
"laravel",
"sanctum"
],
"support": {
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2024-04-10T19:39:58+00:00"
},
{ {
"name": "laravel/serializable-closure", "name": "laravel/serializable-closure",
"version": "v1.3.3", "version": "v1.3.3",

83
config/sanctum.php Normal file
View File

@ -0,0 +1,83 @@
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];

View File

@ -14,27 +14,27 @@ public function up(): void
Schema::create('users', function (Blueprint $table) { Schema::create('users', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->string('email')->unique(); $table->unsignedBigInteger('external_id')->index();
$table->timestamp('email_verified_at')->nullable(); // $table->string('email')->unique();
$table->string('password'); // $table->timestamp('email_verified_at')->nullable();
$table->rememberToken(); // $table->rememberToken();
$table->timestamps(); $table->timestamps();
}); });
Schema::create('password_reset_tokens', function (Blueprint $table) { // Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary(); // $table->string('email')->primary();
$table->string('token'); // $table->string('token');
$table->timestamp('created_at')->nullable(); // $table->timestamp('created_at')->nullable();
}); // });
//
Schema::create('sessions', function (Blueprint $table) { // Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary(); // $table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index(); // $table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable(); // $table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable(); // $table->text('user_agent')->nullable();
$table->longText('payload'); // $table->longText('payload');
$table->integer('last_activity')->index(); // $table->integer('last_activity')->index();
}); // });
} }
/** /**

View File

@ -21,7 +21,6 @@ public function up(): void
$table->unsignedBigInteger('user_id')->index(); $table->unsignedBigInteger('user_id')->index();
$table->foreign('user_id')->references('id')->on('users'); $table->foreign('user_id')->references('id')->on('users');
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -22,7 +22,6 @@ public function up(): void
// index // index
$table->index(['assistant_id', 'user_id']); $table->index(['assistant_id', 'user_id']);
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -31,7 +31,6 @@ public function up(): void
// index // index
$table->index(['chat_id', 'role']); $table->index(['chat_id', 'role']);
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('tools', function (Blueprint $table) {
$table->string('callback_url')->after('api_key');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('tools', function (Blueprint $table) {
$table->dropColumn('callback_url');
});
}
};

8
routes/api.php Normal file
View File

@ -0,0 +1,8 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:api');