Compare commits
8 Commits
dev
...
9483d6aa71
| Author | SHA1 | Date | |
|---|---|---|---|
| 9483d6aa71 | |||
| d6a80098db | |||
| 4ffb0d7f1e | |||
| a672cd8330 | |||
| 6a05f188af | |||
| 00d585738c | |||
| fbb344bc8e | |||
| f6014a55e4 |
402
app/UsersManager.class.php
Normal file
402
app/UsersManager.class.php
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* UserManager - Gestione utenti e autenticazione tramite cookie
|
||||||
|
* PHP 8+ con MariaDB
|
||||||
|
*/
|
||||||
|
|
||||||
|
class UserManager {
|
||||||
|
private PDO $db;
|
||||||
|
private string $cookieName = 'auth_token';
|
||||||
|
private int $cookieLifetime = 2592000; // 30 giorni in secondi
|
||||||
|
private int $maxLoginAttempts = 5;
|
||||||
|
private int $lockoutTime = 900; // 15 minuti in secondi
|
||||||
|
|
||||||
|
public function __construct(PDO $db) {
|
||||||
|
$this->db = $db;
|
||||||
|
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra un nuovo utente
|
||||||
|
*/
|
||||||
|
public function register(string $username, string $email, string $password, string $fullName = null): bool {
|
||||||
|
try {
|
||||||
|
$passwordHash = password_hash($password, PASSWORD_ARGON2ID);
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
INSERT INTO users (username, email, password_hash, full_name)
|
||||||
|
VALUES (:username, :email, :password_hash, :full_name)
|
||||||
|
");
|
||||||
|
|
||||||
|
$result = $stmt->execute([
|
||||||
|
'username' => $username,
|
||||||
|
'email' => $email,
|
||||||
|
'password_hash' => $passwordHash,
|
||||||
|
'full_name' => $fullName
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$userId = $this->db->lastInsertId();
|
||||||
|
$this->logActivity($userId, $username, 'REGISTER', "Nuovo utente registrato: $username");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("Errore registrazione: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login utente con username/email e password
|
||||||
|
*/
|
||||||
|
public function login(string $identifier, string $password, bool $rememberMe = true): array {
|
||||||
|
// Verifica blocco per troppi tentativi
|
||||||
|
if ($this->isAccountLocked($identifier)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Account temporaneamente bloccato per troppi tentativi falliti'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cerca utente per username o email
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
SELECT id, username, email, password_hash, is_active, full_name
|
||||||
|
FROM users
|
||||||
|
WHERE (username = :identifier OR email = :identifier) AND is_active = 1
|
||||||
|
");
|
||||||
|
$stmt->execute(['identifier' => $identifier]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||||
|
$this->logFailedAttempt($identifier);
|
||||||
|
$this->logActivity(null, $identifier, 'LOGIN_FAILED', "Tentativo di login fallito");
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Credenziali non valide'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login riuscito - pulisce tentativi falliti
|
||||||
|
$this->clearFailedAttempts($identifier);
|
||||||
|
|
||||||
|
// Aggiorna last_login
|
||||||
|
$stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $user['id']]);
|
||||||
|
|
||||||
|
// Crea token di autenticazione se richiesto
|
||||||
|
if ($rememberMe) {
|
||||||
|
$this->createAuthToken($user['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Salva in sessione dati base
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['username'] = $user['username'];
|
||||||
|
$_SESSION['full_name'] = $user['full_name'];
|
||||||
|
|
||||||
|
$this->logActivity($user['id'], $user['username'], 'LOGIN', "Login effettuato");
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'user' => [
|
||||||
|
'id' => $user['id'],
|
||||||
|
'username' => $user['username'],
|
||||||
|
'email' => $user['email'],
|
||||||
|
'full_name' => $user['full_name']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-autenticazione con solo password (quando cookie è scaduto ma username è memorizzato)
|
||||||
|
*/
|
||||||
|
public function reAuthenticate(string $username, string $password): array {
|
||||||
|
return $this->login($username, $password, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica validità sessione/cookie
|
||||||
|
*/
|
||||||
|
public function checkAuth(): ?array {
|
||||||
|
// Prima controlla se esiste sessione attiva
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
return $this->getUserById($_SESSION['user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Altrimenti verifica cookie
|
||||||
|
if (isset($_COOKIE[$this->cookieName])) {
|
||||||
|
return $this->validateAuthToken($_COOKIE[$this->cookieName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea token di autenticazione e imposta cookie
|
||||||
|
*/
|
||||||
|
private function createAuthToken(int $userId): bool {
|
||||||
|
try {
|
||||||
|
// Genera selector e token casuali
|
||||||
|
$selector = bin2hex(random_bytes(16));
|
||||||
|
$token = bin2hex(random_bytes(32));
|
||||||
|
$tokenHash = hash('sha256', $token);
|
||||||
|
|
||||||
|
$expiresAt = date('Y-m-d H:i:s', time() + $this->cookieLifetime);
|
||||||
|
|
||||||
|
// Salva nel database
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
INSERT INTO auth_tokens (user_id, token, selector, expires_at, ip_address, user_agent)
|
||||||
|
VALUES (:user_id, :token, :selector, :expires_at, :ip, :ua)
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
'user_id' => $userId,
|
||||||
|
'token' => $tokenHash,
|
||||||
|
'selector' => $selector,
|
||||||
|
'expires_at' => $expiresAt,
|
||||||
|
'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||||
|
'ua' => $_SERVER['HTTP_USER_AGENT'] ?? null
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Imposta cookie (selector:token)
|
||||||
|
$cookieValue = $selector . ':' . $token;
|
||||||
|
setcookie(
|
||||||
|
$this->cookieName,
|
||||||
|
$cookieValue,
|
||||||
|
time() + $this->cookieLifetime,
|
||||||
|
'/',
|
||||||
|
'',
|
||||||
|
true, // Secure (solo HTTPS in produzione)
|
||||||
|
true // HttpOnly
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore creazione token: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida token di autenticazione dal cookie
|
||||||
|
*/
|
||||||
|
private function validateAuthToken(string $cookieValue): ?array {
|
||||||
|
try {
|
||||||
|
$parts = explode(':', $cookieValue);
|
||||||
|
if (count($parts) !== 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$selector, $token] = $parts;
|
||||||
|
$tokenHash = hash('sha256', $token);
|
||||||
|
|
||||||
|
// Cerca token nel database
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
SELECT at.*, u.id, u.username, u.email, u.full_name, u.is_active
|
||||||
|
FROM auth_tokens at
|
||||||
|
JOIN users u ON at.user_id = u.id
|
||||||
|
WHERE at.selector = :selector AND at.expires_at > NOW() AND u.is_active = 1
|
||||||
|
");
|
||||||
|
$stmt->execute(['selector' => $selector]);
|
||||||
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$result || !hash_equals($result['token'], $tokenHash)) {
|
||||||
|
$this->deleteAuthToken($selector);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token valido - rigenera sessione
|
||||||
|
$_SESSION['user_id'] = $result['id'];
|
||||||
|
$_SESSION['username'] = $result['username'];
|
||||||
|
$_SESSION['full_name'] = $result['full_name'];
|
||||||
|
|
||||||
|
// Aggiorna last_login
|
||||||
|
$stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = :id");
|
||||||
|
$stmt->execute(['id' => $result['id']]);
|
||||||
|
|
||||||
|
$this->logActivity($result['id'], $result['username'], 'AUTO_LOGIN', "Login automatico via cookie");
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $result['id'],
|
||||||
|
'username' => $result['username'],
|
||||||
|
'email' => $result['email'],
|
||||||
|
'full_name' => $result['full_name']
|
||||||
|
];
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore validazione token: " . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout - elimina sessione e cookie
|
||||||
|
*/
|
||||||
|
public function logout(): void {
|
||||||
|
$userId = $_SESSION['user_id'] ?? null;
|
||||||
|
$username = $_SESSION['username'] ?? 'unknown';
|
||||||
|
|
||||||
|
// Elimina token dal database se presente
|
||||||
|
if (isset($_COOKIE[$this->cookieName])) {
|
||||||
|
$parts = explode(':', $_COOKIE[$this->cookieName]);
|
||||||
|
if (count($parts) === 2) {
|
||||||
|
$this->deleteAuthToken($parts[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elimina cookie
|
||||||
|
setcookie($this->cookieName, '', time() - 3600, '/', '', true, true);
|
||||||
|
|
||||||
|
// Distrugge sessione
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
if ($userId) {
|
||||||
|
$this->logActivity($userId, $username, 'LOGOUT', "Logout effettuato");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elimina token dal database
|
||||||
|
*/
|
||||||
|
private function deleteAuthToken(string $selector): void {
|
||||||
|
try {
|
||||||
|
$stmt = $this->db->prepare("DELETE FROM auth_tokens WHERE selector = :selector");
|
||||||
|
$stmt->execute(['selector' => $selector]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore eliminazione token: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ottiene utente per ID
|
||||||
|
*/
|
||||||
|
public function getUserById(int $userId): ?array {
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
SELECT id, username, email, full_name, is_active, created_at, last_login
|
||||||
|
FROM users WHERE id = :id AND is_active = 1
|
||||||
|
");
|
||||||
|
$stmt->execute(['id' => $userId]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
return $user ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica se account è bloccato per troppi tentativi
|
||||||
|
*/
|
||||||
|
private function isAccountLocked(string $identifier): bool {
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
SELECT COUNT(*) as attempts
|
||||||
|
FROM failed_login_attempts
|
||||||
|
WHERE username = :identifier
|
||||||
|
AND ip_address = :ip
|
||||||
|
AND attempted_at > DATE_SUB(NOW(), INTERVAL :lockout SECOND)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'identifier' => $identifier,
|
||||||
|
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
|
||||||
|
'lockout' => $this->lockoutTime
|
||||||
|
]);
|
||||||
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
return $result['attempts'] >= $this->maxLoginAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra tentativo di login fallito
|
||||||
|
*/
|
||||||
|
private function logFailedAttempt(string $identifier): void {
|
||||||
|
try {
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
INSERT INTO failed_login_attempts (username, ip_address)
|
||||||
|
VALUES (:username, :ip)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'username' => $identifier,
|
||||||
|
'ip' => $_SERVER['REMOTE_ADDR'] ?? ''
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore log tentativo fallito: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pulisce tentativi falliti dopo login riuscito
|
||||||
|
*/
|
||||||
|
private function clearFailedAttempts(string $identifier): void {
|
||||||
|
try {
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
DELETE FROM failed_login_attempts
|
||||||
|
WHERE username = :username AND ip_address = :ip
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'username' => $identifier,
|
||||||
|
'ip' => $_SERVER['REMOTE_ADDR'] ?? ''
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore pulizia tentativi: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log attività utente
|
||||||
|
*/
|
||||||
|
public function logActivity(?int $userId, string $username, string $action, string $description = null): void {
|
||||||
|
try {
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
INSERT INTO user_activity_log (user_id, username, action, description, ip_address, user_agent)
|
||||||
|
VALUES (:user_id, :username, :action, :description, :ip, :ua)
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
'user_id' => $userId,
|
||||||
|
'username' => $username,
|
||||||
|
'action' => $action,
|
||||||
|
'description' => $description,
|
||||||
|
'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||||
|
'ua' => $_SERVER['HTTP_USER_AGENT'] ?? null
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore log attività: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pulizia token scaduti (da eseguire periodicamente)
|
||||||
|
*/
|
||||||
|
public function cleanExpiredTokens(): int {
|
||||||
|
try {
|
||||||
|
$stmt = $this->db->prepare("DELETE FROM auth_tokens WHERE expires_at < NOW()");
|
||||||
|
$stmt->execute();
|
||||||
|
return $stmt->rowCount();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore pulizia token: " . $e->getMessage());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ottiene username da cookie (per form di re-autenticazione)
|
||||||
|
*/
|
||||||
|
public function getRememberedUsername(): ?string {
|
||||||
|
if (!isset($_COOKIE[$this->cookieName])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode(':', $_COOKIE[$this->cookieName]);
|
||||||
|
if (count($parts) !== 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $this->db->prepare("
|
||||||
|
SELECT u.username
|
||||||
|
FROM auth_tokens at
|
||||||
|
JOIN users u ON at.user_id = u.id
|
||||||
|
WHERE at.selector = :selector
|
||||||
|
");
|
||||||
|
$stmt->execute(['selector' => $parts[0]]);
|
||||||
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
return $result['username'] ?? null;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
config/.DS_Store
vendored
Normal file
BIN
config/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -10,6 +10,9 @@ $DB_NAME = $_ENV['DB_DATABASE'];
|
|||||||
$DB_USER = $_ENV['DB_USERNAME'];
|
$DB_USER = $_ENV['DB_USERNAME'];
|
||||||
$DB_PASS = $_ENV['DB_PASSWORD'];
|
$DB_PASS = $_ENV['DB_PASSWORD'];
|
||||||
|
|
||||||
|
$db = new PDO("mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4",$DB_USER,$DB_PASS,[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||||
|
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
// $pdo = new PDO(
|
// $pdo = new PDO(
|
||||||
// "mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4",
|
// "mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4",
|
||||||
|
|||||||
6
config/init.php
Normal file
6
config/init.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/db.php';
|
||||||
|
require_once __DIR__ . '/../app/UsersManager.class.php';
|
||||||
|
$userManager = new UserManager($db);
|
||||||
|
$currentUser = $userManager->checkAuth();
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
|
||||||
|
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable('/var/www/html/confs/dpmy_dev/');
|
|
||||||
$dotenv->load();
|
|
||||||
|
|
||||||
$DB_HOST = $_ENV['DB_HOST'];
|
|
||||||
$DB_PORT = $_ENV['DB_PORT'];
|
|
||||||
$DB_NAME = $_ENV['DB_DATABASE'];
|
|
||||||
$DB_USER = $_ENV['DB_USERNAME'];
|
|
||||||
$DB_PASS = $_ENV['DB_PASSWORD'];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pdo = new PDO(
|
|
||||||
"mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4",
|
|
||||||
$DB_USER,
|
|
||||||
$DB_PASS,
|
|
||||||
[
|
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
|
||||||
]
|
|
||||||
);
|
|
||||||
echo "Connessione al database riuscita!";
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo "Errore connessione DB: " . $e->getMessage();
|
|
||||||
}
|
|
||||||
22
index.php
22
index.php
@@ -1,10 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once __DIR__ . '/config/init.php';
|
||||||
|
|
||||||
include 'parts/head.php';
|
include 'parts/head.php';
|
||||||
|
|
||||||
include 'parts/sidebar.php';
|
include 'parts/sidebar.php';
|
||||||
|
|
||||||
include 'parts/content.php';
|
|
||||||
|
|
||||||
|
if (!$currentUser) {
|
||||||
|
// Utente non autenticato
|
||||||
|
|
||||||
|
// Controlla se esiste un username memorizzato (cookie scaduto)
|
||||||
|
$rememberedUsername = $userManager->getRememberedUsername();
|
||||||
|
|
||||||
|
if ($rememberedUsername) {
|
||||||
|
include 'login.php';
|
||||||
|
} else {
|
||||||
|
include 'login.php';
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utente autenticato - memorizza in variabile globale per uso nelle pagine
|
||||||
|
$_SESSION['user'] = $currentUser;
|
||||||
|
|
||||||
|
|
||||||
|
include 'parts/content.php';
|
||||||
include 'parts/footer.php';
|
include 'parts/footer.php';
|
||||||
?>
|
?>
|
||||||
58
login.php
Normal file
58
login.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!-- Outer Row -->
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
|
||||||
|
<div class="col-xl-10 col-lg-12 col-md-9">
|
||||||
|
|
||||||
|
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<!-- Nested Row within Card Body -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 d-none d-lg-block bg-login-image"></div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="h4 text-gray-900 mb-4">Welcome Back!</h1>
|
||||||
|
</div>
|
||||||
|
<form class="user">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="email" class="form-control form-control-user"
|
||||||
|
id="exampleInputEmail" aria-describedby="emailHelp"
|
||||||
|
placeholder="Enter Email Address...">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="password" class="form-control form-control-user"
|
||||||
|
id="exampleInputPassword" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-checkbox small">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="customCheck">
|
||||||
|
<label class="custom-control-label" for="customCheck">Remember
|
||||||
|
Me</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="index.html" class="btn btn-primary btn-user btn-block">
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
<hr>
|
||||||
|
<a href="index.html" class="btn btn-google btn-user btn-block">
|
||||||
|
<i class="fab fa-google fa-fw"></i> Login with Google
|
||||||
|
</a>
|
||||||
|
<a href="index.html" class="btn btn-facebook btn-user btn-block">
|
||||||
|
<i class="fab fa-facebook-f fa-fw"></i> Login with Facebook
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
<div class="text-center">
|
||||||
|
<a class="small" href="forgot-password.html">Forgot Password?</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<a class="small" href="register.html">Create an Account!</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user