282 lines
9 KiB
PHP
282 lines
9 KiB
PHP
<?php
|
|
namespace Hanyuu\Auth;
|
|
|
|
use Index\Routing\IRouter;
|
|
use Hanyuu\HanyuuContext;
|
|
use Hanyuu\Auth\Auth;
|
|
use Hanyuu\Auth\IAuthLogin;
|
|
use Hanyuu\Config\IConfig;
|
|
use Hanyuu\Users\IUserInfo;
|
|
use Hanyuu\Users\IUsers;
|
|
use Hanyuu\Users\UserNotFoundException;
|
|
|
|
// VERY IMPORTANT TODO: CSRF AND RATE LIMITING
|
|
|
|
class AuthRoutes {
|
|
private HanyuuContext $context;
|
|
private IUsers $users;
|
|
private IConfig $config;
|
|
private Auth $auth;
|
|
|
|
private ?IAuthLogin $loginSession;
|
|
|
|
public function __construct(
|
|
IRouter $router,
|
|
HanyuuContext $ctx,
|
|
IConfig $config
|
|
) {
|
|
$this->context = $ctx;
|
|
$this->config = $config;
|
|
$this->users = $ctx->getUsers();
|
|
|
|
$this->context->setUpAuth();
|
|
$this->auth = $ctx->getAuth();
|
|
|
|
$router->use('/login', [$this, 'filterLogin']);
|
|
|
|
$router->get('/login', [$this, 'getLogin']);
|
|
$router->post('/login', [$this, 'postLogin']);
|
|
|
|
$router->get('/login/tfa', [$this, 'getLoginTFA']);
|
|
$router->get('/login/done', [$this, 'getLoginDone']);
|
|
|
|
$router->get('/login/tfa/totp', [$this, 'getLoginTOTP']);
|
|
$router->post('/login/tfa/totp', [$this, 'postLoginTOTP']);
|
|
|
|
$router->get('/login/tfa/u2f', [$this, 'getLoginU2F']);
|
|
$router->post('/login/tfa/u2f', [$this, 'postLoginU2F']);
|
|
|
|
$router->get('/login/tfa/backup', [$this, 'getLoginBackup']);
|
|
$router->post('/login/tfa/backup', [$this, 'postLoginBackup']);
|
|
|
|
$router->get('/register', [$this, 'getRegister']);
|
|
|
|
$router->get('/forgot-username', [$this, 'getForgotUserName']);
|
|
|
|
$router->get('/forgot-password', [$this, 'getForgotPassword']);
|
|
$router->get('/recover-password', [$this, 'getRecoverPassword']);
|
|
}
|
|
|
|
private function getLoginCookieName(): string {
|
|
return $this->config->getValue('login_cookie', IConfig::T_STR, 'hau_login');
|
|
}
|
|
|
|
private function destroyLoginSession($response): void {
|
|
if($this->loginSession !== null) {
|
|
$response->removeCookie($this->getLoginCookieName());
|
|
$this->auth->destroyLoginSession($this->loginSession);
|
|
$this->loginSession = null;
|
|
}
|
|
}
|
|
|
|
private function ensureLoginIncomplete($response): bool {
|
|
$loginSession = $this->loginSession;
|
|
if($loginSession === null) {
|
|
$response->redirect('/login');
|
|
return false;
|
|
}
|
|
|
|
if($loginSession->getFactorsDone() >= $loginSession->getFactorsRequired()) {
|
|
$response->redirect('/login/done');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function filterLogin($response, $request) {
|
|
$loginId = (string)$request->getCookie($this->getLoginCookieName());
|
|
$this->loginSession = empty($loginId) ? null : $this->auth->getLoginSessionById($loginId);
|
|
|
|
if($this->loginSession !== null) {
|
|
if(!$this->loginSession->isValid()
|
|
|| $this->loginSession->getRemoteAddress()->getCleanAddress() !== $_SERVER['REMOTE_ADDR'])
|
|
$this->destroyLoginSession($response);
|
|
}
|
|
}
|
|
|
|
public function getLogin($response, $request) {
|
|
$this->destroyLoginSession($response);
|
|
|
|
$userName = (string)$request->getParam('username');
|
|
$errorId = (string)$request->getParam('error');
|
|
$errorText = [
|
|
'invalid_username' => 'Either no username was provided or it was incorrectly formatted.',
|
|
'wrong_password' => 'The provided password was incorrect.',
|
|
'user_not_found' => 'No user with this name exists. NOTE: point to registration somehow',
|
|
'user_deleted' => 'Your account has been marked for deletion. Contact staff to revert this.',
|
|
][$errorId] ?? '';
|
|
|
|
return $this->context->renderTemplate('auth/login', [
|
|
'userName' => $userName,
|
|
'errorId' => $errorId,
|
|
'errorText' => $errorText,
|
|
]);
|
|
}
|
|
|
|
public function postLogin($response, $request) {
|
|
if(!$request->isFormContent())
|
|
return 400;
|
|
$form = $request->getContent();
|
|
|
|
// should be adjusted to respond with json to ajax shit
|
|
|
|
$this->destroyLoginSession($response);
|
|
|
|
$userName = (string)$form->getParam('username');
|
|
$password = (string)$form->getParam('password');
|
|
|
|
if(empty($userName)) {
|
|
$response->redirect('/login?error=invalid_username');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$userInfo = $this->users->getUserInfoByName($userName);
|
|
} catch(UserNotFoundException $ex) {
|
|
$response->redirect('/login?error=user_not_found&username=' . rawurlencode($userName));
|
|
return;
|
|
}
|
|
|
|
if($userInfo->isDeleted()) {
|
|
$response->redirect('/login?error=user_deleted');
|
|
return;
|
|
}
|
|
|
|
$passwordInfo = $this->users->getUserPasswordInfo($userInfo);
|
|
if(!$passwordInfo->verifyPassword($password)) {
|
|
$response->redirect('/login?error=wrong_password');
|
|
return;
|
|
}
|
|
|
|
$factorsRequired = $this->users->countUserTFAMethods($userInfo) > 0 ? 2 : 1;
|
|
$loginId = $this->auth->createLoginSession($userInfo, $_SERVER['REMOTE_ADDR'], 'A1', $factorsRequired);
|
|
$this->loginSession = $this->auth->getLoginSessionById($loginId);
|
|
|
|
$this->auth->incrementLoginSessionDone($this->loginSession);
|
|
|
|
// check for rehash
|
|
|
|
$response->addCookie($this->getLoginCookieName(), $loginId, strtotime('+30 minutes'), '/', '', true, true, true);
|
|
|
|
$response->redirect($factorsRequired > 1 ? '/login/tfa' : '/login/done');
|
|
}
|
|
|
|
public function getLoginTFA($response, $request) {
|
|
if(!$this->ensureLoginIncomplete($response))
|
|
return;
|
|
|
|
$errorId = (string)$request->getParam('error');
|
|
$errorText = [
|
|
'invalid_method' => 'Either no method was provided or it was incorrectly formatted.',
|
|
][$errorId] ?? '';
|
|
|
|
$userInfo = $this->users->getUserInfoById($this->loginSession->getUserId());
|
|
$authMethods = $this->users->getUserTFAMethods($userInfo);
|
|
$authMethodNames = [];
|
|
foreach($authMethods as $authMethod)
|
|
$authMethodNames[] = $authMethod->getType();
|
|
|
|
if(count($authMethods) === 1) {
|
|
$response->redirect("/login/tfa/{$authMethods[0]->getType()}");
|
|
return;
|
|
}
|
|
|
|
return $this->context->renderTemplate('auth/login-tfa', [
|
|
'userInfo' => $userInfo,
|
|
'authMethods' => $authMethods,
|
|
'authMethodNames' => $authMethodNames,
|
|
'loginSession' => $this->loginSession,
|
|
]);
|
|
}
|
|
|
|
public function getLoginDone($response, $request) {
|
|
if($this->loginSession === null) {
|
|
$response->redirect('/login');
|
|
return;
|
|
}
|
|
|
|
if($this->loginSession->getFactorsDone() < $this->loginSession->getFactorsRequired()) {
|
|
$response->redirect('/login/pick');
|
|
return;
|
|
}
|
|
|
|
$this->destroyLoginSession($response);
|
|
|
|
return "you've successfully authenticated but there's no session system yet lol";
|
|
}
|
|
|
|
public function getLoginTOTP($response, $request) {
|
|
if(!$this->ensureLoginIncomplete($response))
|
|
return;
|
|
|
|
$userInfo = $this->users->getUserInfoById($this->loginSession->getUserId());
|
|
|
|
$errorId = (string)$request->getParam('error');
|
|
$errorText = [
|
|
'invalid_method' => 'Either no method was provided or it was incorrectly formatted.',
|
|
][$errorId] ?? '';
|
|
|
|
return $this->context->renderTemplate('auth/login-totp', [
|
|
'userInfo' => $userInfo,
|
|
'loginSession' => $this->loginSession,
|
|
]);
|
|
}
|
|
|
|
public function postLoginTOTP($response, $request) {
|
|
if(!$request->isFormContent())
|
|
return 400;
|
|
$form = $request->getContent();
|
|
|
|
if(!$this->ensureLoginIncomplete($response))
|
|
return;
|
|
|
|
$userInfo = $this->users->getUserInfoById($this->loginSession->getUserId());
|
|
|
|
$totpInfo = $this->users->getUserTOTPInfo($userInfo);
|
|
$totpValid = $totpInfo->generateValidCodes();
|
|
$totpCode = (string)$form->getParam('code');
|
|
|
|
if(!in_array($totpCode, $totpValid, true)) {
|
|
$response->redirect('/login/tfa/totp?error=wrong_totp');
|
|
return;
|
|
}
|
|
|
|
$this->auth->incrementLoginSessionDone($this->loginSession);
|
|
|
|
$response->redirect('/login/done');
|
|
}
|
|
|
|
public function getLoginU2F($response, $request) {
|
|
return 503;
|
|
}
|
|
|
|
public function postLoginU2F($response, $request) {
|
|
return 503;
|
|
}
|
|
|
|
public function getLoginBackup($response, $request) {
|
|
return 503;
|
|
}
|
|
|
|
public function postLoginBackup($response, $request) {
|
|
return 503;
|
|
}
|
|
|
|
public function getRegister($response, $request) {
|
|
return 503;
|
|
}
|
|
|
|
public function getForgotUserName($response, $request) {
|
|
return 503;
|
|
}
|
|
|
|
public function getForgotPassword($response, $request) {
|
|
return 503;
|
|
}
|
|
|
|
public function getRecoverPassword($response, $request) {
|
|
return 503;
|
|
}
|
|
}
|