Internal ID, Argon2 for usernames, username changes

This commit is contained in:
Miraty 2022-11-30 23:12:42 +01:00
parent 05db184fa6
commit f15681999b
31 changed files with 177 additions and 124 deletions

View file

@ -1,41 +1,43 @@
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "params" (
"name" TEXT NOT NULL UNIQUE,
"value" TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "registry" (
"id" INTEGER NOT NULL UNIQUE,
"domain" TEXT NOT NULL UNIQUE,
"username" TEXT NOT NULL,
"last_renewal" TEXT NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT)
);
CREATE TABLE IF NOT EXISTS "zones" (
"id" INTEGER NOT NULL UNIQUE,
"zone" TEXT NOT NULL UNIQUE,
"username" TEXT NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT)
"value" TEXT NOT NULL,
PRIMARY KEY("name")
);
INSERT INTO "params"("name", "value") VALUES("instance_bucket_tokens", "0");
INSERT INTO "params"("name", "value") VALUES("instance_bucket_last_update", "0");
INSERT INTO "params"("name", "value") VALUES("username_salt", "00000000000000000000000000000000"); -- Should be unique and secret ; generate one using `openssl rand -hex 16` ; can't be changed without breaking current accounts login
CREATE TABLE IF NOT EXISTS "users" (
"id" INTEGER NOT NULL UNIQUE,
"id" TEXT NOT NULL UNIQUE,
"username" TEXT NOT NULL UNIQUE,
"password" TEXT NOT NULL,
"registration_date" TEXT NOT NULL,
"bucket_tokens" INTEGER NOT NULL,
"bucket_last_update" INTEGER NOT NULL,
"type" TEXT NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT)
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "registry" (
"domain" TEXT NOT NULL UNIQUE,
"username" TEXT NOT NULL,
"last_renewal" TEXT NOT NULL,
PRIMARY KEY("domain"),
FOREIGN KEY("username") REFERENCES "users"("id")
);
CREATE TABLE IF NOT EXISTS "zones" (
"zone" TEXT NOT NULL UNIQUE,
"username" TEXT NOT NULL,
PRIMARY KEY("zone"),
FOREIGN KEY("username") REFERENCES "users"("id")
);
CREATE TABLE IF NOT EXISTS "sites" (
"id" INTEGER NOT NULL UNIQUE,
"username" TEXT NOT NULL,
"site_dir" TEXT NOT NULL,
"domain" TEXT NOT NULL UNIQUE,
"domain_type" TEXT NOT NULL,
"protocol" TEXT NOT NULL,
"creation_date" TEXT NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT)
PRIMARY KEY("domain"),
FOREIGN KEY("username") REFERENCES "users"("id")
);
INSERT INTO "params"("name", "value") VALUES("instance_bucket_tokens", "0");
INSERT INTO "params"("name", "value") VALUES("instance_bucket_last_update", "0");
COMMIT;

View file

@ -14,38 +14,42 @@ const OPTIONS_PASSWORD = [
'threads' => 64,
];
function checkUsernameFormat($username) {
if (preg_match('/' . USERNAME_REGEX . '/Du', $username) !== 1)
output(403, 'Username malformed.');
}
function checkPasswordFormat($password) {
if (preg_match('/' . PASSWORD_REGEX . '/Du', $password) !== 1)
output(403, 'Password malformed.');
}
function checkUsernameFormat($username) {
if (preg_match('/' . USERNAME_REGEX . '/Du', $username) !== 1)
output(403, 'Username malformed.');
function hashUsername($username) {
return base64_encode(sodium_crypto_pwhash(32, $username, hex2bin(query('select', 'params', ['name' => 'username_salt'], 'value')[0]), 2**10, 2**14, SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13));
}
function hashPassword($password) {
return password_hash($password, ALGO_PASSWORD, OPTIONS_PASSWORD);
}
function userExist($username) {
return isset(query('select', 'users', ['username' => $username], 'username')[0]);
function usernameExists($username) {
return isset(query('select', 'users', ['username' => $username], 'id')[0]);
}
function checkPassword($username, $password) {
return password_verify($password, query('select', 'users', ['username' => $username], 'password')[0]);
function checkPassword($id, $password) {
return password_verify($password, query('select', 'users', ['id' => $id], 'password')[0]);
}
function outdatedPasswordHash($username) {
return password_needs_rehash(query('select', 'users', ['username' => $username], 'password')[0], ALGO_PASSWORD, OPTIONS_PASSWORD);
function outdatedPasswordHash($id) {
return password_needs_rehash(query('select', 'users', ['id' => $id], 'password')[0], ALGO_PASSWORD, OPTIONS_PASSWORD);
}
function changePassword($username, $password) {
function changePassword($id, $password) {
$db = new PDO('sqlite:' . DB_PATH);
$stmt = $db->prepare('UPDATE users SET password = :password WHERE username = :username');
$stmt = $db->prepare('UPDATE users SET password = :password WHERE id = :id');
$stmt->bindValue(':username', $username);
$stmt->bindValue(':id', $id);
$stmt->bindValue(':password', hashPassword($password));
$stmt->execute();
@ -61,7 +65,7 @@ function rateLimit() {
function rateLimitAccount($requestedTokens) {
// Get
$userData = query('select', 'users', ['username' => $_SESSION['username']]);
$userData = query('select', 'users', ['id' => $_SESSION['id']]);
$tokens = $userData[0]['bucket_tokens'];
$bucketLastUpdate = $userData[0]['bucket_last_update'];
@ -75,8 +79,8 @@ function rateLimitAccount($requestedTokens) {
// Update
$db = new PDO('sqlite:' . DB_PATH);
$stmt = $db->prepare('UPDATE users SET bucket_tokens = :bucket_tokens, bucket_last_update = :bucket_last_update WHERE username = :username');
$stmt->bindValue(':username', $_SESSION['username']);
$stmt = $db->prepare('UPDATE users SET bucket_tokens = :bucket_tokens, bucket_last_update = :bucket_last_update WHERE id = :id');
$stmt->bindValue(':id', $_SESSION['id']);
$stmt->bindValue(':bucket_tokens', $tokens);
$stmt->bindValue(':bucket_last_update', time());
$stmt->execute();

View file

@ -20,11 +20,11 @@ function output($code, $msg = '', $logs = ['']) {
function processForm($requireLogin = true) {
if (http_response_code() !== 200)
return false;
if (empty($_POST) AND $requireLogin AND !isset($_SESSION['username']))
if (empty($_POST) AND $requireLogin AND !isset($_SESSION['id']))
echo '<p>Ce formulaire ne sera pas accepté car il faut <a class="auth" href="' . redirUrl('auth/login') . '">se connecter</a> avant.</p>';
if (empty($_POST))
return false;
if ($requireLogin AND !isset($_SESSION['username']))
if ($requireLogin AND !isset($_SESSION['id']))
output(403, 'Vous devez être connecté·e pour effectuer cette action.');
return true;
}
@ -152,11 +152,11 @@ if (!file_exists(SECRET_KEY_FILE)) {
define('SECRET_KEY', file_get_contents(SECRET_KEY_FILE));
function getAuthToken() {
$salt = bin2hex(random_bytes(4));
$hash = hash_hmac('sha256', $salt . ($_SESSION['username'] ?? ''), SECRET_KEY);
$hash = hash_hmac('sha256', $salt . ($_SESSION['id'] ?? ''), SECRET_KEY);
return $salt . '-' . substr($hash, 0, 32);
}
function checkAuthToken($salt, $hash) {
$correctProof = substr(hash_hmac('sha256', $salt . $_SESSION['username'], SECRET_KEY), 0, 32);
$correctProof = substr(hash_hmac('sha256', $salt . $_SESSION['id'], SECRET_KEY), 0, 32);
if (hash_equals($correctProof, $hash) !== true)
output(403, 'Preuve incorrecte');
}

View file

@ -33,15 +33,15 @@ function addSite($username, $siteDir, $domain, $domainType, $protocol) {
}
function dirsStatuses($domainType, $protocol) {
if (isset($_SESSION['username']) !== true)
if (isset($_SESSION['id']) !== true)
return [];
$dbDirs = query('select', 'sites', [
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
'domain_type' => $domainType,
'protocol' => $protocol,
], 'site_dir');
$dirs = [];
foreach (listFsDirs($_SESSION['username']) as $fsDir)
foreach (listFsDirs($_SESSION['id']) as $fsDir)
$dirs[$fsDir] = in_array($fsDir, $dbDirs);
return $dirs;
}
@ -50,7 +50,7 @@ function htDeleteSite($dir, $domainType, $protocol) {
if ($domainType === 'onion') {
// Delete Tor config
if (unlink(CONF['ht']['tor_config_path'] . '/' . $_SESSION['username'] . '/' . $dir) !== true)
if (unlink(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id'] . '/' . $dir) !== true)
output(500, 'Failed to delete Tor configuration.');
// Reload Tor
@ -59,14 +59,14 @@ function htDeleteSite($dir, $domainType, $protocol) {
output(500, 'Failed to reload Tor.');
// Delete Tor keys
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['username'] . '/' . $dir, $output, $code);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $dir, $output, $code);
if ($code !== 0)
output(500, 'Failed to delete Tor keys.');
}
// Delete Nginx config
$domain = query('select', 'sites', [
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
'domain_type' => $domainType,
'protocol' => $protocol,
'site_dir' => $dir,
@ -88,7 +88,7 @@ function htDeleteSite($dir, $domainType, $protocol) {
// Delete from database
query('delete', 'sites', [
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
'domain_type' => $domainType,
'protocol' => $protocol,
'site_dir' => $dir,

View file

@ -22,7 +22,7 @@ function nsCommonRequirements() {
AND isset($_POST['zone'])
AND isset($_POST['ttl-value'])
AND isset($_POST['ttl-multiplier'])
AND isset($_SESSION['username'])
AND isset($_SESSION['id'])
);
}
@ -51,7 +51,7 @@ function nsListUserZones($username) {
function nsCheckZonePossession($zone) {
checkAbsoluteDomainFormat($zone);
if (!in_array($zone, query('select', 'zones', ['username' => $_SESSION['username']], 'zone'), true))
if (!in_array($zone, query('select', 'zones', ['username' => $_SESSION['id']], 'zone'), true))
output(403, 'You don\'t own this zone on the nameserver.');
}
@ -69,6 +69,6 @@ function nsDeleteZone($zone) {
// Remove from database
query('delete', 'zones', [
'zone' => $zone,
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
]);
}

View file

@ -1,13 +1,13 @@
<?php
define('SUBDOMAIN_REGEX', '^[a-z0-9]{4,63}$');
const SUBDOMAIN_REGEX = '^[a-z0-9]{4,63}$';
function regListUserDomains($username) {
return query('select', 'registry', ['username' => $username], 'domain');
}
function regCheckDomainPossession($domain) {
if (in_array($domain, regListUserDomains($_SESSION['username']), true) !== true)
if (in_array($domain, regListUserDomains($_SESSION['id']), true) !== true)
output(403, 'You don\'t own this domain.');
}
@ -23,6 +23,6 @@ function regDeleteDomain($domain) {
// Delete from Niver's database
query('delete', 'registry', [
'domain' => $domain,
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
]);
}

View file

@ -18,8 +18,8 @@
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['username']))
foreach (nsListUserZones($_SESSION['username']) as $zone)
if (isset($_SESSION['id']))
foreach (nsListUserZones($_SESSION['id']) as $zone)
echo "<option value='" . $zone . "'>" . $zone . "</option>";
?>

View file

@ -26,6 +26,10 @@ define('PAGES', [
'title' => 'Changer la clé de passe',
'description' => 'Changer la chaîne de caractères permettant de vous authentifier.',
],
'username' => [
'title' => 'Changer l\'identifiant',
'description' => 'Changer la chaîne de caractères permettant d\'identifier votre compte.',
],
'logout' => [
'title' => 'Déconnexion',
'description' => 'Terminer la session et effacer ses cookies',

View file

@ -1,7 +1,7 @@
<?php displayIndex(); ?>
<p>
<?php if (isset($_SESSION['username'])) { ?>
Vous utilisez actuellement un compte <?= (($_SESSION['type'] === 'trusted') ? 'confiancé' : 'de test') ?>.
<?php if (isset($_SESSION['id'])) { ?>
Vous utilisez actuellement un compte <?= (($_SESSION['type'] === 'trusted') ? 'confiancé' : 'de test') ?>. Son identifiant interne est <code><?= $_SESSION['id'] ?></code>.
<?php } else { ?>
Vous n'utilisez actuellement aucun compte.
<?php } ?>
@ -10,7 +10,7 @@
<h2>Types de comptes</h2>
<dl>
<dt>De test</dt>
<dt><span aria-hidden="true"> </span>De test</dt>
<dd>
C'est le type de compte par défaut, avec des fonctionnalités limitées pour éviter les abus&nbsp;:
<ul>
@ -19,7 +19,7 @@
<li>Certificat Let's Encrypt de test</li>
</ul>
</dd>
<dt>Confiancé</dt>
<dt><span aria-hidden="true">👤 </span>Confiancé</dt>
<dd>
C'est originellement un compte de test mais qui a été confiancé par ane administrataire, et qui a pour but d'être utilisé de façon stable&nbsp;:
<ul>

View file

@ -5,20 +5,22 @@ if (processForm(false)) {
checkUsernameFormat($_POST['username']);
$internal_username = hash('sha256', $_POST['username']);
$username = hashUsername($_POST['username']);
if (userExist($internal_username) !== true)
if (usernameExists($username) !== true)
output(403, 'Connexion impossible : ce compte n\'existe pas.');
if (checkPassword($internal_username, $_POST['password']) !== true)
$id = query('select', 'users', ['username' => $username], 'id')[0];
if (checkPassword($id, $_POST['password']) !== true)
output(403, 'Connexion impossible : clé de passe invalide.');
$_SESSION['username'] = $internal_username;
$_SESSION['id'] = $id;
$_SESSION['display-username'] = htmlspecialchars($_POST['username']);
$_SESSION['type'] = query('select', 'users', ['username' => $internal_username], 'type')[0];
$_SESSION['type'] = query('select', 'users', ['id' => $id], 'type')[0];
if (outdatedPasswordHash($internal_username))
changePassword($internal_username, $_POST['password']);
if (outdatedPasswordHash($id))
changePassword($id, $_POST['password']);
redir();
}

View file

@ -1,12 +1,12 @@
<?php
if (processForm()) {
checkPasswordFormat($_POST['newPassword']);
checkPasswordFormat($_POST['new-password']);
if (checkPassword($_SESSION['username'], $_POST['currentPassword']) !== true)
if (checkPassword($_SESSION['id'], $_POST['current-password']) !== true)
output(403, 'Changement impossible : clé de passe invalide.');
changePassword($_SESSION['username'], $_POST['newPassword']);
changePassword($_SESSION['id'], $_POST['new-password']);
output(200, 'Clé de passe changée.');
}
@ -18,11 +18,11 @@ if (processForm()) {
</p>
<form method="post">
<label for="currentPassword">Clé de passe actuelle</label><br>
<input required="" autocomplete="current-password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" id="currentPassword" name="currentPassword" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>"><br>
<label for="current-password">Clé de passe actuelle</label><br>
<input required="" autocomplete="current-password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" id="current-password" name="current-password" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>"><br>
<label for="newPassword">Nouvelle clé de passe</label><br>
<input required="" autocomplete="new-password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" id="newPassword" name="newPassword" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>"><br>
<label for="new-password">Nouvelle clé de passe</label><br>
<input required="" autocomplete="new-password" minlength="8" maxlength="1024" pattern="<?= PASSWORD_REGEX ?>" id="new-password" name="new-password" type="password" placeholder="<?= PLACEHOLDER_PASSWORD ?>"><br>
<input type="submit">
</form>

View file

@ -5,15 +5,18 @@ if (processForm(false)) {
checkUsernameFormat($_POST['username']);
$internal_username = hash('sha256', $_POST['username']);
$username = hashUsername($_POST['username']);
if (userExist($internal_username) !== false)
if (usernameExists($username) !== false)
output(403, 'Ce nom de compte est déjà utilisé.');
rateLimit();
$id = hash('sha256', random_bytes(32));
insert('users', [
'username' => $internal_username,
'id' => $id,
'username' => $username,
'password' => hashPassword($_POST['password']),
'registration_date' => date('Y-m-d H:i:s'),
'bucket_tokens' => 0,
@ -23,22 +26,22 @@ if (processForm(false)) {
// Setup SFTP directory
umask(0002);
if (mkdir(CONF['ht']['ht_path'] . '/' . $internal_username, 0775) !== true)
if (mkdir(CONF['ht']['ht_path'] . '/' . $id, 0775) !== true)
output(500, 'Can\'t create user directory.');
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['chgrp_path'] . ' ' . CONF['ht']['sftpgo_group'] . ' ' . CONF['ht']['ht_path'] . '/' . $internal_username . ' --no-dereference', result_code: $code);
exec(CONF['ht']['sudo_path'] . ' ' . CONF['ht']['chgrp_path'] . ' ' . CONF['ht']['sftpgo_group'] . ' ' . CONF['ht']['ht_path'] . '/' . $id . ' --no-dereference', result_code: $code);
if ($code !== 0)
output(500, 'Can\'t change user directory group.');
// Setup Tor config directory
if (mkdir(CONF['ht']['tor_config_path'] . '/' . $internal_username, 0755) !== true)
if (mkdir(CONF['ht']['tor_config_path'] . '/' . $id, 0755) !== true)
output(500, 'Can\'t create Tor config directory.');
// Setup Tor keys directory
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['mkdir_path'] . ' --mode=0700 ' . CONF['ht']['tor_keys_path'] . '/' . $internal_username, result_code: $code);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['mkdir_path'] . ' --mode=0700 ' . CONF['ht']['tor_keys_path'] . '/' . $id, result_code: $code);
if ($code !== 0)
output(500, 'Can\'t create Tor keys directory.');
$_SESSION['username'] = $internal_username;
$_SESSION['id'] = $id;
$_SESSION['display-username'] = htmlspecialchars($_POST['username']);
$_SESSION['type'] = 'testing';

View file

@ -4,37 +4,37 @@ if (processForm()) {
if (!isset($_POST['delete']))
output(403, 'Il faut confirmer la suppression du compte');
foreach (query('select', 'registry', ['username' => $_SESSION['username']], 'domain') as $domain)
foreach (query('select', 'registry', ['username' => $_SESSION['id']], 'domain') as $domain)
regDeleteDomain($domain);
foreach (query('select', 'zones', ['username' => $_SESSION['username']], 'zone') as $zone)
foreach (query('select', 'zones', ['username' => $_SESSION['id']], 'zone') as $zone)
nsDeleteZone($zone);
foreach (query('select', 'sites', [
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
'domain_type' => 'onion',
'protocol' => 'http',
], 'site_dir') as $dir)
htDeleteSite($dir, domainType: 'onion', protocol: 'http');
foreach (query('select', 'sites', [
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
'domain_type' => 'dns',
'protocol' => 'http',
], 'site_dir') as $dir)
htDeleteSite($dir, domainType: 'dns', protocol: 'http');
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['username'], result_code: $code);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove Tor keys directory.');
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $_SESSION['username']);
removeDirectory(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id']);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['ht_path'] . '/' . $_SESSION['username'], result_code: $code);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['sftpgo_user'] . ' ' . CONF['ht']['rm_path'] . ' --recursive ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'], result_code: $code);
if ($code !== 0)
output(500, 'Can\'t remove user\'s directory.');
query('delete', 'users', ['username' => $_SESSION['username']]);
query('delete', 'users', ['id' => $_SESSION['id']]);
require 'logout.php';

36
pages/auth/username.php Normal file
View file

@ -0,0 +1,36 @@
<?php
if (processForm()) {
checkUsernameFormat($_POST['new-username']);
$username = hashUsername($_POST['new-username']);
if (usernameExists($username) !== false)
output(403, 'Ce nom de compte est déjà utilisé.');
$db = new PDO('sqlite:' . DB_PATH);
$stmt = $db->prepare('UPDATE users SET username = :username WHERE id = :id');
$stmt->bindValue(':id', $_SESSION['id']);
$stmt->bindValue(':username', $username);
$stmt->execute();
$_SESSION['display-username'] = htmlspecialchars($_POST['new-username']);
output(200, 'Identifiant changé.');
}
?>
<p>
Vous pouvez ici changer l'identifiant permettant d'accéder à votre compte Niver.
</p>
<form method="post">
<label for="new-username">Nouvel identifiant</label><br>
<input required="" autocomplete="new-username" minlength="1" maxlength="1024" pattern="<?= USERNAME_REGEX ?>" id="new-username" name="new-username" type="text" placeholder="<?= PLACEHOLDER_USERNAME ?>"><br>
<input type="submit">
</form>

View file

@ -31,7 +31,7 @@ if (processForm()) {
rateLimit();
addSite($_SESSION['username'], $_POST['dir'], $_POST['domain'], 'dns', 'http');
addSite($_SESSION['id'], $_POST['dir'], $_POST['domain'], 'dns', 'http');
exec('2>&1 ' . CONF['ht']['sudo_path'] . ' ' . CONF['ht']['certbot_path'] . ' certonly' . (($_SESSION['type'] === 'trusted') ? '' : ' --test-cert') . ' --key-type rsa --rsa-key-size 3072 --webroot --webroot-path /srv/niver/acme --domain ' . $_POST['domain'], $output, $returnCode);
if ($returnCode !== 0)
@ -41,7 +41,7 @@ if (processForm()) {
listen [' . CONF['ht']['ipv6_listen_address'] . ']:' . CONF['ht']['https_port'] . ' ssl http2;
listen ' . CONF['ht']['ipv4_listen_address'] . ':' . CONF['ht']['https_port'] . ' ssl http2;
server_name ' . $_POST['domain'] . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['username'] . '/' . $_POST['dir'] . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . ';
ssl_certificate /etc/letsencrypt/live/' . $_POST['domain'] . '/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/' . $_POST['domain'] . '/privkey.pem;

View file

@ -7,10 +7,10 @@ if (processForm()) {
rateLimit();
// Add Tor config
$torConf = 'HiddenServiceDir ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['username'] . '/' . $_POST['dir'] . '/
$torConf = 'HiddenServiceDir ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/
HiddenServicePort 80 [::1]:' . CONF['ht']['internal_onion_http_port'] . '
';
if (file_put_contents(CONF['ht']['tor_config_path'] . '/' . $_SESSION['username'] . '/' . $_POST['dir'], $torConf) === false)
if (file_put_contents(CONF['ht']['tor_config_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'], $torConf) === false)
output(500, 'Failed to write new Tor configuration.');
// Reload Tor
@ -19,19 +19,19 @@ if (processForm()) {
output(500, 'Failed to reload Tor.');
// Get the address generated by Tor
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['cat_path'] . ' ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['username'] . '/' . $_POST['dir'] . '/hostname', $output);
exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['cat_path'] . ' ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . '/hostname', $output);
$onion = $output[0];
if (preg_match('/^[0-9a-z]{56}\.onion$/D', $onion) !== 1)
output(500, 'No onion address found.');
// Store it in the database
addSite($_SESSION['username'], $_POST['dir'], $onion, 'onion', 'http');
addSite($_SESSION['id'], $_POST['dir'], $onion, 'onion', 'http');
// Add Nginx config
$nginxConf = 'server {
listen [::1]:' . CONF['ht']['internal_onion_http_port'] . ';
server_name ' . $onion . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['username'] . '/' . $_POST['dir'] . ';
root ' . CONF['ht']['ht_path'] . '/' . $_SESSION['id'] . '/' . $_POST['dir'] . ';
include inc/ht-onion.conf;
}

View file

@ -9,7 +9,7 @@
<dl>
<?php
$sites = query('select', 'sites', ['username' => $_SESSION['username'] ?? '']);
$sites = query('select', 'sites', ['username' => $_SESSION['id'] ?? '']);
if ($sites === [])
echo ' <p>Ce compte n\'héberge aucun site sur cette instance.<p>' . LF;
else {
@ -71,7 +71,7 @@ echo (($quotaSize >> 30) >= 1) ? $quotaSize >> 30 . ' ' . linkToDocs('units', '<
<section>
<h3>Se connecter au serveur</h3>
<a href="sftp://<?= isset($_SESSION['username']) ? $_SESSION['username'] : '&lt;username&gt;'; ?>@<?= CONF['ht']['sftp_domain'] ?>:<?= CONF['ht']['public_sftp_port'] ?>/">sftp://<?= isset($_SESSION['username']) ? $_SESSION['username'] : '&lt;username&gt;'; ?>@<?= CONF['ht']['sftp_domain'] ?>:<?= CONF['ht']['public_sftp_port'] ?>/</a>
<a href="sftp://<?= isset($_SESSION['display-username']) ? $_SESSION['display-username'] : '&lt;username&gt;'; ?>@<?= CONF['ht']['sftp_domain'] ?>:<?= CONF['ht']['public_sftp_port'] ?>/">sftp://<?= isset($_SESSION['display-username']) ? $_SESSION['display-username'] : '&lt;username&gt;'; ?>@<?= CONF['ht']['sftp_domain'] ?>:<?= CONF['ht']['public_sftp_port'] ?>/</a>
<dl>
<dt>Serveur</dt>
@ -88,7 +88,7 @@ echo (($quotaSize >> 30) >= 1) ? $quotaSize >> 30 . ' ' . linkToDocs('units', '<
</dd>
<dt>Utilisataire</dt>
<dd>
<code><?= isset($_SESSION['username']) ? $_SESSION['username'] : '&lt;username&gt;'; ?></code>
<code><?= isset($_SESSION['display-username']) ? $_SESSION['display-username'] : '&lt;username&gt;'; ?></code>
</dd>
<dt>Clé de passe</dt>
<dd>

View file

@ -70,8 +70,8 @@ if (processForm() AND isset($_POST['zone-content'])) { // Update zone
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['username']))
foreach (nsListUserZones($_SESSION['username']) as $zone)
if (isset($_SESSION['id']))
foreach (nsListUserZones($_SESSION['id']) as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>

View file

@ -19,7 +19,7 @@ foreach (CONF['ns']['servers'] as $server)
<?php
$zones = query('select', 'zones', ['username' => $_SESSION['username'] ?? ''], 'zone');
$zones = query('select', 'zones', ['username' => $_SESSION['id'] ?? ''], 'zone');
if ($zones === [])
echo '<p>Ce compte n\'héberge aucune zone sur cette instance.<p>' . LF;
else {

View file

@ -13,8 +13,8 @@
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['username']))
foreach (nsListUserZones($_SESSION['username']) as $zone)
if (isset($_SESSION['id']))
foreach (nsListUserZones($_SESSION['id']) as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>

View file

@ -22,7 +22,7 @@ if (processForm()) {
insert('zones', [
'zone' => $_POST['domain'],
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
]);
$knotZonePath = CONF['ns']['knot_zones_path'] . '/' . $_POST['domain'] . 'zone';

View file

@ -15,8 +15,8 @@ if (processForm()) {
<select required="" name="zone" id="zone">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['username']))
foreach (nsListUserZones($_SESSION['username']) as $zone)
if (isset($_SESSION['id']))
foreach (nsListUserZones($_SESSION['id']) as $zone)
echo ' <option value="' . $zone . '">' . $zone . '</option>' . LF;
?>
</select>

View file

@ -1,7 +1,7 @@
<?php
if (isset($_SESSION['username']))
$domains = regListUserDomains($_SESSION['username']);
if (isset($_SESSION['id']))
$domains = regListUserDomains($_SESSION['id']);
else
$domains = [];

View file

@ -42,8 +42,8 @@ if (processForm()) {
<select required="" name="suffix" id="suffix">
<option value="" disabled="" selected="">---</option>
<?php
if (isset($_SESSION['username']))
foreach(regListUserDomains($_SESSION['username']) as $suffix)
if (isset($_SESSION['id']))
foreach(regListUserDomains($_SESSION['id']) as $suffix)
echo ' <option value="' . $suffix . '">' . $suffix . '</option>' . LF;
?>
</select>

View file

@ -8,7 +8,7 @@
<?php
$domains = query('select', 'registry', ['username' => $_SESSION['username'] ?? ''], 'domain');
$domains = query('select', 'registry', ['username' => $_SESSION['id'] ?? ''], 'domain');
if ($domains === [])
echo '<p>Ce compte n\'a aucun domaine enregistré sur <code>' . CONF['reg']['registry'] . '</code><p>' . LF;
else {

View file

@ -32,8 +32,8 @@ if (processForm()) {
<select required="" name="domain" id="domain">
<option value="" disabled="" selected="">---</option>
<?php
if (isset($_SESSION['username']))
foreach (regListUserDomains($_SESSION['username']) as $domain)
if (isset($_SESSION['id']))
foreach (regListUserDomains($_SESSION['id']) as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
?>
</select>

View file

@ -3,8 +3,8 @@
<select required="" name="domain" id="domain">
<option value="" disabled="" selected="">-</option>
<?php
if (isset($_SESSION['username']))
foreach (regListUserDomains($_SESSION['username']) as $domain)
if (isset($_SESSION['id']))
foreach (regListUserDomains($_SESSION['id']) as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
?>
</select>

View file

@ -16,7 +16,7 @@ if (processForm()) {
insert('registry', [
'domain' => $domain,
'username' => $_SESSION['username'],
'username' => $_SESSION['id'],
'last_renewal' => date('Y-m-d H:i:s'),
]);

View file

@ -20,8 +20,8 @@ if (processForm()) {
<select required="" name="domain" id="domain">
<option value="" disabled="" selected="">---</option>
<?php
if (isset($_SESSION['username']))
foreach(regListUserDomains($_SESSION['username']) as $domain)
if (isset($_SESSION['id']))
foreach(regListUserDomains($_SESSION['id']) as $domain)
echo ' <option value="' . $domain . '">' . $domain . '</option>' . LF;
?>
</select>

View file

@ -91,7 +91,7 @@ foreach (glob('css/*.css') as $cssPath)
<body>
<header>
<p>
<?php if (isset($_SESSION['username'])) { ?>
<?php if (isset($_SESSION['id'])) { ?>
<?= ($_SESSION['type'] === 'trusted') ? '<span title="Compte confiancé">👤 </span>' : '<span title="Compte de test">⏳ </span>' ?><strong><?= $_SESSION['display-username'] ?></strong> <a class="auth" href="<?= CONF['common']['prefix'] ?>/auth/logout">Se déconnecter</a>
<?php } else { ?>
<span aria-hidden="true">👻 </span><em>Anonyme</em> <a class="auth" href="<?= redirUrl('auth/login') ?>">Se connecter</a>

View file

@ -4,15 +4,17 @@ require 'router.php';
$auth_data = json_decode(file_get_contents('php://input'), true);
$internal_username = hash('sha256', $auth_data['username']);
$username = hashUsername($auth_data['username']);
if (userExist($internal_username) === true AND checkPassword($internal_username, $auth_data['password']) === true) {
$id = query('select', 'users', ['username' => $username], 'id')[0];
if (usernameExists($username) === true AND checkPassword($id, $auth_data['password']) === true) {
echo '
{
"status": 1,
"username": ' . json_encode($auth_data['username']) . ',
"home_dir": "' . CONF['ht']['ht_path'] . '/' . $internal_username . '",
"quota_size": ' . ((query('select', 'users', ['username' => $internal_username], 'type')[0] === 'trusted') ? CONF['ht']['user_quota_trusted'] : CONF['ht']['user_quota_testing']) . ',
"home_dir": "' . CONF['ht']['ht_path'] . '/' . $id . '",
"quota_size": ' . ((query('select', 'users', ['id' => $id], 'type')[0] === 'trusted') ? CONF['ht']['user_quota_trusted'] : CONF['ht']['user_quota_testing']) . ',
"permissions": {
"/": [
"*"