First Commit
This commit is contained in:
Executable
+9
@@ -0,0 +1,9 @@
|
||||
SysCMS Commercial License
|
||||
|
||||
Copyright (c) 2026 Marco Costa. All rights reserved.
|
||||
|
||||
This software is licensed, not sold. Permission is granted to use this software subject to the terms of the SysCMS Commercial License.
|
||||
|
||||
Redistribution, sublicensing, resale, or disclosure of this software, in whole or in part, is strictly prohibited without prior written permission from the author.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
# Auth Module for SysCMS
|
||||
|
||||
Authentication and authorization module for user management.
|
||||
|
||||
## Features
|
||||
- User registration and login
|
||||
- Password reset functionality
|
||||
- User profile management
|
||||
- Role-based access control
|
||||
- Email verification (optional)
|
||||
- Session management
|
||||
|
||||
## Installation
|
||||
1. Upload the `auth.zip` file through the module manager or extract it to the `modules/` directory.
|
||||
2. Run the migrations to register the module and create the database tables.
|
||||
3. Configure authentication settings in the admin panel.
|
||||
|
||||
## Permissions
|
||||
- `auth.login`: User login
|
||||
- `auth.logout`: User logout
|
||||
- `auth.register`: User registration
|
||||
- `auth.profile`: View and edit profile
|
||||
- `auth.password.reset`: Request password reset
|
||||
- `auth.password.change`: Change password
|
||||
|
||||
## Configuration
|
||||
The module supports the following settings:
|
||||
- `allow_registration`: Enable/disable user registration
|
||||
- `require_email_verification`: Require email verification before account activation
|
||||
- `password_min_length`: Minimum password length (default: 8)
|
||||
- `session_timeout`: Session timeout in minutes (default: 120)
|
||||
|
||||
## License
|
||||
Copyright (c) 2026 Marco Costa. All rights reserved.
|
||||
Licensed under the SysCMS Commercial License.
|
||||
+1009
File diff suppressed because it is too large
Load Diff
+187
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Auth\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Modules\Auth\Models\User;
|
||||
use Kernel\Application;
|
||||
|
||||
class AuthMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
// Check if user is authenticated
|
||||
if (!$this->isAuthenticated()) {
|
||||
if ($this->expectsJson($request)) {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => __('auth.unauthenticated'),
|
||||
'timestamp' => time()
|
||||
], 401);
|
||||
}
|
||||
|
||||
// Redirect to login page for web requests
|
||||
return new Response('', 302, [
|
||||
'Location' => url('/login')
|
||||
]);
|
||||
}
|
||||
|
||||
// Check if user session is still valid
|
||||
if (!$this->isSessionValid()) {
|
||||
$this->logout();
|
||||
|
||||
if ($this->expectsJson($request)) {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'message' => __('auth.session_expired'),
|
||||
'timestamp' => time()
|
||||
], 401);
|
||||
}
|
||||
|
||||
return new Response('', 302, [
|
||||
'Location' => url('/login?expired=1')
|
||||
]);
|
||||
}
|
||||
|
||||
// Set user in request for easy access
|
||||
$request->attributes->set('user', $this->getCurrentUser());
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is authenticated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAuthenticated(): bool
|
||||
{
|
||||
if (isset($_SESSION['user_id']) && !empty($_SESSION['user_id'])) {
|
||||
return true;
|
||||
}
|
||||
if (!empty($_COOKIE['remember_token'])) {
|
||||
$secret = $_ENV['APP_KEY'] ?? '';
|
||||
$parts = explode(':', $_COOKIE['remember_token']);
|
||||
if (count($parts) === 3) {
|
||||
[$id, $token, $sig] = $parts;
|
||||
$payload = $id . ':' . $token;
|
||||
$expected = hash_hmac('sha256', $payload, $secret);
|
||||
if (hash_equals($expected, $sig)) {
|
||||
try {
|
||||
$user = User::find((int)$id);
|
||||
if ($user && $user->users_remember_token === $token) {
|
||||
$_SESSION['user_id'] = $user->users_id;
|
||||
$_SESSION['user_idbus'] = $user->users_bus_id;
|
||||
$_SESSION['user_role'] = $user->users_role;
|
||||
$_SESSION['last_activity'] = time();
|
||||
return true;
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if session is still valid.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSessionValid(): bool
|
||||
{
|
||||
if (!isset($_SESSION['last_activity'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timeoutSetting = 120;
|
||||
try {
|
||||
if (class_exists('\App\Models\Module')) {
|
||||
$module = \App\Models\Module::byName('auth')->first();
|
||||
if ($module) {
|
||||
$timeoutSetting = (int)($module->getSetting('session_timeout', 120));
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
$sessionTimeout = (int)($_ENV['SESSION_LIFETIME'] ?? $timeoutSetting) * 60;
|
||||
$lastActivity = $_SESSION['last_activity'];
|
||||
$currentTime = time();
|
||||
|
||||
// Check if session has expired
|
||||
if (($currentTime - $lastActivity) > $sessionTimeout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update last activity time
|
||||
$_SESSION['last_activity'] = $currentTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current authenticated user.
|
||||
*
|
||||
* @return User|null
|
||||
*/
|
||||
protected function getCurrentUser(): ?User
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return User::find($_SESSION['user_id']);
|
||||
} catch (\Exception $e) {
|
||||
// Log error and invalidate session
|
||||
error_log("Error loading user: " . $e->getMessage());
|
||||
$this->logout();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if request expects JSON response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function expectsJson(Request $request): bool
|
||||
{
|
||||
return $request->headers->get('Accept') === 'application/json' ||
|
||||
$request->headers->get('Content-Type') === 'application/json' ||
|
||||
str_contains($request->getPathInfo(), '/api/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user and destroy session.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function logout(): void
|
||||
{
|
||||
// Unset all session variables
|
||||
unset($_SESSION['user_id']);
|
||||
unset($_SESSION['user_idbus']);
|
||||
unset($_SESSION['user_role']);
|
||||
unset($_SESSION['last_activity']);
|
||||
unset($_SESSION['csrf_token']);
|
||||
|
||||
// Destroy session
|
||||
session_destroy();
|
||||
|
||||
// Start new session for possible flash messages
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+526
@@ -0,0 +1,526 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SysCMS - Content Management System
|
||||
*
|
||||
* Copyright (c) 2025 Marco Costa
|
||||
* All rights reserved.
|
||||
*
|
||||
* Author: Marco Costa
|
||||
* Email: contato@syspanel.com.br
|
||||
* Project: SysCMS
|
||||
*
|
||||
* This software is licensed, not sold.
|
||||
* Permission is granted to use this software
|
||||
* subject to the terms of the SysCMS Commercial License.
|
||||
*
|
||||
* Redistribution, sublicensing, resale, or
|
||||
* disclosure of this software, in whole or in part,
|
||||
* is strictly prohibited without prior written permission.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
||||
*/
|
||||
|
||||
namespace Modules\Auth\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'users';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'users_id';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'users_name',
|
||||
'users_email',
|
||||
'users_password',
|
||||
'users_role',
|
||||
'users_bus_id',
|
||||
'users_avatar',
|
||||
'users_bio',
|
||||
'users_phone',
|
||||
'users_email_verified_at',
|
||||
'users_last_login_at',
|
||||
'users_status',
|
||||
'users_preferences'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for arrays.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [
|
||||
'users_password',
|
||||
'remember_token'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'users_email_verified_at' => 'datetime',
|
||||
'users_last_login_at' => 'datetime',
|
||||
'users_preferences' => 'array',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = [
|
||||
'users_email_verified_at',
|
||||
'users_last_login_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* Boot the model
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Add company scope by default
|
||||
static::addGlobalScope('company', function ($query) {
|
||||
if (isset($_SESSION['user_idbus']) && $_SESSION['user_idbus'] !== null && $_SESSION['user_role'] !== 'super_admin') {
|
||||
$query->where('users_bus_id', $_SESSION['user_idbus']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the company that owns the user.
|
||||
*/
|
||||
public function company(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\Modules\Company\Models\Company::class, 'users_bus_id', 'bus_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permissions for the user.
|
||||
*/
|
||||
public function permissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(\App\Models\UserPermission::class, 'user_permissions_user_id', 'users_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activity logs for the user.
|
||||
*/
|
||||
public function activityLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(\App\Models\ActivityLog::class, 'activity_logs_user_id', 'users_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is super admin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSuperAdmin(): bool
|
||||
{
|
||||
return $this->users_role === 'super_admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is admin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->users_role === 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is member
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isMember(): bool
|
||||
{
|
||||
return $this->users_role === 'member';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is regular user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUser(): bool
|
||||
{
|
||||
return $this->users_role === 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has specific permission
|
||||
*
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
// Super admin has all permissions
|
||||
if ($this->isSuperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Admin role has full access within their company scope
|
||||
if ($this->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check direct permissions
|
||||
$hasDirectPermission = \App\Models\UserPermission::where('user_permissions_user_id', $this->users_id)
|
||||
->where('user_permissions_permission', $permission)
|
||||
->where('user_permissions_bus_id', $this->users_bus_id)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('user_permissions_expires_at')
|
||||
->orWhere('user_permissions_expires_at', '>', \Carbon\Carbon::now());
|
||||
})
|
||||
->exists();
|
||||
|
||||
if ($hasDirectPermission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check role permissions
|
||||
return \App\Models\RolePermission::where('role_permissions_role', $this->users_role)
|
||||
->where('role_permissions_bus_id', $this->users_bus_id)
|
||||
->where('role_permissions_permission', $permission)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all permissions for the user as an array of strings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPermissions(): array
|
||||
{
|
||||
// Se for Super Admin, retorna um token que indica todas as permissões
|
||||
// (Ou poderíamos retornar todas as permissões do sistema se necessário)
|
||||
if ($this->isSuperAdmin()) {
|
||||
return ['*'];
|
||||
}
|
||||
|
||||
// Se for Admin, retorna todas as permissões da empresa (ou um token)
|
||||
if ($this->isAdmin()) {
|
||||
return ['*.company'];
|
||||
}
|
||||
|
||||
$directPermissions = \App\Models\UserPermission::where('user_permissions_user_id', $this->users_id)
|
||||
->where('user_permissions_bus_id', $this->users_bus_id)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('user_permissions_expires_at')
|
||||
->orWhere('user_permissions_expires_at', '>', \Carbon\Carbon::now());
|
||||
})
|
||||
->pluck('user_permissions_permission')
|
||||
->toArray();
|
||||
|
||||
$rolePermissions = \App\Models\RolePermission::where('role_permissions_role', $this->users_role)
|
||||
->where('role_permissions_bus_id', $this->users_bus_id)
|
||||
->pluck('role_permissions_permission')
|
||||
->toArray();
|
||||
|
||||
return array_unique(array_merge($directPermissions, $rolePermissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the avatar URL attribute
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAvatarUrlAttribute(): string
|
||||
{
|
||||
if ($this->users_avatar) {
|
||||
return url('storage/uploads/avatars/' . $this->users_avatar);
|
||||
}
|
||||
|
||||
return asset_url('img/default-avatar.png');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDisplayNameAttribute(): string
|
||||
{
|
||||
return $this->users_name ?: $this->users_email;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get id attribute alias
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIdAttribute(): int
|
||||
{
|
||||
return $this->users_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the password before saving
|
||||
*
|
||||
* @param string $value
|
||||
* @return void
|
||||
*/
|
||||
public function setUsersPasswordAttribute($value): void
|
||||
{
|
||||
$this->attributes['users_password'] = password_hash($value, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for setting password via 'password' attribute
|
||||
*
|
||||
* @param string $value
|
||||
* @return void
|
||||
*/
|
||||
public function setPasswordAttribute($value): void
|
||||
{
|
||||
$this->setUsersPasswordAttribute($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the password attribute for authentication
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUsersPasswordAttribute(): string
|
||||
{
|
||||
return $this->attributes['users_password'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for getting password via 'password' attribute
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPasswordAttribute(): string
|
||||
{
|
||||
return $this->getUsersPasswordAttribute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the email attribute
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEmailAttribute(): string
|
||||
{
|
||||
return $this->attributes['users_email'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name attribute
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNameAttribute(): string
|
||||
{
|
||||
return $this->attributes['users_name'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the role attribute
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRoleAttribute(): string
|
||||
{
|
||||
return $this->attributes['users_role'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status attribute
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatusAttribute(): string
|
||||
{
|
||||
return $this->attributes['users_status'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the email_verified_at attribute
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEmailVerifiedAtAttribute(): ?string
|
||||
{
|
||||
return $this->attributes['users_email_verified_at'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email_verified_at attribute
|
||||
*
|
||||
* @param string|null $value
|
||||
* @return void
|
||||
*/
|
||||
public function setEmailVerifiedAtAttribute($value): void
|
||||
{
|
||||
$this->attributes['users_email_verified_at'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the avatar attribute
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAvatarAttribute(): ?string
|
||||
{
|
||||
return $this->attributes['users_avatar'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the avatar attribute
|
||||
*
|
||||
* @param string|null $value
|
||||
* @return void
|
||||
*/
|
||||
public function setAvatarAttribute($value): void
|
||||
{
|
||||
$this->attributes['users_avatar'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bio attribute
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getBioAttribute(): ?string
|
||||
{
|
||||
return $this->attributes['users_bio'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bio attribute
|
||||
*
|
||||
* @param string|null $value
|
||||
* @return void
|
||||
*/
|
||||
public function setBioAttribute($value): void
|
||||
{
|
||||
$this->attributes['users_bio'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the phone attribute
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPhoneAttribute(): ?string
|
||||
{
|
||||
return $this->attributes['users_phone'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the phone attribute
|
||||
*
|
||||
* @param string|null $value
|
||||
* @return void
|
||||
*/
|
||||
public function setPhoneAttribute($value): void
|
||||
{
|
||||
$this->attributes['users_phone'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the preferences attribute
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getPreferencesAttribute(): ?array
|
||||
{
|
||||
$value = $this->attributes['users_preferences'] ?? null;
|
||||
return $value ? json_decode($value, true) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the preferences attribute
|
||||
*
|
||||
* @param array|null $value
|
||||
* @return void
|
||||
*/
|
||||
public function setPreferencesAttribute($value): void
|
||||
{
|
||||
$this->attributes['users_preferences'] = is_array($value) ? json_encode($value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users by company
|
||||
*
|
||||
* @param int $companyId
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public static function getByCompany(int $companyId)
|
||||
{
|
||||
return self::where('users_bus_id', $companyId)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active users only
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('users_status', 'active');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users by role
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $role
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeRole($query, string $role)
|
||||
{
|
||||
return $query->where('users_role', $role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update last login timestamp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateLastLogin(): bool
|
||||
{
|
||||
return $this->update(['users_last_login_at' => \Carbon\Carbon::now()]);
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Auth\Providers;
|
||||
|
||||
use Kernel\Application;
|
||||
use Kernel\View;
|
||||
use Modules\Auth\Models\User;
|
||||
use Modules\Auth\Middleware\AuthMiddleware;
|
||||
|
||||
class AuthServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
$app = Application::getInstance();
|
||||
|
||||
// Register Middleware
|
||||
$app->addProtectedMiddleware(new AuthMiddleware());
|
||||
|
||||
// Register Public Routes
|
||||
$app->addPublicPath('/login');
|
||||
$app->addPublicPath('/register');
|
||||
$app->addPublicPath('/forgot-password');
|
||||
$app->addPublicPath('/reset-password*'); // Prefix
|
||||
$app->addPublicPath('/verify-email*'); // Prefix
|
||||
|
||||
// Inject Auth Global
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
try {
|
||||
if (class_exists(User::class)) {
|
||||
$user = User::find($_SESSION['user_id']);
|
||||
if ($user) {
|
||||
$view = new View();
|
||||
$view->getEngine()->addGlobal('auth', ['user' => $user]);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateUsersTable
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Capsule::schema()->hasTable('users')) {
|
||||
Capsule::schema()->create('users', function (Blueprint $table) {
|
||||
$table->increments('users_id');
|
||||
$table->string('users_name');
|
||||
$table->string('users_email')->unique();
|
||||
$table->string('users_password');
|
||||
$table->string('users_role')->default('user'); // super_admin, admin, member, user
|
||||
$table->integer('users_bus_id')->default(0)->index();
|
||||
$table->string('users_avatar')->nullable();
|
||||
$table->text('users_bio')->nullable();
|
||||
$table->string('users_phone')->nullable();
|
||||
$table->timestamp('users_email_verified_at')->nullable();
|
||||
$table->timestamp('users_last_login_at')->nullable();
|
||||
$table->string('users_status')->default('active'); // active, inactive, banned
|
||||
$table->json('users_preferences')->nullable();
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Capsule::schema()->dropIfExists('users');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateAuthPermissionsTables
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
// User Permissions
|
||||
if (!Capsule::schema()->hasTable('user_permissions')) {
|
||||
Capsule::schema()->create('user_permissions', function (Blueprint $table) {
|
||||
$table->increments('user_permissions_id');
|
||||
$table->integer('user_permissions_user_id')->unsigned();
|
||||
$table->integer('user_permissions_bus_id')->unsigned()->nullable(); // Permission might be global or company specific
|
||||
$table->string('user_permissions_permission');
|
||||
$table->integer('user_permissions_granted_by')->unsigned()->nullable();
|
||||
$table->timestamp('user_permissions_granted_at')->useCurrent();
|
||||
$table->timestamp('user_permissions_expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('user_permissions_user_id')->references('users_id')->on('users')->onDelete('cascade');
|
||||
// Optional: foreign key for bus_id if module order permits
|
||||
});
|
||||
}
|
||||
|
||||
// Role Permissions
|
||||
if (!Capsule::schema()->hasTable('role_permissions')) {
|
||||
Capsule::schema()->create('role_permissions', function (Blueprint $table) {
|
||||
$table->increments('role_permissions_id');
|
||||
$table->string('role_permissions_role');
|
||||
$table->integer('role_permissions_bus_id')->unsigned()->nullable();
|
||||
$table->string('role_permissions_permission');
|
||||
$table->integer('role_permissions_created_by')->unsigned()->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
// Activity Logs
|
||||
if (!Capsule::schema()->hasTable('activity_logs')) {
|
||||
Capsule::schema()->create('activity_logs', function (Blueprint $table) {
|
||||
$table->increments('activity_logs_id');
|
||||
$table->integer('activity_logs_user_id')->unsigned()->nullable();
|
||||
$table->integer('activity_logs_bus_id')->unsigned()->nullable();
|
||||
$table->string('activity_logs_action');
|
||||
$table->string('activity_logs_resource_type')->nullable();
|
||||
$table->string('activity_logs_resource_id')->nullable();
|
||||
$table->json('activity_logs_old_values')->nullable();
|
||||
$table->json('activity_logs_new_values')->nullable();
|
||||
$table->string('activity_logs_ip_address')->nullable();
|
||||
$table->string('activity_logs_user_agent')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('activity_logs_user_id');
|
||||
$table->index('activity_logs_bus_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Capsule::schema()->dropIfExists('activity_logs');
|
||||
Capsule::schema()->dropIfExists('role_permissions');
|
||||
Capsule::schema()->dropIfExists('user_permissions');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateAuthAttemptsTable
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (!Capsule::schema()->hasTable('auth_attempts')) {
|
||||
Capsule::schema()->create('auth_attempts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('email')->nullable();
|
||||
$table->string('ip')->index();
|
||||
$table->string('action')->index();
|
||||
$table->boolean('success')->default(false);
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Capsule::schema()->dropIfExists('auth_attempts');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use App\Models\Module;
|
||||
use Modules\Company\Models\Company;
|
||||
|
||||
class InsertAuthModule
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$moduleData = [
|
||||
'modules_name' => 'auth',
|
||||
'modules_display_name' => 'Authentication',
|
||||
'modules_description' => 'Authentication and authorization module for user management',
|
||||
'modules_version' => '1.0.0',
|
||||
'modules_author' => 'SysCMS Team',
|
||||
'modules_ative' => true,
|
||||
'modules_public' => true,
|
||||
'modules_blocked' => true, // Core module, cannot be disabled easily
|
||||
'modules_dependencies' => [],
|
||||
'modules_permissions' => [
|
||||
"auth.login",
|
||||
"auth.logout",
|
||||
"auth.register",
|
||||
"auth.profile",
|
||||
"auth.password.reset",
|
||||
"auth.password.change"
|
||||
],
|
||||
'modules_settings' => [
|
||||
"allow_registration" => true,
|
||||
"require_email_verification" => false,
|
||||
"password_min_length" => 8,
|
||||
"session_timeout" => 120
|
||||
],
|
||||
'modules_path' => 'modules/auth',
|
||||
'modules_namespace' => 'Modules\\Auth',
|
||||
'modules_icon' => 'bi bi-person-check',
|
||||
'menu_side_bar' => true,
|
||||
'menu_nav_bar' => true,
|
||||
'modules_order' => 1,
|
||||
'modules_install_date' => date('Y-m-d H:i:s'),
|
||||
'modules_update_date' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// Check if module already exists
|
||||
$existingModule = Module::where('modules_name', 'auth')->first();
|
||||
|
||||
if (!$existingModule) {
|
||||
$module = Module::create($moduleData);
|
||||
|
||||
// Associate with all existing companies (bus_modules)
|
||||
// Assuming Company model and table exist.
|
||||
if (Capsule::schema()->hasTable('bus')) {
|
||||
$companies = Company::all();
|
||||
foreach ($companies as $company) {
|
||||
$exists = Capsule::table('bus_modules')
|
||||
->where('bus_modules_bus_id', $company->bus_id)
|
||||
->where('bus_modules_module_id', $module->modules_id)
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
Capsule::table('bus_modules')->insert([
|
||||
'bus_modules_bus_id' => $company->bus_id,
|
||||
'bus_modules_module_id' => $module->modules_id,
|
||||
'bus_modules_is_enabled' => true,
|
||||
'bus_modules_settings' => json_encode([]),
|
||||
'bus_modules_enabled_by' => 1,
|
||||
'bus_modules_enabled_at' => date('Y-m-d H:i:s'),
|
||||
'bus_modules_show_public_nav' => true,
|
||||
'bus_modules_show_dashboard_nav' => true,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$module = Module::where('modules_name', 'auth')->first();
|
||||
if ($module) {
|
||||
Capsule::table('bus_modules')->where('bus_modules_module_id', $module->modules_id)->delete();
|
||||
$module->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"menu": {
|
||||
"dashboard": "Authentifizierung",
|
||||
"login": "Anmeldung"
|
||||
},
|
||||
"login_title": "Anmeldung - :platformName",
|
||||
"register_title": "Registrierung - :platformName",
|
||||
"profile_title": "Mein Profil",
|
||||
"forgot_password_title": "Passwort vergessen",
|
||||
"reset_password_title": "Passwort zurücksetzen",
|
||||
"email": "E-Mail",
|
||||
"name": "Name",
|
||||
"password": "Passwort",
|
||||
"password_confirmation": "Passwort Bestätigung",
|
||||
"email_placeholder": "Geben Sie Ihre E-Mail ein",
|
||||
"password_placeholder": "Passwort eingeben",
|
||||
"password_confirmation_placeholder": "Passwort bestätigen",
|
||||
"name_placeholder": "Name eingeben",
|
||||
"current_password_placeholder": "Aktuelles Passwort",
|
||||
"new_password_placeholder": "Neues Passwort",
|
||||
"new_password_confirmation_placeholder": "Neues Passwort bestätigen",
|
||||
"accept_terms": "Ich akzeptiere die Bedingungen",
|
||||
"read_terms": "Bedingungen lesen",
|
||||
"privacy_policy": "Datenschutz",
|
||||
"terms_of_service": "Nutzungsbedingungen",
|
||||
"cookie_policy": "Cookie-Richtlinie",
|
||||
"data_protection": "Datenschutz",
|
||||
"remember_me": "Angemeldet bleiben",
|
||||
"login_button": "Anmelden",
|
||||
"register_button": "Registrieren",
|
||||
"forgot_password": "Passwort vergessen?",
|
||||
"update_profile": "Profil aktualisieren",
|
||||
"back_to_login": "Zur Anmeldung",
|
||||
"logout": "Abmelden",
|
||||
"send_reset_link": "Link zum Zurücksetzen senden",
|
||||
"reset_token": "Zurücksetzungs-Token",
|
||||
"reset_email": "E-Mail zum Zurücksetzen",
|
||||
"reset_token_invalid": "Ungültiger Token",
|
||||
"reset_token_expired": "Token abgelaufen",
|
||||
"reset_email_invalid": "Ungültige E-Mail",
|
||||
"reset_password_button": "Passwort zurücksetzen",
|
||||
"invalid_credentials": "Ungültige Anmeldedaten",
|
||||
"account_inactive": "Konto inaktiv",
|
||||
"session_expired": "Sitzung abgelaufen. Bitte erneut anmelden.",
|
||||
"registration_pending": "Registrierung ausstehend",
|
||||
"registration_error": "Registrierungsfehler",
|
||||
"registration_success": "Registrierung erfolgreich. Bitte anmelden.",
|
||||
"logout_success": "Erfolgreich abgemeldet",
|
||||
"profile_updated": "Profil aktualisiert",
|
||||
"reset_link_sent": "Link zum Zurücksetzen gesendet",
|
||||
"current_avatar": "Aktueller Avatar",
|
||||
"close": "Schließen",
|
||||
"security_check": "Sicherheitsüberprüfung",
|
||||
"captcha_solved": "Richtig!",
|
||||
"captcha_instruction": "Lösen Sie die Summe, um fortzufahren.",
|
||||
"captcha_placeholder": "Ergebnis eingeben",
|
||||
"captcha_question": "Wie viel ist :a + :b?",
|
||||
"invalid_captcha": "Ungültiges Captcha",
|
||||
"email_verified_success": "E-Mail bestätigt. Bitte anmelden.",
|
||||
"verification_email_sent": "Bestätigungs-E-Mail gesendet.",
|
||||
"resend_verification": "Bestätigung erneut senden",
|
||||
"too_many_attempts": "Zu viele Versuche. Später erneut probieren.",
|
||||
"captcha_reload": "Herausforderung neu laden",
|
||||
"terms_and_conditions": "Allgemeine Geschäftsbedingungen",
|
||||
"privacy_policy_text": "Text der Datenschutzerklärung...",
|
||||
"terms_of_service_text": "Text der Nutzungsbedingungen...",
|
||||
"cookie_policy_text": "Text der Cookie-Richtlinie...",
|
||||
"data_protection_text": "Text zum Datenschutz...",
|
||||
"phone": "Telefon",
|
||||
"bio": "Biografie",
|
||||
"current_password": "Aktuelles Passwort",
|
||||
"avatar": "Avatar",
|
||||
"cancel": "Abbrechen",
|
||||
"preview": "Vorschau",
|
||||
"email_already_verified": "Este e-mail já foi verificado. Faça login.",
|
||||
"email_not_found": "E-mail não encontrado."
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Authentifizierung",
|
||||
"description": "Systemauthentifizierungs- und Autorisierungsmodul",
|
||||
"welcome": "Willkommen beim Content Management System",
|
||||
"login_required": "Anmeldung erforderlich, um auf diese Seite zuzugreifen",
|
||||
"access_denied": "Zugriff verweigert",
|
||||
"invalid_credentials": "Ungültige Anmeldedaten",
|
||||
"session_expired": "Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an."
|
||||
}
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"menu": {
|
||||
"dashboard": "Authentication",
|
||||
"login": "Login"
|
||||
},
|
||||
"login_title": "Login - :platformName",
|
||||
"register_title": "Register - :platformName",
|
||||
"profile_title": "My Profile",
|
||||
"forgot_password_title": "Forgot Password",
|
||||
"reset_password_title": "Reset Password",
|
||||
"email": "Email",
|
||||
"name": "Name",
|
||||
"password": "Password",
|
||||
"password_confirmation": "Password Confirmation",
|
||||
"email_placeholder": "Enter your email",
|
||||
"password_placeholder": "Enter your password",
|
||||
"password_confirmation_placeholder": "Confirm your password",
|
||||
"name_placeholder": "Enter your name",
|
||||
"current_password_placeholder": "Current password",
|
||||
"new_password_placeholder": "New password",
|
||||
"new_password_confirmation_placeholder": "Confirm new password",
|
||||
"accept_terms": "I accept the terms",
|
||||
"read_terms": "Read terms",
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"terms_of_service": "Terms of Service",
|
||||
"cookie_policy": "Cookie Policy",
|
||||
"data_protection": "Data Protection",
|
||||
"remember_me": "Remember me",
|
||||
"login_button": "Sign In",
|
||||
"register_button": "Register",
|
||||
"forgot_password": "Forgot your password?",
|
||||
"update_profile": "Update Profile",
|
||||
"back_to_login": "Back to Login",
|
||||
"logout": "Logout",
|
||||
"send_reset_link": "Send Reset Link",
|
||||
"reset_token": "Reset Token",
|
||||
"reset_email": "Reset Email",
|
||||
"reset_token_invalid": "Invalid token",
|
||||
"reset_token_expired": "Token expired",
|
||||
"reset_email_invalid": "Invalid email",
|
||||
"reset_password_button": "Reset Password",
|
||||
"invalid_credentials": "Invalid credentials",
|
||||
"account_inactive": "Inactive account",
|
||||
"session_expired": "Session expired. Please log in again.",
|
||||
"registration_pending": "Registration pending",
|
||||
"registration_error": "Registration error",
|
||||
"registration_success": "Registration successful! Please log in to continue.",
|
||||
"logout_success": "Logout successful",
|
||||
"profile_updated": "Profile updated successfully",
|
||||
"reset_link_sent": "Reset link sent successfully",
|
||||
"current_avatar": "Current avatar",
|
||||
"close": "Close",
|
||||
"security_check": "Security Check",
|
||||
"captcha_solved": "Correct!",
|
||||
"captcha_instruction": "Solve the sum to continue.",
|
||||
"captcha_placeholder": "Enter the result",
|
||||
"captcha_question": "What is :a + :b?",
|
||||
"invalid_captcha": "Invalid captcha",
|
||||
"email_verified_success": "Email verified successfully. Please log in.",
|
||||
"verification_email_sent": "Verification email sent. Check your inbox.",
|
||||
"resend_verification": "Resend verification",
|
||||
"too_many_attempts": "Too many attempts. Please try again later.",
|
||||
"captcha_reload": "Reload challenge",
|
||||
"terms_and_conditions": "Terms and Conditions",
|
||||
"privacy_policy_text": "Privacy policy text...",
|
||||
"terms_of_service_text": "Terms of service text...",
|
||||
"cookie_policy_text": "Cookie policy text...",
|
||||
"data_protection_text": "Data protection text...",
|
||||
"phone": "Phone",
|
||||
"bio": "Biography",
|
||||
"current_password": "Current Password",
|
||||
"avatar": "Avatar",
|
||||
"cancel": "Cancel",
|
||||
"preview": "Preview",
|
||||
"email_already_verified": "Este e-mail já foi verificado. Faça login.",
|
||||
"email_not_found": "E-mail não encontrado."
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Authentication",
|
||||
"description": "System authentication and authorization module",
|
||||
"welcome": "Welcome to the Content Management System",
|
||||
"login_required": "Login required to access this page",
|
||||
"access_denied": "Access denied",
|
||||
"invalid_credentials": "Invalid credentials",
|
||||
"session_expired": "Your session has expired. Please log in again."
|
||||
}
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"menu": {
|
||||
"dashboard": "Autenticación",
|
||||
"login": "Login"
|
||||
},
|
||||
"login_title": "Inicio de sesión - :platformName",
|
||||
"register_title": "Registro - :platformName",
|
||||
"profile_title": "Mi Perfil",
|
||||
"forgot_password_title": "Olvidé mi contraseña",
|
||||
"reset_password_title": "Restablecer contraseña",
|
||||
"email": "Correo Electrónico",
|
||||
"name": "Nombre",
|
||||
"password": "Contraseña",
|
||||
"password_confirmation": "Confirmación de Contraseña",
|
||||
"email_placeholder": "Introduce tu correo",
|
||||
"password_placeholder": "Introduce tu contraseña",
|
||||
"password_confirmation_placeholder": "Confirma tu contraseña",
|
||||
"name_placeholder": "Introduce tu nombre",
|
||||
"current_password_placeholder": "Contraseña actual",
|
||||
"new_password_placeholder": "Nueva contraseña",
|
||||
"new_password_confirmation_placeholder": "Confirmar nueva contraseña",
|
||||
"accept_terms": "Acepto los términos",
|
||||
"read_terms": "Leer términos",
|
||||
"privacy_policy": "Política de privacidad",
|
||||
"terms_of_service": "Términos de servicio",
|
||||
"cookie_policy": "Política de cookies",
|
||||
"data_protection": "Protección de datos",
|
||||
"remember_me": "Recordarme",
|
||||
"login_button": "Entrar",
|
||||
"register_button": "Registrar",
|
||||
"forgot_password": "¿Olvidaste tu contraseña?",
|
||||
"update_profile": "Actualizar perfil",
|
||||
"back_to_login": "Volver al inicio",
|
||||
"logout": "Salir",
|
||||
"send_reset_link": "Enviar enlace de restablecimiento",
|
||||
"reset_token": "Token de restablecimiento",
|
||||
"reset_email": "Correo para restablecer",
|
||||
"reset_token_invalid": "Token inválido",
|
||||
"reset_token_expired": "Token expirado",
|
||||
"reset_email_invalid": "Correo inválido",
|
||||
"reset_password_button": "Restablecer contraseña",
|
||||
"invalid_credentials": "Credenciales inválidas",
|
||||
"account_inactive": "Cuenta inactiva",
|
||||
"session_expired": "Sesión expirada. Por favor, inicia sesión nuevamente.",
|
||||
"registration_pending": "Registro pendiente",
|
||||
"registration_error": "Error en el registro",
|
||||
"registration_success": "Registro exitoso. Inicia sesión para continuar.",
|
||||
"logout_success": "Salida exitosa",
|
||||
"profile_updated": "Perfil actualizado",
|
||||
"reset_link_sent": "Enlace de restablecimiento enviado",
|
||||
"current_avatar": "Avatar actual",
|
||||
"close": "Cerrar",
|
||||
"security_check": "Verificación de seguridad",
|
||||
"captcha_solved": "¡Correcto!",
|
||||
"captcha_instruction": "Resuelve la suma para continuar.",
|
||||
"captcha_placeholder": "Introduce el resultado",
|
||||
"captcha_question": "¿Cuánto es :a + :b?",
|
||||
"invalid_captcha": "Captcha inválido",
|
||||
"email_verified_success": "Correo verificado. Inicia sesión.",
|
||||
"verification_email_sent": "Correo de verificación enviado.",
|
||||
"resend_verification": "Reenviar verificación",
|
||||
"too_many_attempts": "Demasiados intentos. Inténtalo más tarde.",
|
||||
"captcha_reload": "Recargar desafío",
|
||||
"terms_and_conditions": "Términos y Condiciones",
|
||||
"privacy_policy_text": "Texto de la política de privacidad...",
|
||||
"terms_of_service_text": "Texto de los términos de servicio...",
|
||||
"cookie_policy_text": "Texto de la política de cookies...",
|
||||
"data_protection_text": "Texto de protección de datos...",
|
||||
"phone": "Teléfono",
|
||||
"bio": "Biografía",
|
||||
"current_password": "Contraseña actual",
|
||||
"avatar": "Avatar",
|
||||
"cancel": "Cancelar",
|
||||
"preview": "Vista previa",
|
||||
"email_already_verified": "Este e-mail já foi verificado. Faça login.",
|
||||
"email_not_found": "E-mail não encontrado."
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Autenticación",
|
||||
"description": "Módulo de autenticación y autorización del sistema",
|
||||
"welcome": "Bienvenido al Sistema de Gestión de Contenidos",
|
||||
"login_required": "Es necesario iniciar sesión para acceder a esta página",
|
||||
"access_denied": "Acceso denegado",
|
||||
"invalid_credentials": "Credenciales inválidas",
|
||||
"session_expired": "Tu sesión ha expirado. Por favor, inicia sesión de nuevo."
|
||||
}
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"menu": {
|
||||
"dashboard": "Authentification",
|
||||
"login": "Connexion"
|
||||
},
|
||||
"login_title": "Connexion - :platformName",
|
||||
"register_title": "Inscription - :platformName",
|
||||
"profile_title": "Mon Profil",
|
||||
"forgot_password_title": "Mot de passe oublié",
|
||||
"reset_password_title": "Réinitialiser le mot de passe",
|
||||
"email": "Email",
|
||||
"name": "Nom",
|
||||
"password": "Mot de passe",
|
||||
"password_confirmation": "Confirmation du mot de passe",
|
||||
"email_placeholder": "Entrez votre email",
|
||||
"password_placeholder": "Saisissez votre mot de passe",
|
||||
"password_confirmation_placeholder": "Confirmez votre mot de passe",
|
||||
"name_placeholder": "Saisissez votre nom",
|
||||
"current_password_placeholder": "Mot de passe actuel",
|
||||
"new_password_placeholder": "Nouveau mot de passe",
|
||||
"new_password_confirmation_placeholder": "Confirmez le nouveau mot de passe",
|
||||
"accept_terms": "J'accepte les conditions",
|
||||
"read_terms": "Lire les conditions",
|
||||
"privacy_policy": "Politique de confidentialité",
|
||||
"terms_of_service": "Conditions d'utilisation",
|
||||
"cookie_policy": "Politique de cookies",
|
||||
"data_protection": "Protection des données",
|
||||
"remember_me": "Se souvenir de moi",
|
||||
"login_button": "Se connecter",
|
||||
"register_button": "S'inscrire",
|
||||
"forgot_password": "Mot de passe oublié ?",
|
||||
"update_profile": "Mettre à jour le profil",
|
||||
"back_to_login": "Retour à la connexion",
|
||||
"logout": "Se déconnecter",
|
||||
"send_reset_link": "Envoyer le lien de réinitialisation",
|
||||
"reset_token": "Jeton de réinitialisation",
|
||||
"reset_email": "E-mail de réinitialisation",
|
||||
"reset_token_invalid": "Jeton invalide",
|
||||
"reset_token_expired": "Jeton expiré",
|
||||
"reset_email_invalid": "E-mail invalide",
|
||||
"reset_password_button": "Réinitialiser le mot de passe",
|
||||
"invalid_credentials": "Identifiants invalides",
|
||||
"account_inactive": "Compte inactif",
|
||||
"session_expired": "Session expirée. Veuillez vous reconnecter.",
|
||||
"registration_pending": "Inscription en attente",
|
||||
"registration_error": "Erreur d'inscription",
|
||||
"registration_success": "Inscription réussie ! Connectez-vous pour continuer.",
|
||||
"logout_success": "Déconnexion réussie",
|
||||
"profile_updated": "Profil mis à jour avec succès",
|
||||
"reset_link_sent": "Lien de réinitialisation envoyé avec succès",
|
||||
"current_avatar": "Avatar actuel",
|
||||
"close": "Fermer",
|
||||
"security_check": "Vérification de sécurité",
|
||||
"captcha_solved": "Correct !",
|
||||
"captcha_instruction": "Résolvez l'addition pour continuer.",
|
||||
"invalid_captcha": "Captcha invalide",
|
||||
"email_verified_success": "E-mail confirmé avec succès. Connectez-vous.",
|
||||
"verification_email_sent": "E-mail de confirmation envoyé. Vérifiez votre boîte de réception.",
|
||||
"email_already_verified": "Cet e-mail a déjà été vérifié. Connectez-vous.",
|
||||
"email_not_found": "E-mail introuvable.",
|
||||
"resend_verification": "Renvoyer la confirmation",
|
||||
"too_many_attempts": "Trop de tentatives. Réessayez plus tard.",
|
||||
"captcha_reload": "Recharger le défi",
|
||||
"terms_and_conditions": "Termes et Conditions",
|
||||
"privacy_policy_text": "Texte de la politique de confidentialité...",
|
||||
"terms_of_service_text": "Texte des conditions d'utilisation...",
|
||||
"cookie_policy_text": "Texte de la politique de cookies...",
|
||||
"data_protection_text": "Texte de la protection des données...",
|
||||
"phone": "Téléphone",
|
||||
"bio": "Biographie",
|
||||
"current_password": "Mot de passe actuel",
|
||||
"avatar": "Avatar",
|
||||
"cancel": "Annuler",
|
||||
"preview": "Aperçu",
|
||||
"captcha_question": "What is :a + :b?",
|
||||
"captcha_placeholder": "Enter the result"
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Authentification",
|
||||
"description": "Module d'authentification et d'autorisation",
|
||||
"welcome": "Bienvenue dans le système de gestion de contenu",
|
||||
"login_required": "Connexion requise pour accéder à cette page",
|
||||
"access_denied": "Accès refusé",
|
||||
"invalid_credentials": "Identifiants invalides",
|
||||
"session_expired": "Votre session a expiré. Veuillez vous reconnecter."
|
||||
}
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"menu": {
|
||||
"dashboard": "Autenticação",
|
||||
"login": "Login"
|
||||
},
|
||||
"login_title": "Login - :platformName",
|
||||
"register_title": "Cadastro - :platformName",
|
||||
"profile_title": "Meu Perfil",
|
||||
"forgot_password_title": "Esqueci minha senha",
|
||||
"reset_password_title": "Redefinir senha",
|
||||
"password_reset_success": "Senha redefinida com sucesso! Faça login para continuar.",
|
||||
"password_confirmation_error": "As senhas não coincidem.",
|
||||
"weak_password_error": "A senha deve ter pelo menos 8 caracteres.",
|
||||
"reset_error": "Ocorreu um erro ao redefinir sua senha. Verifique os dados e tente novamente.",
|
||||
"email": "E-mail",
|
||||
"name": "Nome",
|
||||
"password": "Senha",
|
||||
"password_confirmation": "Confirmação de Senha",
|
||||
"email_placeholder": "Digite seu email",
|
||||
"password_placeholder": "Digite sua senha",
|
||||
"password_confirmation_placeholder": "Confirme sua senha",
|
||||
"name_placeholder": "Digite seu nome",
|
||||
"current_password_placeholder": "Senha atual",
|
||||
"new_password_placeholder": "Nova senha",
|
||||
"new_password_confirmation_placeholder": "Confirme a nova senha",
|
||||
"accept_terms": "Aceito os termos",
|
||||
"read_terms": "Ler os termos",
|
||||
"privacy_policy": "Política de Privacidade",
|
||||
"terms_of_service": "Termos de Serviço",
|
||||
"cookie_policy": "Política de Cookies",
|
||||
"data_protection": "Proteção de Dados",
|
||||
"remember_me": "Lembrar-me",
|
||||
"login_button": "Entrar",
|
||||
"register_button": "Cadastrar",
|
||||
"forgot_password": "Esqueceu a senha?",
|
||||
"update_profile": "Atualizar Perfil",
|
||||
"back_to_login": "Voltar ao Login",
|
||||
"logout": "Sair",
|
||||
"send_reset_link": "Enviar Link de Redefinição",
|
||||
"reset_token": "Token de Redefinição",
|
||||
"reset_email": "Email para Redefinição",
|
||||
"reset_token_invalid": "Token inválido",
|
||||
"reset_token_expired": "Token expirado",
|
||||
"reset_email_invalid": "Email inválido",
|
||||
"reset_password_button": "Redefinir Senha",
|
||||
"invalid_credentials": "Credenciais inválidas",
|
||||
"account_inactive": "Conta inativa",
|
||||
"session_expired": "Sessão expirada. Por favor, faça login novamente.",
|
||||
"registration_pending": "Cadastro pendente",
|
||||
"registration_error": "Erro no cadastro",
|
||||
"registration_success": "Cadastro realizado com sucesso! Faça login para continuar.",
|
||||
"logout_success": "Logout realizado com sucesso",
|
||||
"profile_updated": "Perfil atualizado com sucesso",
|
||||
"reset_link_sent": "Link de redefinição enviado com sucesso",
|
||||
"current_avatar": "Avatar atual",
|
||||
"close": "Fechar",
|
||||
"security_check": "Verificação de Segurança",
|
||||
"captcha_solved": "Correto!",
|
||||
"captcha_instruction": "Resolva a soma para continuar.",
|
||||
"invalid_captcha": "Captcha inválido",
|
||||
"email_verified_success": "E-mail confirmado com sucesso. Faça login.",
|
||||
"verification_email_sent": "E-mail de confirmação enviado. Verifique sua caixa de entrada.",
|
||||
"email_already_verified": "Este e-mail já foi verificado. Faça login.",
|
||||
"email_not_found": "E-mail não encontrado.",
|
||||
"resend_verification": "Reenviar confirmação",
|
||||
"too_many_attempts": "Muitas tentativas. Tente novamente mais tarde.",
|
||||
"captcha_reload": "Trocar desafio",
|
||||
"terms_and_conditions": "Termos e Condições",
|
||||
"privacy_policy_text": "Texto da política de privacidade...",
|
||||
"terms_of_service_text": "Texto dos termos de serviço...",
|
||||
"cookie_policy_text": "Texto da política de cookies...",
|
||||
"data_protection_text": "Texto da proteção de dados...",
|
||||
"phone": "Telefone",
|
||||
"bio": "Biografia",
|
||||
"current_password": "Senha atual",
|
||||
"new_password": "Nova senha",
|
||||
"avatar": "Avatar",
|
||||
"cancel": "Cancelar",
|
||||
"preview": "Visualização",
|
||||
"captcha_question": "What is :a + :b?",
|
||||
"captcha_placeholder": "Enter the result"
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Autenticação",
|
||||
"description": "Módulo de autenticação e autorização do sistema",
|
||||
"welcome": "Bem-vindo ao Sistema de Gerenciamento de Conteúdo",
|
||||
"login_required": "É necessário fazer login para acessar esta página",
|
||||
"access_denied": "Acesso negado",
|
||||
"invalid_credentials": "Credenciais inválidas",
|
||||
"session_expired": "Sua sessão expirou. Por favor, faça login novamente."
|
||||
}
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "auth",
|
||||
"display_name": "Authentication",
|
||||
"description": "Authentication and authorization module for user management",
|
||||
"version": "1.0.0",
|
||||
"author": "SysCMS Team",
|
||||
"dependencies": [],
|
||||
"permissions": [
|
||||
"auth.login",
|
||||
"auth.logout",
|
||||
"auth.register",
|
||||
"auth.profile",
|
||||
"auth.password.reset",
|
||||
"auth.password.change"
|
||||
],
|
||||
"settings": {
|
||||
"allow_registration": true,
|
||||
"require_email_verification": false,
|
||||
"password_min_length": 8,
|
||||
"session_timeout": 120
|
||||
},
|
||||
"assets": {
|
||||
"css": [],
|
||||
"js": []
|
||||
},
|
||||
"providers": [
|
||||
"Modules\\Auth\\Providers\\AuthServiceProvider"
|
||||
],
|
||||
"routes": {
|
||||
"web": "routes/web.php",
|
||||
"api": "routes/api.php"
|
||||
},
|
||||
"menu": {
|
||||
"dashboard": {
|
||||
"icon": "bi bi-person-check",
|
||||
"label": "auth.menu.dashboard",
|
||||
"route": "admin/dashboard",
|
||||
"order": 1
|
||||
},
|
||||
"public": {
|
||||
"label": "auth.menu.login",
|
||||
"route": "login",
|
||||
"order": 1
|
||||
}
|
||||
},
|
||||
"views": "views",
|
||||
"locales": "locales",
|
||||
"module_ative": true,
|
||||
"module_public": true,
|
||||
"module_blocked": true,
|
||||
"ready": true,
|
||||
"tables": [
|
||||
"users"
|
||||
]
|
||||
}
|
||||
Executable
Executable
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SysCMS - Content Management System
|
||||
*
|
||||
* Copyright (c) 2025 Marco Costa
|
||||
* All rights reserved.
|
||||
*
|
||||
* Author: Marco Costa
|
||||
* Email: contato@syspanel.com.br
|
||||
* Project: SysCMS
|
||||
*
|
||||
* This software is licensed, not sold.
|
||||
* Permission is granted to use this software
|
||||
* subject to the terms of the SysCMS Commercial License.
|
||||
*
|
||||
* Redistribution, sublicensing, resale, or
|
||||
* disclosure of this software, in whole or in part,
|
||||
* is strictly prohibited without prior written permission.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
||||
*/
|
||||
|
||||
use Kernel\Application;
|
||||
|
||||
/**
|
||||
* Register auth module routes
|
||||
*
|
||||
* @param Application $app
|
||||
* @return void
|
||||
*/
|
||||
return function (Application $app) {
|
||||
// Authentication routes
|
||||
$app->addRoute('GET', '/login', [\Modules\Auth\Controllers\AuthController::class, 'showLoginForm']);
|
||||
$app->addRoute('POST', '/login', [\Modules\Auth\Controllers\AuthController::class, 'login']);
|
||||
$app->addRoute('GET', '/logout', [\Modules\Auth\Controllers\AuthController::class, 'logout']);
|
||||
|
||||
// Registration routes
|
||||
$app->addRoute('GET', '/register', [\Modules\Auth\Controllers\AuthController::class, 'showRegistrationForm']);
|
||||
$app->addRoute('POST', '/register', [\Modules\Auth\Controllers\AuthController::class, 'register']);
|
||||
|
||||
// Profile routes
|
||||
$app->addRoute('GET', '/profile', [\Modules\Auth\Controllers\AuthController::class, 'profile']);
|
||||
$app->addRoute('POST', '/profile', [\Modules\Auth\Controllers\AuthController::class, 'updateProfile']);
|
||||
|
||||
// Password reset routes
|
||||
$app->addRoute('GET', '/forgot-password', [\Modules\Auth\Controllers\AuthController::class, 'showForgotPasswordForm']);
|
||||
$app->addRoute('POST', '/forgot-password', [\Modules\Auth\Controllers\AuthController::class, 'sendResetLink']);
|
||||
$app->addRoute('GET', '/reset-password/{token}', [\Modules\Auth\Controllers\AuthController::class, 'showResetPasswordForm']);
|
||||
$app->addRoute('POST', '/reset-password', [\Modules\Auth\Controllers\AuthController::class, 'resetPassword']);
|
||||
|
||||
// Email verification
|
||||
$app->addRoute('GET', '/verify-email/resend', [\Modules\Auth\Controllers\AuthController::class, 'showResendVerificationForm']);
|
||||
$app->addRoute('POST', '/verify-email/resend', [\Modules\Auth\Controllers\AuthController::class, 'resendVerification']);
|
||||
$app->addRoute('GET', '/verify-email/{token}', [\Modules\Auth\Controllers\AuthController::class, 'verifyEmail']);
|
||||
};
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
{% extends "layouts/base.twig" %}
|
||||
|
||||
{% block title %}{{ __('auth.forgot_password_title', {'platformName': platformName}) }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<div class="card shadow-lg border-0 rounded-lg mt-5">
|
||||
<div class="card-header">
|
||||
<h3 class="text-center font-weight-light my-4">
|
||||
{{ __('auth.forgot_password_title', {'platformName': platformName}) }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{ error == 1 ? __('auth.email_not_found') : '' }}
|
||||
{{ error == 'captcha' ? __('auth.invalid_captcha') : '' }}
|
||||
{{ error == 'rate' ? __('auth.too_many_attempts') : '' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if success %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('auth.reset_link_sent') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST" action="{{ url('forgot-password') }}">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input type="email" class="form-control" id="email" name="email" placeholder="{{ __('auth.email_placeholder') }}" required>
|
||||
<label for="email">{{ __('auth.email') }}</label>
|
||||
</div>
|
||||
|
||||
{% if require_captcha_forgot %}
|
||||
<div class="mb-3" x-data="{
|
||||
num1: 0,
|
||||
num2: 0,
|
||||
userAnswer: '',
|
||||
solved: false,
|
||||
generate() {
|
||||
this.num1 = Math.floor(Math.random() * 10) + 1;
|
||||
this.num2 = Math.floor(Math.random() * 10) + 1;
|
||||
this.userAnswer = '';
|
||||
this.solved = false;
|
||||
},
|
||||
check() {
|
||||
if (parseInt(this.userAnswer) === (this.num1 + this.num2)) {
|
||||
this.solved = true;
|
||||
} else {
|
||||
this.solved = false;
|
||||
}
|
||||
}
|
||||
}" x-init="generate()">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold mb-2">{{ __('auth.security_check') }}</label>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="px-3 py-2 bg-white border rounded fw-bold fs-5 text-nowrap">
|
||||
<span x-text="num1"></span> + <span x-text="num2"></span> =
|
||||
</div>
|
||||
<input type="number" class="form-control" style="width: 80px;" x-model="userAnswer" @input="check()" placeholder="?">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm ms-1" @click="generate()" title="{{ __('auth.captcha_reload') }}">
|
||||
<i class="bi bi-arrow-clockwise"></i> ↻
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2" style="min-height: 24px;">
|
||||
<span x-show="solved" class="text-success fw-bold small">
|
||||
✓ {{ __('auth.captcha_solved') }}
|
||||
</span>
|
||||
<span x-show="!solved" class="text-muted small">
|
||||
{{ __('auth.captcha_instruction') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="captcha_valid" :value="solved ? '1' : ''">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-grid">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ __('auth.send_reset_link') }}
|
||||
</button>
|
||||
<a href="{{ url('login') }}" class="btn btn-link">
|
||||
{{ __('auth.back_to_login') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Auto-focus on email field
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const emailField = document.getElementById('email');
|
||||
if (emailField) {
|
||||
emailField.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Executable
Executable
+259
@@ -0,0 +1,259 @@
|
||||
{% extends "layouts/base.twig" %}
|
||||
|
||||
{% block title %}{{ __('auth.login_title', {'platformName': platformName}) }}{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
.login-container {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.login-card {
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
}
|
||||
.login-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.login-header h3 {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.login-body {
|
||||
padding: 40px 30px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.form-floating {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-control {
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 12px 15px;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.btn-link {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn-link:hover {
|
||||
color: #764ba2;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.form-check-input:checked {
|
||||
background-color: #667eea;
|
||||
border-color: #667eea;
|
||||
}
|
||||
.alert {
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.captcha-container {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.captcha-image {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
color: #6c757d;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="login-container">
|
||||
<div class="row justify-content-center w-100">
|
||||
<div class="col-lg-5">
|
||||
<div class="card login-card">
|
||||
<div class="login-header">
|
||||
<h3>{{ __('auth.login_title', {'platformName': platformName}) }}</h3>
|
||||
</div>
|
||||
<div class="login-body">
|
||||
{% if password_reset_success %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('auth.password_reset_success') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{ error == 1 ? __('auth.invalid_credentials') : '' }}
|
||||
{{ error == 2 ? __('auth.account_inactive') : '' }}
|
||||
{{ error == 3 ? __('auth.registration_pending') : '' }}
|
||||
{{ error == 'captcha' ? __('auth.invalid_captcha') : '' }}
|
||||
{{ error == 'rate' ? __('auth.too_many_attempts') : '' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if expired %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{{ __('auth.session_expired') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST" action="{{ url('login') }}">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input type="email" class="form-control" id="email" name="email" placeholder="{{ __('auth.email_placeholder') }}" value="{{ old('email') }}" required>
|
||||
<label for="email">{{ __('auth.email') }}</label>
|
||||
</div>
|
||||
|
||||
{% include 'components/password-field.twig' with {
|
||||
id: 'password',
|
||||
name: 'password',
|
||||
label: __('auth.password'),
|
||||
placeholder: __('auth.password_placeholder'),
|
||||
required: true
|
||||
} %}
|
||||
|
||||
{% if require_captcha_login %}
|
||||
<div class="captcha-container" x-data="{
|
||||
num1: 0,
|
||||
num2: 0,
|
||||
userAnswer: '',
|
||||
solved: false,
|
||||
generate() {
|
||||
this.num1 = Math.floor(Math.random() * 10) + 1;
|
||||
this.num2 = Math.floor(Math.random() * 10) + 1;
|
||||
this.userAnswer = '';
|
||||
this.solved = false;
|
||||
},
|
||||
check() {
|
||||
if (parseInt(this.userAnswer) === (this.num1 + this.num2)) {
|
||||
this.solved = true;
|
||||
} else {
|
||||
this.solved = false;
|
||||
}
|
||||
}
|
||||
}" x-init="generate()">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold mb-2">{{ __('auth.security_check') }}</label>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="px-3 py-2 bg-white border rounded fw-bold fs-5 text-nowrap">
|
||||
<span x-text="num1"></span> + <span x-text="num2"></span> =
|
||||
</div>
|
||||
<input type="number" class="form-control" style="width: 80px;" x-model="userAnswer" @input="check()" placeholder="?">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm ms-1" @click="generate()" title="{{ __('auth.captcha_reload') }}">
|
||||
<i class="bi bi-arrow-clockwise"></i> ↻
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2" style="min-height: 24px;">
|
||||
<span x-show="solved" class="text-success fw-bold small">
|
||||
✓ {{ __('auth.captcha_solved') }}
|
||||
</span>
|
||||
<span x-show="!solved" class="text-muted small">
|
||||
{{ __('auth.captcha_instruction') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="captcha_valid" :value="solved ? '1' : ''">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="remember" name="remember">
|
||||
<label class="form-check-label" for="remember">{{ __('auth.remember_me') }}</label>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-3">
|
||||
<button class="btn btn-primary btn-lg" type="submit">
|
||||
{{ __('auth.login_button') }}
|
||||
</button>
|
||||
<a href="{{ url('forgot-password') }}" class="btn btn-link">
|
||||
{{ __('auth.forgot_password') }}
|
||||
</a>
|
||||
{% if allow_registration %}
|
||||
<a href="{{ url('register') }}" class="btn btn-link">
|
||||
{{ __('auth.register_button') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-3 d-grid">
|
||||
<a href="{{ url('verify-email/resend') }}" class="btn btn-outline-secondary btn-sm">
|
||||
{{ __('auth.resend_verification') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if success %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('auth.logout_success') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if registered %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('auth.registration_success') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if verified %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('auth.email_verified_success') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if resend %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
{{ __('auth.verification_email_sent') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Auto-focus on email field
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const emailField = document.getElementById('email');
|
||||
if (emailField) {
|
||||
emailField.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
{% extends "layouts/dashboard.twig" %}
|
||||
|
||||
{% block title %}{{ __('auth.profile_title') }} - {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ __('auth.profile_title') }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if success %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('auth.profile_updated') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST" action="{{ url('profile') }}" enctype="multipart/form-data">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="name" class="form-label">{{ __('auth.name') }}</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{ user.name }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="email" class="form-label">{{ __('auth.email') }}</label>
|
||||
<input type="email" class="form-control" id="email" name="email" value="{{ user.email }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="phone" class="form-label">{{ __('auth.phone') }}</label>
|
||||
<input type="text" class="form-control" id="phone" name="phone" value="{{ user.phone }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 mb-3">
|
||||
<label for="bio" class="form-label">{{ __('auth.bio') }}</label>
|
||||
<textarea class="form-control" id="bio" name="bio" rows="4">{{ user.bio }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="avatar" class="form-label">{{ __('auth.avatar') }}</label>
|
||||
<input type="file" class="form-control" id="avatar" name="avatar" accept="image/*">
|
||||
<div id="avatarPreview" class="mt-2"></div>
|
||||
{% if user.avatar %}
|
||||
<div class="mt-2">
|
||||
<small class="form-text text-muted">{{ __('auth.current_avatar') }}:</small>
|
||||
<img src="{{ user.avatar_url }}" alt="{{ __('auth.current_avatar') }}" class="img-thumbnail" style="max-width: 100px;">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3" x-data="{ show: false }">
|
||||
<label for="current_password" class="form-label">{{ __('auth.current_password') }}</label>
|
||||
<div class="input-group">
|
||||
<input :type="show ? 'text' : 'password'" class="form-control" id="current_password" name="current_password" placeholder="{{ __('auth.current_password_placeholder') }}" autocomplete="new-password" value="">
|
||||
<button class="btn btn-outline-secondary" type="button" @click="show = !show">
|
||||
<i :class="show ? 'bi bi-eye-slash' : 'bi bi-eye'"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3" x-data="{ show: false }">
|
||||
<label for="password" class="form-label">{{ __('auth.new_password') }}</label>
|
||||
<div class="input-group">
|
||||
<input :type="show ? 'text' : 'password'" class="form-control" id="password" name="password" placeholder="{{ __('auth.new_password_placeholder') }}" autocomplete="new-password" value="">
|
||||
<button class="btn btn-outline-secondary" type="button" @click="show = !show">
|
||||
<i :class="show ? 'bi bi-eye-slash' : 'bi bi-eye'"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3" x-data="{ show: false }">
|
||||
<label for="password_confirmation" class="form-label">{{ __('auth.password_confirmation') }}</label>
|
||||
<div class="input-group">
|
||||
<input :type="show ? 'text' : 'password'" class="form-control" id="password_confirmation" name="password_confirmation" placeholder="{{ __('auth.password_confirmation_placeholder') }}" autocomplete="new-password" value="">
|
||||
<button class="btn btn-outline-secondary" type="button" @click="show = !show">
|
||||
<i :class="show ? 'bi bi-eye-slash' : 'bi bi-eye'"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">{{ __('auth.update_profile') }}</button>
|
||||
<a href="{{ url('dashboard') }}" class="btn btn-secondary">{{ __('auth.cancel') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Preview avatar before upload
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const avatarInput = document.getElementById('avatar');
|
||||
const avatarPreview = document.getElementById('avatarPreview');
|
||||
|
||||
if (avatarInput && avatarPreview) {
|
||||
avatarInput.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function(e) {
|
||||
avatarPreview.innerHTML = `<img src="${e.target.result}" alt="{{ __('auth.preview') }}" class="img-thumbnail" style="max-width: 200px;">`;
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Executable
+131
@@ -0,0 +1,131 @@
|
||||
{% extends "layouts/base.twig" %}
|
||||
|
||||
{% block title %}{{ __('auth.register_title', {'platformName': platformName}) }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<div class="card shadow-lg border-0 rounded-lg mt-5">
|
||||
<div class="card-header">
|
||||
<h3 class="text-center font-weight-light my-4">
|
||||
{{ __('auth.register_title', {'platformName': platformName}) }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{ error == 'captcha' ? __('auth.invalid_captcha') : __('auth.registration_error') }}
|
||||
{{ error == 'rate' ? __('auth.too_many_attempts') : '' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST" action="{{ url('register') }}">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="{{ __('auth.name_placeholder') }}" value="{{ old('name') }}" required>
|
||||
<label for="name">{{ __('auth.name') }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input type="email" class="form-control" id="email" name="email" placeholder="{{ __('auth.email_placeholder') }}" value="{{ old('email') }}" required>
|
||||
<label for="email">{{ __('auth.email') }}</label>
|
||||
</div>
|
||||
|
||||
{% include 'components/password-field.twig' with {
|
||||
id: 'password',
|
||||
name: 'password',
|
||||
label: __('auth.password'),
|
||||
placeholder: __('auth.password_placeholder'),
|
||||
required: true
|
||||
} %}
|
||||
|
||||
{% include 'components/password-field.twig' with {
|
||||
id: 'password_confirmation',
|
||||
name: 'password_confirmation',
|
||||
label: __('auth.password_confirmation'),
|
||||
placeholder: __('auth.password_confirmation_placeholder'),
|
||||
required: true
|
||||
} %}
|
||||
|
||||
{% if require_captcha_register %}
|
||||
<div class="mb-3" x-data="{
|
||||
num1: 0,
|
||||
num2: 0,
|
||||
userAnswer: '',
|
||||
solved: false,
|
||||
generate() {
|
||||
this.num1 = Math.floor(Math.random() * 10) + 1;
|
||||
this.num2 = Math.floor(Math.random() * 10) + 1;
|
||||
this.userAnswer = '';
|
||||
this.solved = false;
|
||||
},
|
||||
check() {
|
||||
if (parseInt(this.userAnswer) === (this.num1 + this.num2)) {
|
||||
this.solved = true;
|
||||
} else {
|
||||
this.solved = false;
|
||||
}
|
||||
}
|
||||
}" x-init="generate()">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold mb-2">{{ __('auth.security_check') }}</label>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="px-3 py-2 bg-white border rounded fw-bold fs-5 text-nowrap">
|
||||
<span x-text="num1"></span> + <span x-text="num2"></span> =
|
||||
</div>
|
||||
<input type="number" class="form-control" style="width: 80px;" x-model="userAnswer" @input="check()" placeholder="?">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm ms-1" @click="generate()" title="{{ __('auth.captcha_reload') }}">
|
||||
<i class="bi bi-arrow-clockwise"></i> ↻
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2" style="min-height: 24px;">
|
||||
<span x-show="solved" class="text-success fw-bold small">
|
||||
✓ {{ __('auth.captcha_solved') }}
|
||||
</span>
|
||||
<span x-show="!solved" class="text-muted small">
|
||||
{{ __('auth.captcha_instruction') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="captcha_valid" :value="solved ? '1' : ''">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="terms" name="terms" required>
|
||||
<label class="form-check-label" for="terms">
|
||||
{{ __('auth.accept_terms') }}
|
||||
<a href="/user_terms.html" target="_blank">
|
||||
{{ __('auth.read_terms') }}
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
<button class="btn btn-primary btn-lg" type="submit">
|
||||
{{ __('auth.register_button') }}
|
||||
</button>
|
||||
<a href="{{ url('login') }}" class="btn btn-link">
|
||||
{{ __('auth.back_to_login') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Auto-focus on name field
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const nameField = document.getElementById('name');
|
||||
if (nameField) {
|
||||
nameField.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
{% extends "layouts/base.twig" %}
|
||||
|
||||
{% block title %}{{ __('auth.resend_verification') }} - {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<div class="card shadow-lg border-0 rounded-lg mt-5">
|
||||
<div class="card-header">
|
||||
<h3 class="text-center font-weight-light my-4">
|
||||
{{ __('auth.resend_verification') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{{ error == 1 ? __('auth.email_not_found') : '' }}
|
||||
{{ error == 'captcha' ? __('auth.invalid_captcha') : '' }}
|
||||
{{ error == 'rate' ? __('auth.too_many_attempts') : '' }}
|
||||
{{ error == 'verified' ? __('auth.email_already_verified') : '' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if success %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('auth.verification_email_sent') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST" action="{{ url('verify-email/resend') }}">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input type="email" class="form-control" id="email" name="email" placeholder="{{ __('auth.email_placeholder') }}" required value="{{ old('email') }}">
|
||||
<label for="email">{{ __('auth.email') }}</label>
|
||||
</div>
|
||||
|
||||
{% if require_captcha_resend %}
|
||||
<div class="mb-3" x-data="{
|
||||
num1: 0,
|
||||
num2: 0,
|
||||
userAnswer: '',
|
||||
solved: false,
|
||||
generate() {
|
||||
this.num1 = Math.floor(Math.random() * 10) + 1;
|
||||
this.num2 = Math.floor(Math.random() * 10) + 1;
|
||||
this.userAnswer = '';
|
||||
this.solved = false;
|
||||
},
|
||||
check() {
|
||||
if (parseInt(this.userAnswer) === (this.num1 + this.num2)) {
|
||||
this.solved = true;
|
||||
} else {
|
||||
this.solved = false;
|
||||
}
|
||||
}
|
||||
}" x-init="generate()">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold mb-2">{{ __('auth.security_check') }}</label>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="px-3 py-2 bg-white border rounded fw-bold fs-5 text-nowrap">
|
||||
<span x-text="num1"></span> + <span x-text="num2"></span> =
|
||||
</div>
|
||||
<input type="number" class="form-control" style="width: 80px;" x-model="userAnswer" @input="check()" placeholder="?">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm ms-1" @click="generate()" title="{{ __('auth.captcha_reload') }}">
|
||||
<i class="bi bi-arrow-clockwise"></i> ↻
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2" style="min-height: 24px;">
|
||||
<span x-show="solved" class="text-success fw-bold small">
|
||||
✓ {{ __('auth.captcha_solved') }}
|
||||
</span>
|
||||
<span x-show="!solved" class="text-muted small">
|
||||
{{ __('auth.captcha_instruction') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="captcha_valid" :value="solved ? '1' : ''">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-grid">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ __('auth.resend_verification') }}
|
||||
</button>
|
||||
<a href="{{ url('login') }}" class="btn btn-link">
|
||||
{{ __('auth.back_to_login') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Auto-focus on email field
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const emailField = document.getElementById('email');
|
||||
if (emailField) {
|
||||
emailField.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Executable
+163
@@ -0,0 +1,163 @@
|
||||
{% extends "layouts/base.twig" %}
|
||||
|
||||
{% block title %}{{ __('auth.reset_password_title', {'platformName': platformName}) }}{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
.reset-container {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.reset-card {
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
max-width: 450px;
|
||||
width: 100%;
|
||||
}
|
||||
.reset-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.reset-header h3 {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.reset-body {
|
||||
padding: 40px 30px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.form-floating {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-control {
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 12px 15px;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.btn-link {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn-link:hover {
|
||||
color: #764ba2;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.alert {
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="reset-container">
|
||||
<div class="row justify-content-center w-100">
|
||||
<div class="col-lg-5">
|
||||
<div class="card reset-card">
|
||||
<div class="reset-header">
|
||||
<h3>{{ __('auth.reset_password_title', {'platformName': platformName}) }}</h3>
|
||||
</div>
|
||||
<div class="reset-body">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% if error == 'password_confirmation' %}
|
||||
{{ __('auth.password_confirmation_error') }}
|
||||
{% elseif error == 'weak_password' %}
|
||||
{{ __('auth.weak_password_error') }}
|
||||
{% elseif error == '4' %}
|
||||
{{ __('auth.reset_error') }}
|
||||
{% elseif error == '1' %}
|
||||
{{ __('auth.reset_token_invalid') }}
|
||||
{% elseif error == '2' %}
|
||||
{{ __('auth.reset_token_expired') }}
|
||||
{% elseif error == '3' %}
|
||||
{{ __('auth.reset_email_invalid') }}
|
||||
{% else %}
|
||||
{{ __('auth.reset_error') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="POST" action="{{ url('reset-password') }}">
|
||||
{{ csrf_field() }}
|
||||
|
||||
<input type="hidden" name="token" value="{{ token }}">
|
||||
<input type="hidden" name="email" value="{{ email }}">
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input type="email" class="form-control" id="email_display" value="{{ email }}" disabled>
|
||||
<label for="email_display">{{ __('auth.email') }}</label>
|
||||
</div>
|
||||
|
||||
{% include 'components/password-field.twig' with {
|
||||
id: 'password',
|
||||
name: 'password',
|
||||
label: __('auth.new_password'),
|
||||
placeholder: __('auth.new_password_placeholder'),
|
||||
required: true
|
||||
} %}
|
||||
|
||||
{% include 'components/password-field.twig' with {
|
||||
id: 'password_confirmation',
|
||||
name: 'password_confirmation',
|
||||
label: __('auth.password_confirmation'),
|
||||
placeholder: __('auth.password_confirmation_placeholder'),
|
||||
required: true
|
||||
} %}
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-primary btn-lg" type="submit">
|
||||
{{ __('auth.reset_password_button') }}
|
||||
</button>
|
||||
<a href="{{ url('login') }}" class="btn btn-link text-decoration-none text-center">
|
||||
{{ __('auth.back_to_login') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Auto-focus on password field
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const passwordField = document.getElementById('password');
|
||||
if (passwordField) {
|
||||
passwordField.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user