Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions server/.htaccess
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
RewriteEngine On

### Allow server-status from localhost for supervision ###
RewriteCond %{HTTP_HOST} ^localhost$
RewriteCond %{REQUEST_URI} ^/server-status
RewriteRule ^(.*)$ - [L]

### Normal rewrite rules for lookup-server ###
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

### Exclude server-status from rewrite to index.php ###
RewriteCond %{REQUEST_URI} !^/server-status
RewriteRule ^ index.php [QSA,L]
25 changes: 25 additions & 0 deletions server/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,28 @@
});
$container->get('DependenciesService')->initContainer($container, $app);

// Add error middleware for logging
$errorMiddleware = $app->addErrorMiddleware(
$settings['settings']['displayErrorDetails'] ?? true,
true, // logErrors
true // logErrorDetails
);

// Set custom error handler that logs to our Logger
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
$errorHandler->registerErrorRenderer('text/html', function ($exception, $displayErrorDetails) use ($container) {
/** @var \LookupServer\Logger $logger */
$logger = $container->get('Logger');
$logger->error('Exception: ' . $exception->getMessage(), [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString()
]);

$message = $displayErrorDetails
? sprintf('Error: %s in %s:%d', $exception->getMessage(), $exception->getFile(), $exception->getLine())
: 'An error occurred';

return "<html><body><h1>Error</h1><p>{$message}</p></body></html>";
});

111 changes: 111 additions & 0 deletions server/lib/Logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

/**
* lookup-server - Standalone Lookup Server.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Nicolas Varlot <[email protected]>
* @copyright 2025
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace LookupServer;

class Logger {
private string $logFile;
private bool $enabled;

public function __construct(string $logFile, bool $enabled = true) {
$this->logFile = $logFile;
$this->enabled = $enabled;
}

/**
* Log a message with a specific level
*
* @param string $level
* @param string $message
* @param array $context
*/
private function log(string $level, string $message, array $context = []): void {
if (!$this->enabled) {
return;
}

$timestamp = date('Y-m-d H:i:s');
$contextString = !empty($context) ? ' ' . json_encode($context) : '';
$logMessage = "[{$timestamp}] [{$level}] {$message}{$contextString}\n";

// Write to file
@file_put_contents($this->logFile, $logMessage, FILE_APPEND | LOCK_EX);
}

/**
* Log debug message
*
* @param string $message
* @param array $context
*/
public function debug(string $message, array $context = []): void {
$this->log('DEBUG', $message, $context);
}

/**
* Log info message
*
* @param string $message
* @param array $context
*/
public function info(string $message, array $context = []): void {
$this->log('INFO', $message, $context);
}

/**
* Log warning message
*
* @param string $message
* @param array $context
*/
public function warning(string $message, array $context = []): void {
$this->log('WARNING', $message, $context);
}

/**
* Log error message
*
* @param string $message
* @param array $context
*/
public function error(string $message, array $context = []): void {
$this->log('ERROR', $message, $context);
}

/**
* Log critical message
*
* @param string $message
* @param array $context
*/
public function critical(string $message, array $context = []): void {
$this->log('CRITICAL', $message, $context);
}
}

12 changes: 11 additions & 1 deletion server/lib/Service/DependenciesService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use DI\Container;
use Exception;
use LookupServer\InstanceManager;
use LookupServer\Logger;
use LookupServer\Replication;
use LookupServer\SignatureHandler;
use LookupServer\Tools\Traits\TArrayTools;
Expand All @@ -39,6 +40,14 @@ public function __construct(array $settings = []) {
*/
public function initContainer(Container $container, App $app): void {

$container->set('Logger', function (Container $c) {
$settings = $c->get('Settings');
return new Logger(
$this->get('settings.log_file', $settings),
$this->getBool('settings.log_enabled', $settings)
);
});

$container->set('db', function (Container $c) {
$db = $this->getArray('settings.db', $c->get('Settings'));
if (empty($db)) {
Expand Down Expand Up @@ -76,7 +85,8 @@ public function initContainer(Container $container, App $app): void {
$c->get('TwitterValidator'),
$c->get('InstanceManager'),
$c->get('SignatureHandler'),
$c->get('SecurityService')
$c->get('SecurityService'),
$c->get('Logger')
);
});

Expand Down
40 changes: 39 additions & 1 deletion server/lib/UserManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public function __construct(
private Twitter $twitterValidator,
private InstanceManager $instanceManager,
private SignatureHandler $signatureHandler,
private SecurityService $securityService
private SecurityService $securityService,
private ?Logger $logger
) {
}

Expand Down Expand Up @@ -57,6 +58,10 @@ public function search(Request $request, Response $response, array $args = []):
$params = $request->getQueryParams();
$search = urldecode($params['search'] ?? '');

if ($this->logger) {
$this->logger->info('Search request', ['search' => $search, 'params' => $params]);
}

if ($search === '') {
return $response->withStatus(400);
}
Expand Down Expand Up @@ -411,6 +416,9 @@ public function register(Request $request, Response $response, array $args = [])
|| !isset($body['message']['data']['federationId'])
|| !isset($body['signature'])
|| !isset($body['message']['timestamp'])) {
if ($this->logger) {
$this->logger->warning('Invalid registration request - missing required fields');
}
return $response->withStatus(400);
}

Expand All @@ -419,17 +427,32 @@ public function register(Request $request, Response $response, array $args = [])
try {
$verified = $this->signatureHandler->verify($cloudId, $body['message'], $body['signature']);
} catch (Exception $e) {
if ($this->logger) {
$this->logger->error('Registration signature verification failed', [
'cloudId' => $cloudId,
'error' => $e->getMessage()
]);
}
return $response->withStatus(400);
}

if ($verified) {
$result =
$this->insertOrUpdate($cloudId, $body['message']['data'], $body['message']['timestamp']);
if ($result === false) {
if ($this->logger) {
$this->logger->warning('Registration rejected - timestamp too old', ['cloudId' => $cloudId]);
}
return $response->withStatus(403);
}
if ($this->logger) {
$this->logger->info('User registered/updated successfully', ['cloudId' => $cloudId]);
}
} else {
// ERROR OUT
if ($this->logger) {
$this->logger->error('Registration signature invalid', ['cloudId' => $cloudId]);
}
return $response->withStatus(403);
}

Expand Down Expand Up @@ -542,16 +565,31 @@ public function delete(Request $request, Response $response, array $args = []):
try {
$verified = $this->signatureHandler->verify($cloudId, $body['message'], $body['signature']);
} catch (Exception $e) {
if ($this->logger) {
$this->logger->error('Delete signature verification failed', [
'cloudId' => $cloudId,
'error' => $e->getMessage()
]);
}
return $response->withStatus(400);
}

if ($verified) {
$result = $this->deleteDBRecord($cloudId);
if ($result === false) {
if ($this->logger) {
$this->logger->warning('Delete failed - user not found', ['cloudId' => $cloudId]);
}
return $response->withStatus(404);
}
if ($this->logger) {
$this->logger->info('User deleted successfully', ['cloudId' => $cloudId]);
}
} else {
// ERROR OUT
if ($this->logger) {
$this->logger->error('Delete signature invalid', ['cloudId' => $cloudId]);
}
return $response->withStatus(403);
}

Expand Down