feat(auth): implement authentication endpoints with registration and login functionality
This commit is contained in:
156
app/Http/Controllers/Api/AuthController.php
Normal file
156
app/Http/Controllers/Api/AuthController.php
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Auth\LoginRequest;
|
||||||
|
use App\Http\Requests\Auth\RegisterRequest;
|
||||||
|
use App\Http\Resources\UserResource;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
#[OA\Post(
|
||||||
|
path: '/auth/register',
|
||||||
|
description: 'Register a new user account',
|
||||||
|
summary: 'Register',
|
||||||
|
tags: ['Auth'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
required: ['name', 'mobile', 'email', 'password'],
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'name', type: 'string', example: 'علی رضایی'),
|
||||||
|
new OA\Property(property: 'mobile', type: 'string', example: '09123456789'),
|
||||||
|
new OA\Property(property: 'email', type: 'string', format: 'email', example: 'ali@example.com'),
|
||||||
|
new OA\Property(property: 'password', type: 'string', format: 'password', example: 'password123'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Registered successfully',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'ثبتنام با موفقیت انجام شد.'),
|
||||||
|
new OA\Property(
|
||||||
|
property: 'data',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'user', ref: '#/components/schemas/User'),
|
||||||
|
new OA\Property(property: 'token', type: 'string'),
|
||||||
|
],
|
||||||
|
type: 'object'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new OA\Response(response: 422, description: 'Validation error'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function register(RegisterRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = User::query()->create([
|
||||||
|
'name' => $request->validated('name'),
|
||||||
|
'username' => $this->generateUsername($request->validated('email')),
|
||||||
|
'mobile' => $request->validated('mobile'),
|
||||||
|
'email' => $request->validated('email'),
|
||||||
|
'password' => $request->validated('password'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$token = $user->createToken('auth-token')->plainTextToken;
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'ثبتنام با موفقیت انجام شد.',
|
||||||
|
'data' => [
|
||||||
|
'user' => new UserResource($user),
|
||||||
|
'token' => $token,
|
||||||
|
],
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
path: '/auth/login',
|
||||||
|
description: 'Login with email or username',
|
||||||
|
summary: 'Login',
|
||||||
|
tags: ['Auth'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
required: ['login', 'password'],
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'login', description: 'Email or username', type: 'string', example: 'ali@example.com'),
|
||||||
|
new OA\Property(property: 'password', type: 'string', format: 'password', example: 'password123'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Logged in successfully',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'ورود با موفقیت انجام شد.'),
|
||||||
|
new OA\Property(
|
||||||
|
property: 'data',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'user', ref: '#/components/schemas/User'),
|
||||||
|
new OA\Property(property: 'token', type: 'string'),
|
||||||
|
],
|
||||||
|
type: 'object'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new OA\Response(response: 422, description: 'Invalid credentials'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function login(LoginRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$login = $request->validated('login');
|
||||||
|
|
||||||
|
$user = User::query()
|
||||||
|
->where(function ($query) use ($login): void {
|
||||||
|
$query->where('email', $login)
|
||||||
|
->orWhere('username', $login);
|
||||||
|
})
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $user || ! Hash::check($request->validated('password'), $user->password)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'اطلاعات ورود نادرست است.',
|
||||||
|
'errors' => [
|
||||||
|
'login' => ['ایمیل یا نام کاربری یا رمز عبور اشتباه است.'],
|
||||||
|
],
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $user->createToken('auth-token')->plainTextToken;
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'ورود با موفقیت انجام شد.',
|
||||||
|
'data' => [
|
||||||
|
'user' => new UserResource($user),
|
||||||
|
'token' => $token,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateUsername(string $email): string
|
||||||
|
{
|
||||||
|
$base = Str::slug(Str::before($email, '@'), '') ?: 'user';
|
||||||
|
$username = $base;
|
||||||
|
$counter = 1;
|
||||||
|
|
||||||
|
while (User::query()->where('username', $username)->exists()) {
|
||||||
|
$username = $base.$counter;
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
app/Http/Requests/Auth/LoginRequest.php
Normal file
35
app/Http/Requests/Auth/LoginRequest.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class LoginRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'login' => ['required', 'string'],
|
||||||
|
'password' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'login.required' => 'ایمیل یا نام کاربری الزامی است.',
|
||||||
|
'password.required' => 'رمز عبور الزامی است.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
44
app/Http/Requests/Auth/RegisterRequest.php
Normal file
44
app/Http/Requests/Auth/RegisterRequest.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class RegisterRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'mobile' => ['required', 'string', 'regex:/^09\d{9}$/', 'unique:users,mobile'],
|
||||||
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
|
||||||
|
'password' => ['required', 'string', 'min:8'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name.required' => 'نام و نام خانوادگی الزامی است.',
|
||||||
|
'mobile.required' => 'شماره موبایل الزامی است.',
|
||||||
|
'mobile.regex' => 'فرمت شماره موبایل صحیح نیست.',
|
||||||
|
'mobile.unique' => 'این شماره موبایل قبلاً ثبت شده است.',
|
||||||
|
'email.required' => 'ایمیل الزامی است.',
|
||||||
|
'email.email' => 'فرمت ایمیل صحیح نیست.',
|
||||||
|
'email.unique' => 'این ایمیل قبلاً ثبت شده است.',
|
||||||
|
'password.required' => 'رمز عبور الزامی است.',
|
||||||
|
'password.min' => 'رمز عبور باید حداقل ۸ کاراکتر باشد.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Http/Resources/UserResource.php
Normal file
38
app/Http/Resources/UserResource.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
schema: 'User',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'id', type: 'integer', example: 1),
|
||||||
|
new OA\Property(property: 'name', type: 'string', example: 'علی رضایی'),
|
||||||
|
new OA\Property(property: 'username', type: 'string', example: 'ali'),
|
||||||
|
new OA\Property(property: 'email', type: 'string', format: 'email', example: 'ali@example.com'),
|
||||||
|
new OA\Property(property: 'mobile', type: 'string', example: '09123456789'),
|
||||||
|
new OA\Property(property: 'created_at', type: 'string', format: 'date-time'),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
/** @mixin User */
|
||||||
|
class UserResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'username' => $this->username,
|
||||||
|
'email' => $this->email,
|
||||||
|
'mobile' => $this->mobile,
|
||||||
|
'created_at' => $this->created_at?->toIso8601String(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,13 +12,14 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
#[Fillable(['name', 'email', 'mobile', 'password'])]
|
#[Fillable(['name', 'username', 'email', 'mobile', 'password'])]
|
||||||
#[Hidden(['password', 'remember_token'])]
|
#[Hidden(['password', 'remember_token'])]
|
||||||
class User extends Authenticatable implements FilamentUser
|
class User extends Authenticatable implements FilamentUser
|
||||||
{
|
{
|
||||||
/** @use HasFactory<UserFactory> */
|
/** @use HasFactory<UserFactory> */
|
||||||
use HasFactory, Notifiable;
|
use HasApiTokens, HasFactory, Notifiable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return BelongsToMany<Role, $this>
|
* @return BelongsToMany<Role, $this>
|
||||||
|
|||||||
14
app/OpenApi.php
Normal file
14
app/OpenApi.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Info(
|
||||||
|
version: '1.0.0',
|
||||||
|
title: 'Hoshpoint API',
|
||||||
|
description: 'API documentation for Hoshpoint backend',
|
||||||
|
)]
|
||||||
|
#[OA\Server(url: '/api', description: 'API server')]
|
||||||
|
#[OA\Tag(name: 'Auth', description: 'Authentication endpoints')]
|
||||||
|
class OpenApi {}
|
||||||
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
|
|||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
web: __DIR__.'/../routes/web.php',
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
api: __DIR__.'/../routes/api.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ return [
|
|||||||
'driver' => 'session',
|
'driver' => 'session',
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
],
|
],
|
||||||
|
'sanctum' => [
|
||||||
|
'driver' => 'sanctum',
|
||||||
|
'provider' => 'users',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
321
config/l5-swagger.php
Normal file
321
config/l5-swagger.php
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use L5Swagger\Generator;
|
||||||
|
use OpenApi\scan;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => 'default',
|
||||||
|
'documentations' => [
|
||||||
|
'default' => [
|
||||||
|
'api' => [
|
||||||
|
'title' => 'Hoshpoint API',
|
||||||
|
],
|
||||||
|
|
||||||
|
'routes' => [
|
||||||
|
/*
|
||||||
|
* Route for accessing api documentation interface
|
||||||
|
*/
|
||||||
|
'api' => 'api/documentation',
|
||||||
|
],
|
||||||
|
'paths' => [
|
||||||
|
/*
|
||||||
|
* Edit to include full URL in ui for assets
|
||||||
|
*/
|
||||||
|
'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Edit to set path where swagger ui assets should be stored
|
||||||
|
*/
|
||||||
|
'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File name of the generated json documentation file
|
||||||
|
*/
|
||||||
|
'docs_json' => 'api-docs.json',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File name of the generated YAML documentation file
|
||||||
|
*/
|
||||||
|
'docs_yaml' => 'api-docs.yaml',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set this to `json` or `yaml` to determine which documentation file to use in UI
|
||||||
|
*/
|
||||||
|
'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Absolute paths to directory containing the swagger annotations are stored.
|
||||||
|
*/
|
||||||
|
'annotations' => [
|
||||||
|
base_path('app'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'defaults' => [
|
||||||
|
'routes' => [
|
||||||
|
/*
|
||||||
|
* Route for accessing parsed swagger annotations.
|
||||||
|
*/
|
||||||
|
'docs' => 'docs',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Route for Oauth2 authentication callback.
|
||||||
|
*/
|
||||||
|
'oauth2_callback' => 'api/oauth2-callback',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Middleware allows to prevent unexpected access to API documentation
|
||||||
|
*/
|
||||||
|
'middleware' => [
|
||||||
|
'api' => [],
|
||||||
|
'asset' => [],
|
||||||
|
'docs' => [],
|
||||||
|
'oauth2_callback' => [],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Route Group options
|
||||||
|
*/
|
||||||
|
'group_options' => [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'paths' => [
|
||||||
|
/*
|
||||||
|
* Absolute path to location where parsed annotations will be stored
|
||||||
|
*/
|
||||||
|
'docs' => storage_path('api-docs'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Absolute path to directory where to export views
|
||||||
|
*/
|
||||||
|
'views' => base_path('resources/views/vendor/l5-swagger'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Edit to set the api's base path
|
||||||
|
*/
|
||||||
|
'base' => env('L5_SWAGGER_BASE_PATH', null),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Absolute path to directories that should be excluded from scanning
|
||||||
|
* @deprecated Please use `scanOptions.exclude`
|
||||||
|
* `scanOptions.exclude` overwrites this
|
||||||
|
*/
|
||||||
|
'excludes' => [],
|
||||||
|
],
|
||||||
|
|
||||||
|
'scanOptions' => [
|
||||||
|
/**
|
||||||
|
* Configuration for default processors. Allows to pass processors configuration to swagger-php.
|
||||||
|
*
|
||||||
|
* @link https://zircote.github.io/swagger-php/reference/processors.html
|
||||||
|
*/
|
||||||
|
'default_processors_configuration' => [
|
||||||
|
/** Example */
|
||||||
|
/**
|
||||||
|
* 'operationId.hash' => true,
|
||||||
|
* 'pathFilter' => [
|
||||||
|
* 'tags' => [
|
||||||
|
* '/pets/',
|
||||||
|
* '/store/',
|
||||||
|
* ],
|
||||||
|
* ],.
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* analyser: defaults to \OpenApi\StaticAnalyser .
|
||||||
|
*
|
||||||
|
* @see scan
|
||||||
|
*/
|
||||||
|
'analyser' => null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* analysis: defaults to a new \OpenApi\Analysis .
|
||||||
|
*
|
||||||
|
* @see scan
|
||||||
|
*/
|
||||||
|
'analysis' => null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom query path processors classes.
|
||||||
|
*
|
||||||
|
* @link https://github.com/zircote/swagger-php/tree/master/Examples/processors/schema-query-parameter
|
||||||
|
* @see scan
|
||||||
|
*/
|
||||||
|
'processors' => [
|
||||||
|
// new \App\SwaggerProcessors\SchemaQueryParameter(),
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pattern: string $pattern File pattern(s) to scan (default: *.php) .
|
||||||
|
*
|
||||||
|
* @see scan
|
||||||
|
*/
|
||||||
|
'pattern' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Absolute path to directories that should be excluded from scanning
|
||||||
|
* @note This option overwrites `paths.excludes`
|
||||||
|
* @see \OpenApi\scan
|
||||||
|
*/
|
||||||
|
'exclude' => [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allows to generate specs either for OpenAPI 3.0.0 or OpenAPI 3.1.0.
|
||||||
|
* By default the spec will be in version 3.0.0
|
||||||
|
*/
|
||||||
|
'open_api_spec_version' => env('L5_SWAGGER_OPEN_API_SPEC_VERSION', Generator::OPEN_API_DEFAULT_SPEC_VERSION),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API security definitions. Will be generated into documentation file.
|
||||||
|
*/
|
||||||
|
'securityDefinitions' => [
|
||||||
|
'securitySchemes' => [
|
||||||
|
/*
|
||||||
|
* Examples of Security schemes
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
'api_key_security_example' => [ // Unique name of security
|
||||||
|
'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
||||||
|
'description' => 'A short description for security scheme',
|
||||||
|
'name' => 'api_key', // The name of the header or query parameter to be used.
|
||||||
|
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
|
||||||
|
],
|
||||||
|
'oauth2_security_example' => [ // Unique name of security
|
||||||
|
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
||||||
|
'description' => 'A short description for oauth2 security scheme.',
|
||||||
|
'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode".
|
||||||
|
'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode)
|
||||||
|
//'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode)
|
||||||
|
'scopes' => [
|
||||||
|
'read:projects' => 'read your projects',
|
||||||
|
'write:projects' => 'modify projects in your account',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Open API 3.0 support
|
||||||
|
'passport' => [ // Unique name of security
|
||||||
|
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
||||||
|
'description' => 'Laravel passport oauth2 security.',
|
||||||
|
'in' => 'header',
|
||||||
|
'scheme' => 'https',
|
||||||
|
'flows' => [
|
||||||
|
"password" => [
|
||||||
|
"authorizationUrl" => config('app.url') . '/oauth/authorize',
|
||||||
|
"tokenUrl" => config('app.url') . '/oauth/token',
|
||||||
|
"refreshUrl" => config('app.url') . '/token/refresh',
|
||||||
|
"scopes" => []
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'sanctum' => [ // Unique name of security
|
||||||
|
'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2".
|
||||||
|
'description' => 'Enter token in format (Bearer <token>)',
|
||||||
|
'name' => 'Authorization', // The name of the header or query parameter to be used.
|
||||||
|
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
'security' => [
|
||||||
|
/*
|
||||||
|
* Examples of Securities
|
||||||
|
*/
|
||||||
|
[
|
||||||
|
/*
|
||||||
|
'oauth2_security_example' => [
|
||||||
|
'read',
|
||||||
|
'write'
|
||||||
|
],
|
||||||
|
|
||||||
|
'passport' => []
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set this to `true` in development mode so that docs would be regenerated on each request
|
||||||
|
* Set this to `false` to disable swagger generation on production
|
||||||
|
*/
|
||||||
|
'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set this to `true` to generate a copy of documentation in yaml format
|
||||||
|
*/
|
||||||
|
'generate_yaml_copy' => env('L5_SWAGGER_GENERATE_YAML_COPY', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Edit to trust the proxy's ip address - needed for AWS Load Balancer
|
||||||
|
* string[]
|
||||||
|
*/
|
||||||
|
'proxy' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle.
|
||||||
|
* See more at: https://github.com/swagger-api/swagger-ui#configs-plugin
|
||||||
|
*/
|
||||||
|
'additional_config_url' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically),
|
||||||
|
* 'method' (sort by HTTP method).
|
||||||
|
* Default is the order returned by the server unchanged.
|
||||||
|
*/
|
||||||
|
'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pass the validatorUrl parameter to SwaggerUi init on the JS side.
|
||||||
|
* A null value here disables validation.
|
||||||
|
*/
|
||||||
|
'validator_url' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Swagger UI configuration parameters
|
||||||
|
*/
|
||||||
|
'ui' => [
|
||||||
|
'display' => [
|
||||||
|
'dark_mode' => env('L5_SWAGGER_UI_DARK_MODE', false),
|
||||||
|
/*
|
||||||
|
* Controls the default expansion setting for the operations and tags. It can be :
|
||||||
|
* 'list' (expands only the tags),
|
||||||
|
* 'full' (expands the tags and operations),
|
||||||
|
* 'none' (expands nothing).
|
||||||
|
*/
|
||||||
|
'doc_expansion' => env('L5_SWAGGER_UI_DOC_EXPANSION', 'none'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, enables filtering. The top bar will show an edit box that
|
||||||
|
* you can use to filter the tagged operations that are shown. Can be
|
||||||
|
* Boolean to enable or disable, or a string, in which case filtering
|
||||||
|
* will be enabled using that string as the filter expression. Filtering
|
||||||
|
* is case-sensitive matching the filter expression anywhere inside
|
||||||
|
* the tag.
|
||||||
|
*/
|
||||||
|
'filter' => env('L5_SWAGGER_UI_FILTERS', true), // true | false
|
||||||
|
],
|
||||||
|
|
||||||
|
'authorization' => [
|
||||||
|
/*
|
||||||
|
* If set to true, it persists authorization data, and it would not be lost on browser close/refresh
|
||||||
|
*/
|
||||||
|
'persist_authorization' => env('L5_SWAGGER_UI_PERSIST_AUTHORIZATION', false),
|
||||||
|
|
||||||
|
'oauth2' => [
|
||||||
|
/*
|
||||||
|
* If set to true, adds PKCE to AuthorizationCodeGrant flow
|
||||||
|
*/
|
||||||
|
'use_pkce_with_authorization_code_grant' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
* Constants which can be used in annotations
|
||||||
|
*/
|
||||||
|
'constants' => [
|
||||||
|
'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
87
config/sanctum.php
Normal file
87
config/sanctum.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||||
|
use Laravel\Sanctum\Http\Middleware\AuthenticateSession;
|
||||||
|
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::currentRequestHost(),
|
||||||
|
))),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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' => AuthenticateSession::class,
|
||||||
|
'encrypt_cookies' => EncryptCookies::class,
|
||||||
|
'validate_csrf_token' => ValidateCsrfToken::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -24,9 +24,12 @@ class UserFactory extends Factory
|
|||||||
*/
|
*/
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
|
$email = fake()->unique()->safeEmail();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'name' => fake()->name(),
|
'name' => fake()->name(),
|
||||||
'email' => fake()->unique()->safeEmail(),
|
'username' => fake()->unique()->userName(),
|
||||||
|
'email' => $email,
|
||||||
'mobile' => fake()->unique()->numerify('09#########'),
|
'mobile' => fake()->unique()->numerify('09#########'),
|
||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
'password' => static::$password ??= Hash::make('password'),
|
'password' => static::$password ??= Hash::make('password'),
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?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::create('personal_access_tokens', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->morphs('tokenable');
|
||||||
|
$table->text('name');
|
||||||
|
$table->string('token', 64)->unique();
|
||||||
|
$table->text('abilities')->nullable();
|
||||||
|
$table->timestamp('last_used_at')->nullable();
|
||||||
|
$table->timestamp('expires_at')->nullable()->index();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('personal_access_tokens');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('username')->nullable()->unique()->after('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (DB::table('users')->orderBy('id')->get() as $user) {
|
||||||
|
$base = Str::slug(Str::before($user->email, '@'), '') ?: 'user';
|
||||||
|
$username = $base;
|
||||||
|
$counter = 1;
|
||||||
|
|
||||||
|
while (DB::table('users')->where('username', $username)->where('id', '!=', $user->id)->exists()) {
|
||||||
|
$username = $base.$counter;
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('users')->where('id', $user->id)->update(['username' => $username]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropUnique(['username']);
|
||||||
|
$table->dropColumn('username');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
174
resources/views/vendor/l5-swagger/index.blade.php
vendored
Normal file
174
resources/views/vendor/l5-swagger/index.blade.php
vendored
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ $documentationTitle }}</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ l5_swagger_asset($documentation, 'swagger-ui.css') }}">
|
||||||
|
<link rel="icon" type="image/png" href="{{ l5_swagger_asset($documentation, 'favicon-32x32.png') }}" sizes="32x32"/>
|
||||||
|
<link rel="icon" type="image/png" href="{{ l5_swagger_asset($documentation, 'favicon-16x16.png') }}" sizes="16x16"/>
|
||||||
|
<style>
|
||||||
|
html
|
||||||
|
{
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: -moz-scrollbars-vertical;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after
|
||||||
|
{
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin:0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@if(config('l5-swagger.defaults.ui.display.dark_mode'))
|
||||||
|
<style>
|
||||||
|
body#dark-mode,
|
||||||
|
#dark-mode .scheme-container {
|
||||||
|
background: #1b1b1b;
|
||||||
|
}
|
||||||
|
#dark-mode .scheme-container,
|
||||||
|
#dark-mode .opblock .opblock-section-header{
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
#dark-mode .operation-filter-input,
|
||||||
|
#dark-mode .dialog-ux .modal-ux,
|
||||||
|
#dark-mode input[type=email],
|
||||||
|
#dark-mode input[type=file],
|
||||||
|
#dark-mode input[type=password],
|
||||||
|
#dark-mode input[type=search],
|
||||||
|
#dark-mode input[type=text],
|
||||||
|
#dark-mode textarea{
|
||||||
|
background: #343434;
|
||||||
|
color: #e7e7e7;
|
||||||
|
}
|
||||||
|
#dark-mode .title,
|
||||||
|
#dark-mode li,
|
||||||
|
#dark-mode p,
|
||||||
|
#dark-mode table,
|
||||||
|
#dark-mode label,
|
||||||
|
#dark-mode .opblock-tag,
|
||||||
|
#dark-mode .opblock .opblock-summary-operation-id,
|
||||||
|
#dark-mode .opblock .opblock-summary-path,
|
||||||
|
#dark-mode .opblock .opblock-summary-path__deprecated,
|
||||||
|
#dark-mode h1,
|
||||||
|
#dark-mode h2,
|
||||||
|
#dark-mode h3,
|
||||||
|
#dark-mode h4,
|
||||||
|
#dark-mode h5,
|
||||||
|
#dark-mode .btn,
|
||||||
|
#dark-mode .tab li,
|
||||||
|
#dark-mode .parameter__name,
|
||||||
|
#dark-mode .parameter__type,
|
||||||
|
#dark-mode .prop-format,
|
||||||
|
#dark-mode .loading-container .loading:after{
|
||||||
|
color: #e7e7e7;
|
||||||
|
}
|
||||||
|
#dark-mode .opblock-description-wrapper p,
|
||||||
|
#dark-mode .opblock-external-docs-wrapper p,
|
||||||
|
#dark-mode .opblock-title_normal p,
|
||||||
|
#dark-mode .response-col_status,
|
||||||
|
#dark-mode table thead tr td,
|
||||||
|
#dark-mode table thead tr th,
|
||||||
|
#dark-mode .response-col_links,
|
||||||
|
#dark-mode .swagger-ui{
|
||||||
|
color: wheat;
|
||||||
|
}
|
||||||
|
#dark-mode .parameter__extension,
|
||||||
|
#dark-mode .parameter__in,
|
||||||
|
#dark-mode .model-title{
|
||||||
|
color: #949494;
|
||||||
|
}
|
||||||
|
#dark-mode table thead tr td,
|
||||||
|
#dark-mode table thead tr th{
|
||||||
|
border-color: rgba(120,120,120,.2);
|
||||||
|
}
|
||||||
|
#dark-mode .opblock .opblock-section-header{
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
#dark-mode .opblock.opblock-post{
|
||||||
|
background: rgba(73,204,144,.25);
|
||||||
|
}
|
||||||
|
#dark-mode .opblock.opblock-get{
|
||||||
|
background: rgba(97,175,254,.25);
|
||||||
|
}
|
||||||
|
#dark-mode .opblock.opblock-put{
|
||||||
|
background: rgba(252,161,48,.25);
|
||||||
|
}
|
||||||
|
#dark-mode .opblock.opblock-delete{
|
||||||
|
background: rgba(249,62,62,.25);
|
||||||
|
}
|
||||||
|
#dark-mode .loading-container .loading:before{
|
||||||
|
border-color: rgba(255,255,255,10%);
|
||||||
|
border-top-color: rgba(255,255,255,.6);
|
||||||
|
}
|
||||||
|
#dark-mode svg:not(:root){
|
||||||
|
fill: #e7e7e7;
|
||||||
|
}
|
||||||
|
#dark-mode .opblock-summary-description {
|
||||||
|
color: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endif
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body @if(config('l5-swagger.defaults.ui.display.dark_mode')) id="dark-mode" @endif>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
|
<script src="{{ l5_swagger_asset($documentation, 'swagger-ui-bundle.js') }}"></script>
|
||||||
|
<script src="{{ l5_swagger_asset($documentation, 'swagger-ui-standalone-preset.js') }}"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
const urls = [];
|
||||||
|
|
||||||
|
@foreach($urlsToDocs as $title => $url)
|
||||||
|
urls.push({name: "{{ $title }}", url: "{{ $url }}"});
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
// Build a system
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
urls: urls,
|
||||||
|
"urls.primaryName": "{{ $documentationTitle }}",
|
||||||
|
operationsSorter: {!! isset($operationsSorter) ? '"' . $operationsSorter . '"' : 'null' !!},
|
||||||
|
configUrl: {!! isset($configUrl) ? '"' . $configUrl . '"' : 'null' !!},
|
||||||
|
validatorUrl: {!! isset($validatorUrl) ? '"' . $validatorUrl . '"' : 'null' !!},
|
||||||
|
oauth2RedirectUrl: "{{ route('l5-swagger.'.$documentation.'.oauth2_callback', [], $useAbsolutePath) }}",
|
||||||
|
|
||||||
|
requestInterceptor: function(request) {
|
||||||
|
request.headers['X-CSRF-TOKEN'] = '{{ csrf_token() }}';
|
||||||
|
return request;
|
||||||
|
},
|
||||||
|
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
|
||||||
|
layout: "StandaloneLayout",
|
||||||
|
docExpansion : "{!! config('l5-swagger.defaults.ui.display.doc_expansion', 'none') !!}",
|
||||||
|
deepLinking: true,
|
||||||
|
filter: {!! config('l5-swagger.defaults.ui.display.filter') ? 'true' : 'false' !!},
|
||||||
|
persistAuthorization: "{!! config('l5-swagger.defaults.ui.authorization.persist_authorization') ? 'true' : 'false' !!}",
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
window.ui = ui
|
||||||
|
|
||||||
|
@if(in_array('oauth2', array_column(config('l5-swagger.defaults.securityDefinitions.securitySchemes'), 'type')))
|
||||||
|
ui.initOAuth({
|
||||||
|
usePkceWithAuthorizationCodeGrant: "{!! (bool)config('l5-swagger.defaults.ui.authorization.oauth2.use_pkce_with_authorization_code_grant') !!}"
|
||||||
|
})
|
||||||
|
@endif
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9
routes/api.php
Normal file
9
routes/api.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\AuthController;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::prefix('auth')->group(function (): void {
|
||||||
|
Route::post('register', [AuthController::class, 'register']);
|
||||||
|
Route::post('login', [AuthController::class, 'login']);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user