mirror of
https://github.com/LogicLabs-OU/OpenArchiver.git
synced 2026-04-06 00:31:57 +02:00
User api key: Exclude public API endpoints from rate limiting (#86)
* feat(auth): Implement API key authentication This commit enables API access with an API key system. This change provides a better experience for programmatic access and third-party integrations. Key changes include: - **API Key Management:** Users can now generate, manage, and revoke persistent API keys through a new "API Keys" section in the settings UI. - **Authentication Middleware:** API requests are now authenticated via an `X-API-KEY` header instead of the previous `Authorization: Bearer` token. - **Backend Implementation:** Adds a new `api_keys` database table, along with corresponding services, controllers, and routes to manage the key lifecycle securely. - **Rate Limiting:** The API rate limiter now uses the API key to identify and track requests. - **Documentation:** The API authentication documentation has been updated to reflect the new method. * Add configurable API rate limiting Two new variables are added to `.env.example`: - `RATE_LIMIT_WINDOW_MS`: The time window in milliseconds for which requests are checked (defaults to 15 minutes). - `RATE_LIMIT_MAX_REQUESTS`: The maximum number of requests allowed from an IP within the window (defaults to 100). The installation documentation has been updated to reflect these new configuration options. * Disable API operation in demo mode * Exclude public API endpoints from rate limiting --------- Co-authored-by: Wayne <5291640+ringoinca@users.noreply.github.com>
This commit is contained in:
@@ -93,7 +93,19 @@ const apiKeyRouter = apiKeyRoutes(authService);
|
||||
app.use('/v1/upload', uploadRouter);
|
||||
|
||||
// Middleware for all other routes
|
||||
app.use(rateLimiter);
|
||||
app.use((req, res, next) => {
|
||||
// exclude certain API endpoints from the rate limiter, for example status, system settings
|
||||
const excludedPatterns = [
|
||||
/^\/v\d+\/auth\/status$/,
|
||||
/^\/v\d+\/settings\/system$/
|
||||
];
|
||||
for (const pattern of excludedPatterns) {
|
||||
if (pattern.test(req.path)) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
rateLimiter(req, res, next);
|
||||
});
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ import type { SystemSettings } from '@open-archiver/types';
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
const { locals, url } = event;
|
||||
try {
|
||||
const response = await api('/auth/status', event);
|
||||
const response = await api('/auth/status', event);
|
||||
|
||||
if (response.ok) {
|
||||
const { needsSetup } = await response.json();
|
||||
|
||||
if (needsSetup && url.pathname !== '/setup') {
|
||||
@@ -17,19 +18,24 @@ export const load: LayoutServerLoad = async (event) => {
|
||||
if (!needsSetup && url.pathname === '/setup') {
|
||||
throw redirect(307, '/signin');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} else {
|
||||
// if auth status check fails, we can't know if the setup is complete,
|
||||
// so we redirect to signin page as a safe fallback.
|
||||
if (url.pathname !== '/signin') {
|
||||
console.error('Failed to get auth status:', await response.text());
|
||||
throw redirect(307, '/signin');
|
||||
}
|
||||
}
|
||||
|
||||
const settingsResponse = await api('/settings', event);
|
||||
const settings: SystemSettings | null = settingsResponse.ok
|
||||
? await settingsResponse.json()
|
||||
const systemSettingsResponse = await api('/settings/system', event);
|
||||
const systemSettings: SystemSettings | null = systemSettingsResponse.ok
|
||||
? await systemSettingsResponse.json()
|
||||
: null;
|
||||
|
||||
return {
|
||||
user: locals.user,
|
||||
accessToken: locals.accessToken,
|
||||
isDemo: process.env.IS_DEMO === 'true',
|
||||
settings,
|
||||
systemSettings,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
let finalTheme = $theme;
|
||||
|
||||
if (finalTheme === 'system') {
|
||||
finalTheme = data.settings?.theme || 'system';
|
||||
finalTheme = data.systemSettings?.theme || 'system';
|
||||
}
|
||||
|
||||
const isDark =
|
||||
|
||||
@@ -8,8 +8,8 @@ export const load: LayoutLoad = async ({ url, data }) => {
|
||||
|
||||
let initLocale: SupportedLanguage = 'en'; // Default fallback
|
||||
|
||||
if (data.settings?.language) {
|
||||
initLocale = data.settings.language;
|
||||
if (data.systemSettings?.language) {
|
||||
initLocale = data.systemSettings.language;
|
||||
}
|
||||
|
||||
console.log(initLocale);
|
||||
|
||||
@@ -11,9 +11,9 @@ export const load: PageServerLoad = async (event) => {
|
||||
throw error(response.status, message || 'Failed to fetch system settings');
|
||||
}
|
||||
|
||||
const settings: SystemSettings = await response.json();
|
||||
const systemSettings: SystemSettings = await response.json();
|
||||
return {
|
||||
settings,
|
||||
systemSettings,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let { data, form }: { data: PageData; form: any } = $props();
|
||||
let settings = $state(data.settings);
|
||||
let settings = $state(data.systemSettings);
|
||||
let isSaving = $state(false);
|
||||
|
||||
const languageOptions: { value: SupportedLanguage; label: string }[] = [
|
||||
|
||||
Reference in New Issue
Block a user