Compare commits

...

3 Commits

Author SHA1 Message Date
Zoey
a78b625568 Apply suggestion from @Zoey2936
Signed-off-by: Zoey <zoey@z0ey.de>
2026-04-13 16:00:54 +02:00
Zoey
443593d4fb block reusing tokens 2026-03-05 21:38:26 +01:00
Zoey
15721c6d3a use a private/public key logic to generate login tokens for the AIO ui #7686 2026-03-05 21:37:03 +01:00
4 changed files with 52 additions and 5 deletions

View File

@@ -55,7 +55,12 @@ class Admin implements ISettings {
$lastUpdateCheckTimestamp = $this->config->getAppValue('core', 'lastupdatedat');
$lastUpdateCheck = $this->dateTimeFormatter->formatDateTime($lastUpdateCheckTimestamp);
$token = urlencode(getenv('AIO_TOKEN'));
$privateKeyBase64 = getenv('AIO_TOKEN');
$privateKeyBin = sodium_base642bin($privateKeyBase64, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
$timestamp = (string) time();
$tokenBin = sodium_crypto_sign($timestamp, $privateKeyBin);
$token = sodium_bin2base64($tokenBin, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
$params = [
'AIOLoginUrl' => 'https://' . getenv('AIO_URL') . '/api/auth/getlogin' . '?token=' . $token,
];

View File

@@ -20,7 +20,38 @@ readonly class AuthManager {
}
public function CheckToken(string $token) : bool {
return hash_equals($this->configurationManager->aioToken, $token);
$publicKeyBase64 = $this->configurationManager->aioPublicKey;
if ($publicKeyBase64 === '' || $token === '') {
return false;
}
try {
$publicKeyBin = sodium_base642bin($publicKeyBase64, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
$tokenBin = sodium_base642bin($token, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
} catch (\SodiumException) {
return false;
}
$timestamp = sodium_crypto_sign_open($tokenBin, $publicKeyBin);
if ($timestamp === false) {
return false;
}
$timeElapsed = time() - (int) $timestamp;
if ($timeElapsed > 60 || $timeElapsed < 0) {
return false;
}
// Prevent token replay: reject tokens that have already been used
$tokenHash = hash('sha256', $token);
$cacheKey = 'used_token_' . $tokenHash;
if (apcu_fetch($cacheKey) !== false) {
return false;
}
apcu_add($cacheKey, true, 60);
return true;
}
public function SetAuthState(bool $isLoggedIn) : void {

View File

@@ -232,7 +232,16 @@ readonly class DockerController {
}
public function startTopContainer(bool $pullImage, ?\Closure $addToStreamingResponseBody = null) : void {
$this->configurationManager->aioToken = bin2hex(random_bytes(24));
$keypair = sodium_crypto_sign_keypair();
$privateKeyBin = sodium_crypto_sign_secretkey($keypair);
$publicKeyBin = sodium_crypto_sign_publickey($keypair);
$privateKeyBase64 = sodium_bin2base64($privateKeyBin, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
$publicKeyBase64 = sodium_bin2base64($publicKeyBin, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
$this->configurationManager->aioPublicKey = $publicKeyBase64;
$this->configurationManager->aioPrivateKey = $privateKeyBase64;
// Stop domaincheck since apache would not be able to start otherwise
$this->StopDomaincheckContainer();

View File

@@ -12,9 +12,11 @@ class ConfigurationManager
private array $config = [];
public string $aioPrivateKey = '';
private bool $noWrite = false;
public string $aioToken {
public string $aioPublicKey {
get => $this->get('AIO_TOKEN', '');
set { $this->set('AIO_TOKEN', $value); }
}
@@ -1017,7 +1019,7 @@ class ConfigurationManager
return match ($placeholder) {
'NC_DOMAIN' => $this->domain,
'NC_BASE_DN' => $this->getBaseDN(),
'AIO_TOKEN' => $this->aioToken,
'AIO_TOKEN' => $this->aioPrivateKey,
'BORGBACKUP_REMOTE_REPO' => $this->borgRemoteRepo,
'BORGBACKUP_MODE' => $this->backupMode,
'AIO_URL' => $this->aioUrl,