123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- <?php declare(strict_types=1);
- const USERNAME_REGEX = '^.{1,1024}$';
- const PASSWORD_REGEX = '^(?=.*[\p{Ll}])(?=.*[\p{Lu}])(?=.*[\p{N}]).{8,1024}|.{10,1024}$';
- const PLACEHOLDER_USERNAME = 'lain';
- const PLACEHOLDER_PASSWORD = '••••••••••••••••••••••••';
- // Password storage security
- const ALGO_PASSWORD = PASSWORD_ARGON2ID;
- const OPTIONS_PASSWORD = [
- 'memory_cost' => 8192,
- 'time_cost' => 2,
- 'threads' => 64,
- ];
- function checkUsernameFormat(string $username): void {
- if (preg_match('/' . USERNAME_REGEX . '/Du', $username) !== 1)
- output(403, 'Username malformed.');
- }
- function checkPasswordFormat(string $password): void {
- if (preg_match('/' . PASSWORD_REGEX . '/Du', $password) !== 1)
- output(403, 'Password malformed.');
- }
- function hashUsername(string $username): string {
- 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(string $password): string {
- return password_hash($password, ALGO_PASSWORD, OPTIONS_PASSWORD);
- }
- function usernameExists(string $username): bool {
- return isset(query('select', 'users', ['username' => $username], ['id'])[0]);
- }
- function checkPassword(string $id, string $password): bool {
- return password_verify($password, query('select', 'users', ['id' => $id], ['password'])[0]);
- }
- function outdatedPasswordHash(string $id): bool {
- return password_needs_rehash(query('select', 'users', ['id' => $id], ['password'])[0], ALGO_PASSWORD, OPTIONS_PASSWORD);
- }
- function changePassword(string $id, string $password): void {
- DB->prepare('UPDATE users SET password = :password WHERE id = :id')
- ->execute([':password' => hashPassword($password), ':id' => $id]);
- }
- function stopSession(): void {
- if (session_status() === PHP_SESSION_ACTIVE)
- session_destroy();
- }
- function logout(): never {
- stopSession();
- header('Clear-Site-Data: "*"');
- redir();
- }
- function setupDisplayUsername(string $display_username): void {
- $nonce = random_bytes(24);
- $key = sodium_crypto_aead_xchacha20poly1305_ietf_keygen();
- $cyphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
- htmlspecialchars($display_username),
- '',
- $nonce,
- $key
- );
- $_SESSION['display-username-nonce'] = $nonce;
- setcookie(
- 'display-username-decryption-key',
- base64_encode($key),
- [
- 'expires' => time() + 432000,
- 'path' => CONF['common']['prefix'] . '/',
- 'secure' => true,
- 'httponly' => true,
- 'samesite' => 'Strict'
- ]
- );
- $_SESSION['display-username-cyphertext'] = $cyphertext;
- }
- function authDeleteUser(string $user_id): void {
- $user_services = explode(',', query('select', 'users', ['id' => $user_id], ['services'])[0]);
- foreach (SERVICES_USER as $service)
- if (in_array($service, $user_services, true) AND CONF['common']['services'][$service] !== 'enabled')
- output(503, sprintf(_('Your account can\'t be deleted because the %s service is currently unavailable.'), '<em>' . PAGES[$service]['index']['title'] . '</em>'));
- if (in_array('reg', $user_services, true))
- foreach (query('select', 'registry', ['username' => $user_id], ['domain']) as $domain)
- regDeleteDomain($domain, $user_id);
- if (in_array('ns', $user_services, true))
- foreach (query('select', 'zones', ['username' => $user_id], ['zone']) as $zone)
- nsDeleteZone($zone, $user_id);
- if (in_array('ht', $user_services, true)) {
- foreach (query('select', 'sites', ['username' => $user_id]) as $site)
- htDeleteSite($site['address'], $site['type'], $user_id);
- exescape([
- CONF['ht']['sudo_path'],
- '-u',
- CONF['ht']['tor_user'],
- '--',
- CONF['ht']['rm_path'],
- '-r',
- '--',
- CONF['ht']['tor_keys_path'] . '/' . $user_id,
- ], result_code: $code);
- if ($code !== 0)
- output(500, 'Can\'t remove Tor keys directory.');
- removeDirectory(CONF['ht']['tor_config_path'] . '/' . $user_id);
- exescape([
- CONF['ht']['sudo_path'],
- '-u',
- CONF['ht']['sftpgo_user'],
- '--',
- CONF['ht']['rm_path'],
- '-r',
- '--',
- CONF['ht']['ht_path'] . '/fs/' . $user_id
- ], result_code: $code);
- if ($code !== 0)
- output(500, 'Can\'t remove user\'s directory.');
- query('delete', 'ssh-keys', ['username' => $user_id]);
- }
- query('delete', 'users', ['id' => $user_id]);
- }
- function rateLimit(): void {
- if ((PAGE_METADATA['tokens_account_cost'] ?? 0) > 0)
- rateLimitAccount(PAGE_METADATA['tokens_account_cost']);
- if ((PAGE_METADATA['tokens_instance_cost'] ?? 0) > 0)
- rateLimitInstance(PAGE_METADATA['tokens_instance_cost']);
- }
- const MAX_ACCOUNT_TOKENS = 86400;
- function rateLimitAccount(int $requestedTokens): int {
- // Get
- $userData = query('select', 'users', ['id' => $_SESSION['id']]);
- $tokens = $userData[0]['bucket_tokens'];
- $bucketLastUpdate = $userData[0]['bucket_last_update'];
- // Compute
- $tokens = min(MAX_ACCOUNT_TOKENS, $tokens + (time() - $bucketLastUpdate));
- if ($requestedTokens > 0) {
- if ($requestedTokens > $tokens)
- output(453, _('Account rate limit reached, try again later.'));
- $tokens -= $requestedTokens;
- // Update
- DB->prepare('UPDATE users SET bucket_tokens = :bucket_tokens, bucket_last_update = :bucket_last_update WHERE id = :id')
- ->execute([
- ':bucket_tokens' => $tokens,
- ':bucket_last_update' => time(),
- ':id' => $_SESSION['id']
- ]);
- }
- return $tokens;
- }
- function rateLimitInstance(int $requestedTokens): void {
- // Get
- $tokens = query('select', 'params', ['name' => 'instance_bucket_tokens'], ['value'])[0];
- $bucketLastUpdate = query('select', 'params', ['name' => 'instance_bucket_last_update'], ['value'])[0];
- // Compute
- $tokens = min(86400, $tokens + (time() - $bucketLastUpdate));
- if ($requestedTokens > $tokens)
- output(453, _('Global rate limit reached, try again later.'));
- $tokens -= $requestedTokens;
- // Update
- DB->prepare("UPDATE params SET value = :bucket_tokens WHERE name = 'instance_bucket_tokens';")
- ->execute([':bucket_tokens' => $tokens]);
- DB->prepare("UPDATE params SET value = :bucket_last_update WHERE name = 'instance_bucket_last_update';")
- ->execute([':bucket_last_update' => time()]);
- }
|