2023-07-17 19:15:18 +00:00
< ? php declare ( strict_types = 1 );
2021-08-05 00:51:21 +00:00
2022-11-26 20:45:48 +00:00
const USERNAME_REGEX = '^.{1,1024}$' ;
2022-10-09 21:36:35 +00:00
const PASSWORD_REGEX = '^(?=.*[\p{Ll}])(?=.*[\p{Lu}])(?=.*[\p{N}]).{8,1024}|.{10,1024}$' ;
2022-04-22 23:57:43 +00:00
2022-10-09 21:36:35 +00:00
const PLACEHOLDER_USERNAME = 'lain' ;
const PLACEHOLDER_PASSWORD = '••••••••••••••••••••••••' ;
2022-06-10 19:14:47 +00:00
2022-04-22 23:57:43 +00:00
// Password storage security
2022-10-09 21:36:35 +00:00
const ALGO_PASSWORD = PASSWORD_ARGON2ID ;
const OPTIONS_PASSWORD = [
2022-11-20 14:11:54 +00:00
'memory_cost' => 65536 ,
'time_cost' => 4 ,
'threads' => 64 ,
2022-10-09 21:36:35 +00:00
];
2022-04-22 23:57:43 +00:00
2023-06-19 22:36:58 +00:00
function checkUsernameFormat ( string $username ) : void {
2022-11-30 22:12:42 +00:00
if ( preg_match ( '/' . USERNAME_REGEX . '/Du' , $username ) !== 1 )
output ( 403 , 'Username malformed.' );
}
2023-06-19 22:36:58 +00:00
function checkPasswordFormat ( string $password ) : void {
2022-11-20 17:17:03 +00:00
if ( preg_match ( '/' . PASSWORD_REGEX . '/Du' , $password ) !== 1 )
2022-09-15 17:17:48 +00:00
output ( 403 , 'Password malformed.' );
2022-04-22 23:57:43 +00:00
}
2023-06-19 22:36:58 +00:00
function hashUsername ( string $username ) : string {
2023-10-07 22:50:48 +00:00
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 ));
2022-04-22 23:57:43 +00:00
}
2023-06-19 22:36:58 +00:00
function hashPassword ( string $password ) : string {
2022-04-18 14:05:00 +00:00
return password_hash ( $password , ALGO_PASSWORD , OPTIONS_PASSWORD );
2021-08-05 00:51:21 +00:00
}
2023-06-19 22:36:58 +00:00
function usernameExists ( string $username ) : bool {
2023-10-07 22:50:48 +00:00
return isset ( query ( 'select' , 'users' , [ 'username' => $username ], [ 'id' ])[ 0 ]);
2022-04-22 23:57:43 +00:00
}
2023-06-19 22:36:58 +00:00
function checkPassword ( string $id , string $password ) : bool {
2023-10-07 22:50:48 +00:00
return password_verify ( $password , query ( 'select' , 'users' , [ 'id' => $id ], [ 'password' ])[ 0 ]);
2021-08-05 00:51:21 +00:00
}
2023-06-19 22:36:58 +00:00
function outdatedPasswordHash ( string $id ) : bool {
2023-10-07 22:50:48 +00:00
return password_needs_rehash ( query ( 'select' , 'users' , [ 'id' => $id ], [ 'password' ])[ 0 ], ALGO_PASSWORD , OPTIONS_PASSWORD );
2021-08-05 00:51:21 +00:00
}
2023-06-19 22:36:58 +00:00
function changePassword ( string $id , string $password ) : void {
2022-12-13 16:38:54 +00:00
DB -> prepare ( 'UPDATE users SET password = :password WHERE id = :id' )
-> execute ([ ':password' => hashPassword ( $password ), ':id' => $id ]);
2021-08-05 00:51:21 +00:00
}
2022-09-16 22:49:07 +00:00
2023-06-19 22:36:58 +00:00
function stopSession () : void {
2022-11-30 22:38:02 +00:00
if ( session_status () === PHP_SESSION_ACTIVE )
session_destroy ();
2022-12-20 20:17:03 +00:00
}
2023-06-19 22:36:58 +00:00
function logout () : never {
2022-12-20 20:17:03 +00:00
stopSession ();
2022-11-30 22:38:02 +00:00
header ( 'Clear-Site-Data: "*"' );
redir ();
}
2023-06-19 22:36:58 +00:00
function setupDisplayUsername ( string $display_username ) : void {
2023-01-18 15:00:17 +00:00
$nonce = random_bytes ( 24 );
$key = sodium_crypto_aead_xchacha20poly1305_ietf_keygen ();
$cyphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt (
2023-01-07 22:11:44 +00:00
htmlspecialchars ( $display_username ),
2023-02-07 21:25:16 +00:00
'' ,
2023-01-18 15:00:17 +00:00
$nonce ,
$key
2023-01-07 22:11:44 +00:00
);
2023-01-18 15:00:17 +00:00
$_SESSION [ 'display-username-nonce' ] = $nonce ;
2023-01-07 22:11:44 +00:00
setcookie (
'display-username-decryption-key' ,
base64_encode ( $key ),
[
'expires' => time () + 432000 ,
2023-01-26 15:22:03 +00:00
'path' => CONF [ 'common' ][ 'prefix' ] . '/' ,
2023-01-07 22:11:44 +00:00
'secure' => true ,
'httponly' => true ,
'samesite' => 'Strict'
]
);
$_SESSION [ 'display-username-cyphertext' ] = $cyphertext ;
}
2023-06-19 22:36:58 +00:00
function authDeleteUser ( string $user_id ) : void {
2023-10-07 22:50:48 +00:00
$user_services = explode ( ',' , query ( 'select' , 'users' , [ 'id' => $user_id ], [ 'services' ])[ 0 ]);
2023-06-08 15:36:44 +00:00
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 ))
2023-10-07 22:50:48 +00:00
foreach ( query ( 'select' , 'registry' , [ 'username' => $user_id ], [ 'domain' ]) as $domain )
2023-06-08 15:36:44 +00:00
regDeleteDomain ( $domain , $user_id );
2023-06-26 02:13:52 +00:00
if ( in_array ( 'ns' , $user_services , true ))
2023-10-07 22:50:48 +00:00
foreach ( query ( 'select' , 'zones' , [ 'username' => $user_id ], [ 'zone' ]) as $zone )
2023-06-08 15:36:44 +00:00
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 );
2023-06-19 00:15:43 +00:00
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 );
2023-06-08 15:36:44 +00:00
if ( $code !== 0 )
output ( 500 , 'Can\'t remove Tor keys directory.' );
removeDirectory ( CONF [ 'ht' ][ 'tor_config_path' ] . '/' . $user_id );
2023-06-19 00:15:43 +00:00
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 );
2023-06-08 15:36:44 +00:00
if ( $code !== 0 )
output ( 500 , 'Can\'t remove user\'s directory.' );
2023-06-21 20:08:57 +00:00
query ( 'delete' , 'ssh-keys' , [ 'username' => $user_id ]);
2023-06-08 15:36:44 +00:00
}
query ( 'delete' , 'users' , [ 'id' => $user_id ]);
}
2023-06-19 22:36:58 +00:00
function rateLimit () : void {
2023-08-14 16:20:47 +00:00
if (( PAGE_METADATA [ 'tokens_account_cost' ] ? ? 0 ) > 0 )
2022-09-16 22:49:07 +00:00
rateLimitAccount ( PAGE_METADATA [ 'tokens_account_cost' ]);
2023-08-14 16:20:47 +00:00
if (( PAGE_METADATA [ 'tokens_instance_cost' ] ? ? 0 ) > 0 )
2022-09-16 22:49:07 +00:00
rateLimitInstance ( PAGE_METADATA [ 'tokens_instance_cost' ]);
}
2023-05-02 17:30:53 +00:00
const MAX_ACCOUNT_TOKENS = 86400 ;
2023-06-19 22:36:58 +00:00
function rateLimitAccount ( int $requestedTokens ) : int {
2022-09-16 22:49:07 +00:00
// Get
2022-11-30 22:12:42 +00:00
$userData = query ( 'select' , 'users' , [ 'id' => $_SESSION [ 'id' ]]);
2022-09-16 22:49:07 +00:00
$tokens = $userData [ 0 ][ 'bucket_tokens' ];
$bucketLastUpdate = $userData [ 0 ][ 'bucket_last_update' ];
// Compute
2023-05-02 17:30:53 +00:00
$tokens = min ( MAX_ACCOUNT_TOKENS , $tokens + ( time () - $bucketLastUpdate ));
2022-09-16 22:49:07 +00:00
2023-05-02 17:30:53 +00:00
if ( $requestedTokens > 0 ) {
if ( $requestedTokens > $tokens )
output ( 453 , _ ( 'Account rate limit reached, try again later.' ));
2022-09-16 22:49:07 +00:00
2023-05-02 17:30:53 +00:00
$tokens -= $requestedTokens ;
2022-09-16 22:49:07 +00:00
2023-05-02 17:30:53 +00:00
// 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 ;
2022-09-16 22:49:07 +00:00
}
2023-06-19 22:36:58 +00:00
function rateLimitInstance ( int $requestedTokens ) : void {
2022-09-16 22:49:07 +00:00
// Get
2023-10-07 22:50:48 +00:00
$tokens = query ( 'select' , 'params' , [ 'name' => 'instance_bucket_tokens' ], [ 'value' ])[ 0 ];
$bucketLastUpdate = query ( 'select' , 'params' , [ 'name' => 'instance_bucket_last_update' ], [ 'value' ])[ 0 ];
2022-09-16 22:49:07 +00:00
// Compute
$tokens = min ( 86400 , $tokens + ( time () - $bucketLastUpdate ));
if ( $requestedTokens > $tokens )
2023-01-18 15:00:17 +00:00
output ( 453 , _ ( 'Global rate limit reached, try again later.' ));
2022-09-16 22:49:07 +00:00
$tokens -= $requestedTokens ;
// Update
2022-12-13 16:38:54 +00:00
DB -> prepare ( " UPDATE params SET value = :bucket_tokens WHERE name = 'instance_bucket_tokens'; " )
-> execute ([ ':bucket_tokens' => $tokens ]);
2022-09-16 22:49:07 +00:00
2022-12-13 16:38:54 +00:00
DB -> prepare ( " UPDATE params SET value = :bucket_last_update WHERE name = 'instance_bucket_last_update'; " )
-> execute ([ ':bucket_last_update' => time ()]);
2022-09-16 22:49:07 +00:00
}