hanyuu/src/Auth/AuthRoutes.php

270 lines
8.5 KiB
PHP

<?php
namespace Hanyuu\Auth;
use RuntimeException;
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler};
use Syokuhou\IConfig;
use Hanyuu\HanyuuContext;
use Hanyuu\Auth\{Auth,AuthLoginInfo};
use Hanyuu\Users\Users;
// VERY IMPORTANT TODO: CSRF AND RATE LIMITING
class AuthRoutes extends RouteHandler {
private HanyuuContext $context;
private Users $users;
private IConfig $config;
private Auth $auth;
private ?AuthLoginInfo $loginSession;
public function __construct(
HanyuuContext $ctx,
IConfig $config
) {
$this->context = $ctx;
$this->config = $config;
$this->users = $ctx->getUsers();
$this->context->setUpAuth();
$this->auth = $ctx->getAuth();
}
private function getLoginCookieName(): string {
return $this->config->getString('login_cookie', '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;
}
#[HttpMiddleware('/login')]
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);
}
}
#[HttpGet('/login')]
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', [
'user_name' => $userName,
'error_name' => $errorId,
'error_text' => $errorText,
]);
}
#[HttpPost('/login')]
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(RuntimeException $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');
}
#[HttpGet('/login/tfa')]
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', [
'user_info' => $userInfo,
'auth_methods' => $authMethods,
'auth_method_names' => $authMethodNames,
'login_session' => $this->loginSession,
]);
}
#[HttpGet('/login/done')]
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";
}
#[HttpGet('/login/tfa/totp')]
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', [
'user_info' => $userInfo,
'login_session' => $this->loginSession,
]);
}
#[HttpPost('/login/tfa/totp')]
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');
}
#[HttpGet('/login/tfa/u2f')]
public function getLoginU2F($response, $request) {
return 503;
}
#[HttpPost('/login/tfa/u2f')]
public function postLoginU2F($response, $request) {
return 503;
}
#[HttpGet('/login/tfa/backup')]
public function getLoginBackup($response, $request) {
return 503;
}
#[HttpPost('/login/tfa/backup')]
public function postLoginBackup($response, $request) {
return 503;
}
#[HttpGet('/register')]
public function getRegister($response, $request) {
return 503;
}
#[HttpGet('/forgot-username')]
public function getForgotUserName($response, $request) {
return 503;
}
#[HttpGet('/forgot-password')]
public function getForgotPassword($response, $request) {
return 503;
}
#[HttpGet('/recover-password')]
public function getRecoverPassword($response, $request) {
return 503;
}
}