mirror of
https://github.com/getnamingo/registry.git
synced 2025-05-13 16:16:59 +02:00
280 lines
No EOL
11 KiB
PHP
280 lines
No EOL
11 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers\Auth;
|
|
|
|
use App\Auth\Auth;
|
|
use App\Controllers\Controller;
|
|
use Respect\Validation\Validator as v;
|
|
use Psr\Http\Message\ResponseInterface as Response;
|
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
|
use Pinga\Session;
|
|
|
|
/**
|
|
* AuthController
|
|
*
|
|
* @author Hezekiah O. <support@hezecom.com>
|
|
*/
|
|
class AuthController extends Controller
|
|
{
|
|
private $webAuthn;
|
|
|
|
public function __construct() {
|
|
$rpName = 'Namingo';
|
|
$rpId = envi('APP_DOMAIN');
|
|
$this->webAuthn = new \lbuchs\WebAuthn\WebAuthn($rpName, $rpId, ['android-key', 'android-safetynet', 'apple', 'fido-u2f', 'packed', 'tpm']);
|
|
}
|
|
|
|
/**
|
|
* @param Request $request
|
|
* @param Response $response
|
|
* @return mixed
|
|
* @throws \DI\DependencyException
|
|
* @throws \DI\NotFoundException
|
|
*/
|
|
public function createLogin(Request $request, Response $response){
|
|
$isWebAuthnEnabled = (envi('WEB_AUTHN_ENABLED') === 'true') ? true : false;
|
|
return view($response, 'auth/login.twig', ['isWebaEnabled' => $isWebAuthnEnabled]);
|
|
}
|
|
|
|
/**
|
|
* Show 2FA verification form.
|
|
*
|
|
* @param Request $request
|
|
* @param Response $response
|
|
* @return mixed
|
|
*/
|
|
public function verify2FA(Request $request, Response $response){
|
|
if (isset($_SESSION['is2FAEnabled']) && $_SESSION['is2FAEnabled'] === true) {
|
|
return view($response, 'auth/verify2fa.twig');
|
|
} else {
|
|
return $response->withHeader('Location', '/login')->withStatus(302);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param Request $request
|
|
* @param Response $response
|
|
* @throws \Pinga\Auth\AttemptCancelledException
|
|
* @throws \Pinga\Auth\AuthError
|
|
*/
|
|
public function login(Request $request, Response $response){
|
|
global $container;
|
|
$data = $request->getParsedBody();
|
|
$db = $container->get('db');
|
|
$is2FAEnabled = $db->selectValue('SELECT tfa_enabled FROM users WHERE email = ?', [$data['email']]);
|
|
$isWebaEnabled = $db->selectValue('SELECT auth_method FROM users WHERE email = ?', [$data['email']]);
|
|
|
|
if ($isWebaEnabled == 'webauthn') {
|
|
$container->get('flash')->addMessage('error', 'WebAuthn enabled for this account');
|
|
return $response->withHeader('Location', '/login')->withStatus(302);
|
|
}
|
|
|
|
// If 2FA is enabled and no code is provided, redirect to 2FA code entry
|
|
if($is2FAEnabled && !isset($data['code'])) {
|
|
$_SESSION['2fa_email'] = $data['email'];
|
|
$_SESSION['2fa_password'] = $data['password'];
|
|
$_SESSION['is2FAEnabled'] = true;
|
|
return $response->withHeader('Location', '/login/verify')->withStatus(302);
|
|
} else {
|
|
$email = $data['email'];
|
|
$password = $data['password'];
|
|
$_SESSION['is2FAEnabled'] = false;
|
|
}
|
|
|
|
// If the 2FA code is present, this might be a 2FA verification attempt
|
|
if (isset($data['code']) && isset($_SESSION['2fa_email']) && isset($_SESSION['2fa_password'])) {
|
|
$email = $_SESSION['2fa_email'];
|
|
$password = $_SESSION['2fa_password'];
|
|
// Clear the session variables immediately after use
|
|
unset($_SESSION['2fa_email'], $_SESSION['2fa_password'], $_SESSION['is2FAEnabled']);
|
|
}
|
|
|
|
$login = Auth::login($email, $password, $data['remember'] ?? null, $data['code'] ?? null);
|
|
unset($_SESSION['2fa_email'], $_SESSION['2fa_password'], $_SESSION['is2FAEnabled']);
|
|
|
|
if ($login===true) {
|
|
// Check if password renewal is needed
|
|
$passwordLastChanged = $_SESSION['password_last_changed'][$_SESSION['auth_user_id']] ?? 0;
|
|
if (checkPasswordRenewal($passwordLastChanged)) {
|
|
Auth::logout();
|
|
redirect()->route('forgot.password')->with('error','Your password is expired. Please change it');
|
|
}
|
|
|
|
$db = $container->get('db');
|
|
$currentDateTime = new \DateTime();
|
|
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
|
|
$db->insert(
|
|
'users_audit',
|
|
[
|
|
'user_id' => $_SESSION['auth_user_id'],
|
|
'user_event' => 'user.login',
|
|
'user_resource' => 'control.panel',
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
|
'user_ip' => get_client_ip(),
|
|
'user_location' => get_client_location(),
|
|
'event_time' => $currentDate,
|
|
'user_data' => null
|
|
]
|
|
);
|
|
redirect()->route('home');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \Pinga\Auth\AuthError
|
|
*/
|
|
public function logout()
|
|
{
|
|
global $container;
|
|
$db = $container->get('db');
|
|
$currentDateTime = new \DateTime();
|
|
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
|
|
$db->insert(
|
|
'users_audit',
|
|
[
|
|
'user_id' => $_SESSION['auth_user_id'],
|
|
'user_event' => 'user.logout',
|
|
'user_resource' => 'control.panel',
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
|
'user_ip' => get_client_ip(),
|
|
'user_location' => get_client_location(),
|
|
'event_time' => $currentDate,
|
|
'user_data' => null
|
|
]
|
|
);
|
|
Auth::logout();
|
|
redirect()->route('login');
|
|
}
|
|
|
|
public function getLoginChallenge(Request $request, Response $response)
|
|
{
|
|
global $container;
|
|
|
|
$ids = [];
|
|
$rawData = $request->getBody();
|
|
$data = json_decode($rawData, true);
|
|
|
|
try {
|
|
$db = $container->get('db');
|
|
$userId = $db->selectValue('SELECT id FROM users WHERE email = ?', [$data['email']]);
|
|
|
|
if ($userId) {
|
|
// User found, get the user ID
|
|
$registrations = $db->select('SELECT id, credential_id FROM users_webauthn WHERE user_id = ?', [$userId]);
|
|
|
|
if ($registrations) {
|
|
foreach ($registrations as $reg) {
|
|
$ids[] = base64_decode($reg['credential_id']);
|
|
}
|
|
}
|
|
|
|
if (count($ids) === 0) {
|
|
$response->getBody()->write(json_encode(['error' => 'no registrations in session for userId ' . $userId]));
|
|
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
|
}
|
|
} else {
|
|
$response->getBody()->write(json_encode(['error' => 'No user found with the provided email.']));
|
|
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
|
}
|
|
} catch (PDOException $e) {
|
|
$response->getBody()->write(json_encode(['error' => $e->getMessage()]));
|
|
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
|
}
|
|
|
|
$getArgs = $this->webAuthn->getGetArgs($ids[0], 60*4, true, true, true, true, true, 'discouraged');
|
|
|
|
$response->getBody()->write(json_encode($getArgs));
|
|
$_SESSION['challenge'] = ($this->webAuthn->getChallenge())->getBinaryString();
|
|
|
|
return $response->withHeader('Content-Type', 'application/json');
|
|
}
|
|
|
|
public function verifyLogin(Request $request, Response $response)
|
|
{
|
|
global $container;
|
|
|
|
$challenge = $_SESSION['challenge'];
|
|
$credentialPublicKey = null;
|
|
|
|
$data = json_decode($request->getBody()->getContents(), null, 512, JSON_THROW_ON_ERROR);
|
|
|
|
try {
|
|
// Decode the incoming data
|
|
$clientDataJSON = base64_decode($data->clientDataJSON);
|
|
$authenticatorData = base64_decode($data->authenticatorData);
|
|
$signature = base64_decode($data->signature);
|
|
$userHandle = base64_decode($data->userHandle);
|
|
$id = $data->id;
|
|
|
|
$db = $container->get('db');
|
|
$credentials = $db->select('SELECT * FROM users_webauthn WHERE credential_id = ?', [$id]);
|
|
|
|
if ($credentials) {
|
|
foreach ($credentials as $reg) {
|
|
if ($reg['credential_id'] === $id) {
|
|
$credentialPublicKey = $reg['public_key'];
|
|
$user_id = $reg['user_id'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($credentialPublicKey === null) {
|
|
throw new \Exception('Public Key for credential ID not found!');
|
|
}
|
|
|
|
// process the get request. throws WebAuthnException if it fails
|
|
$this->webAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, 'discouraged');
|
|
|
|
$return = array();
|
|
$return['success'] = true;
|
|
$return['msg'] = "Authentication successful.";
|
|
|
|
if($return['success']===true) {
|
|
// Send success response
|
|
$user = $db->selectRow('SELECT * FROM users WHERE id = ?', [$user_id]);
|
|
|
|
session_regenerate_id();
|
|
$_SESSION['auth_logged_in'] = true;
|
|
$_SESSION['auth_user_id'] = $user['id'];
|
|
$_SESSION['auth_email'] = $user['email'];
|
|
$_SESSION['auth_username'] = $user['username'];
|
|
$_SESSION['auth_status'] = $user['status'];
|
|
$_SESSION['auth_roles'] = $user['roles_mask'];
|
|
$_SESSION['auth_force_logout'] = $user['force_logout'];
|
|
$_SESSION['auth_remembered'] = 0;
|
|
$_SESSION['auth_last_resync'] = \time();
|
|
|
|
$db = $container->get('db');
|
|
$currentDateTime = new \DateTime();
|
|
$currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp
|
|
$db->insert(
|
|
'users_audit',
|
|
[
|
|
'user_id' => $_SESSION['auth_user_id'],
|
|
'user_event' => 'user.login.webauthn',
|
|
'user_resource' => 'control.panel',
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
|
'user_ip' => get_client_ip(),
|
|
'user_location' => get_client_location(),
|
|
'event_time' => $currentDate,
|
|
'user_data' => null
|
|
]
|
|
);
|
|
$response->getBody()->write(json_encode($return));
|
|
return $response->withHeader('Content-Type', 'application/json');
|
|
} else {
|
|
$response->getBody()->write(json_encode($return));
|
|
return $response->withHeader('Content-Type', 'application/json');
|
|
}
|
|
} catch (\Exception $e) {
|
|
$response->getBody()->write(json_encode(['msg' => $e->getMessage()]));
|
|
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
|
} catch (WebAuthnException $e) {
|
|
$return = array();
|
|
$return['success'] = false;
|
|
$response->getBody()->write(json_encode(['msg' => "Authentication failed: " . $e->getMessage()]));
|
|
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
|
}
|
|
}
|
|
} |