KaiRo bug 392 - Create an interstitial page to confirm the user to log in
[authserver.git] / authutils.php-class
index bcf1b38dbb0a4e92929b6d7d5935977c0a6e06a2..e72768a2bd35c883384d47fec68ff7375a8f5544 100755 (executable)
@@ -18,6 +18,9 @@ class AuthUtils {
   // public $running_on_localhost
   //   A boolean telling if the system is running on localhost (where https is not required).
   //
+  // public $client_reg_email_whitelist
+  //   An array of emails that are whitelisted for registering clients.
+  //
   // private $pwd_cost
   //   The cost parameter for use with PHP password_hash function.
   //
@@ -34,6 +37,9 @@ class AuthUtils {
   // function initSession()
   //   Initialize a session. Returns an associative array of all the DB fields of the session.
   //
+  // function getDomainBaseURL()
+  //   Get the base URL of the current domain, e.g. 'https://example.com'.
+  //
   // function checkPasswordConstraints($new_password, $user_email)
   //   Check password constraints and return an array of error messages (empty if all constraints are met).
   //
@@ -43,6 +49,9 @@ class AuthUtils {
   // function createVerificationCode()
   //   Return a random acount/email verification code.
   //
+  // function createClientSecret()
+  //   Return a random client secret.
+  //
   // function createTimeCode($session, [$offset], [$validity_minutes])
   //   Return a time-based code based on the key and ID of the given session.
   //     An offset can be given to create a specific code for verification, otherwise and offset will be generated.
@@ -60,11 +69,15 @@ class AuthUtils {
   //
   // function pwdNeedsRehash($user)
   //   Return true if the pwdhash field of the user uses an outdated standard and needs to be rehashed.
+  //
+  // function appendLoginForm($dom_element, $session, $user)
+  //   append a login form for the given session to the given DOM element, possibly prefilling the email from the given user info array.
 
   function __construct($settings, $db) {
     // *** constructor ***
     $this->db = $db;
     $this->db->exec("SET time_zone='+00:00';"); // Execute directly on PDO object, set session to UTC to make our gmdate() values match correctly.
+    // For debugging, potentially add |robert\.box\.kairo\.at to that regex temporarily.
     $this->running_on_localhost = preg_match('/^((.+\.)?localhost|127\.0\.0\.\d+)$/', $_SERVER['SERVER_NAME']);
     if (array_key_exists('pwd_cost', $settings)) {
       $this->pwd_cost = $settings['pwd_cost'];
@@ -76,6 +89,7 @@ class AuthUtils {
 
   public $db = null;
   public $running_on_localhost = false;
+  public $client_reg_email_whitelist = array('kairo@kairo.at', 'com@kairo.at');
   private $pwd_cost = 10;
   private $pwd_nonces = array();
 
@@ -94,6 +108,26 @@ class AuthUtils {
     return $errors;
   }
 
+  function sendSecurityHeaders() {
+    // Send various headers that we want to have for security resons, mostly as recommended by https://observatory.mozilla.org/
+
+    // CSP - see https://wiki.mozilla.org/Security/Guidelines/Web_Security#Content_Security_Policy
+    // Disable unsafe inline/eval, only allow loading of resources (images, fonts, scripts, etc.) from ourselves; also disable framing.
+    header('Content-Security-Policy: default-src \'none\';img-src \'self\'; script-src \'self\'; style-src \'self\'; frame-ancestors \'none\'');
+
+    // X-Content-Type-Options - see https://wiki.mozilla.org/Security/Guidelines/Web_Security#X-Content-Type-Options
+    // Prevent browsers from incorrectly detecting non-scripts as scripts
+    header('X-Content-Type-Options: nosniff');
+
+    // X-Frame-Options (for older browsers) - see https://wiki.mozilla.org/Security/Guidelines/Web_Security#X-Frame-Options
+    // Block site from being framed
+    header('X-Frame-Options: DENY');
+
+    // X-XSS-Protection (for older browsers) - see https://wiki.mozilla.org/Security/Guidelines/Web_Security#X-XSS-Protection
+    // Block pages from loading when they detect reflected XSS attacks
+    header('X-XSS-Protection: 1; mode=block');
+  }
+
   function initSession() {
     $session = null;
     if (strlen(@$_COOKIE['sessionkey'])) {
@@ -125,6 +159,10 @@ class AuthUtils {
     return $session;
   }
 
+  function getDomainBaseURL() {
+    return ($this->running_on_localhost?'http':'https').'://'.$_SERVER['SERVER_NAME'];
+  }
+
   function checkPasswordConstraints($new_password, $user_email) {
     $errors = array();
     if ($new_password != trim($new_password)) {
@@ -156,6 +194,10 @@ class AuthUtils {
     return bin2hex(openssl_random_pseudo_bytes(512 / 8)); // Get 512 bits of randomness (128 byte hex string).
   }
 
+  function createClientSecret() {
+    return bin2hex(openssl_random_pseudo_bytes(160 / 8)); // Get 160 bits of randomness (40 byte hex string).
+  }
+
   function createTimeCode($session, $offset = null, $validity_minutes = 10) {
     // Matches TOTP algorithms, see https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm
     $valid_seconds = intval($validity_minutes) * 60;
@@ -210,5 +252,36 @@ class AuthUtils {
       return true;
     }
   }
+
+  function appendLoginForm($dom_element, $session, $user) {
+    $form = $dom_element->appendForm('./', 'POST', 'loginform');
+    $form->setAttribute('id', 'loginform');
+    $form->setAttribute('class', 'loginarea hidden');
+    $ulist = $form->appendElement('ul');
+    $ulist->setAttribute('class', 'flat login');
+    $litem = $ulist->appendElement('li');
+    $inptxt = $litem->appendInputEmail('email', 30, 20, 'login_email', (intval(@$user['id'])?$user['email']:''));
+    $inptxt->setAttribute('autocomplete', 'email');
+    $inptxt->setAttribute('required', '');
+    $inptxt->setAttribute('placeholder', _('Email'));
+    $inptxt->setAttribute('class', 'login');
+    $litem = $ulist->appendElement('li');
+    $inptxt = $litem->appendInputPassword('pwd', 20, 20, 'login_pwd', '');
+    $inptxt->setAttribute('required', '');
+    $inptxt->setAttribute('placeholder', _('Password'));
+    $inptxt->setAttribute('class', 'login');
+    $litem = $ulist->appendElement('li');
+    $litem->appendLink('./?reset', _('Forgot password?'));
+    $litem = $ulist->appendElement('li');
+    $cbox = $litem->appendInputCheckbox('remember', 'login_remember', 'true', false);
+    $cbox->setAttribute('class', 'logincheck');
+    $label = $litem->appendLabel('login_remember', _('Remember me'));
+    $label->setAttribute('id', 'rememprompt');
+    $label->setAttribute('class', 'loginprompt');
+    $litem = $ulist->appendElement('li');
+    $litem->appendInputHidden('tcode', $this->createTimeCode($session));
+    $submit = $litem->appendInputSubmit(_('Log in / Register'));
+    $submit->setAttribute('class', 'loginbutton');
+  }
 }
 ?>