hanyuu/src/Auth/AuthRoutes.php
2023-10-18 10:34:30 +00:00

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;
}
}