From d2fdeccce33c5a102dc83b09760881ec51c47565 Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Sat, 26 Jul 2025 14:07:18 +0200 Subject: [PATCH] auth splitting --- .env.example | 5 +- src/components/AIQueryInterface.astro | 2 +- src/layouts/BaseLayout.astro | 43 +++++++++++------ src/pages/api/ai/query.ts | 2 +- src/pages/api/auth/status.ts | 16 ++++--- src/pages/api/contribute/knowledgebase.ts | 4 +- src/pages/api/contribute/tool.ts | 4 +- src/pages/api/upload/media.ts | 8 +--- src/pages/contribute/index.astro | 2 +- src/pages/contribute/knowledgebase.astro | 2 +- src/pages/contribute/tool.astro | 2 +- src/pages/index.astro | 8 ++-- src/utils/auth.ts | 58 ++++++++++++----------- 13 files changed, 87 insertions(+), 69 deletions(-) diff --git a/.env.example b/.env.example index d08ae25..7e9f47f 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,10 @@ AUTH_SECRET=change-this-to-a-strong-secret-key-in-production OIDC_ENDPOINT=https://your-oidc-provider.com OIDC_CLIENT_ID=your-oidc-client-id OIDC_CLIENT_SECRET=your-oidc-client-secret -AUTHENTICATION_NECESSARY=true + +# Auth Scopes - set to true in prod +AUTHENTICATION_NECESSARY_CONTRIBUTIONS=true +AUTHENTICATION_NECESSARY_AI=true # Application Configuration (Required) PUBLIC_BASE_URL=https://your-domain.com diff --git a/src/components/AIQueryInterface.astro b/src/components/AIQueryInterface.astro index 713b76d..1b58445 100644 --- a/src/components/AIQueryInterface.astro +++ b/src/components/AIQueryInterface.astro @@ -64,7 +64,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || []; - Ihre Anfrage wird an mistral.ai übertragen und unterliegt deren + Ihre Anfrage wird über die kostenlose API von mistral.ai übertragen, wird für KI-Training verwendet und unterliegt deren Datenschutzrichtlinien

diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 01a1b19..6faa170 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -112,15 +112,31 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital (window as any).isToolHosted = isToolHosted; // Client-side auth functions (consolidated from client-auth.js) - async function checkClientAuth() { + async function checkClientAuth(context = 'general') { try { const response = await fetch('/api/auth/status'); const data = await response.json(); - return { - authenticated: data.authenticated, - authRequired: data.authRequired, - expires: data.expires - }; + + switch (context) { + case 'contributions': + return { + authenticated: data.contributionAuthenticated, + authRequired: data.contributionAuthRequired, + expires: data.expires + }; + case 'ai': + return { + authenticated: data.aiAuthenticated, + authRequired: data.aiAuthRequired, + expires: data.expires + }; + default: + return { + authenticated: data.authenticated, + authRequired: data.contributionAuthRequired || data.aiAuthRequired, + expires: data.expires + }; + } } catch (error) { console.error('Auth check failed:', error); return { @@ -130,8 +146,8 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital } } - async function requireClientAuth(callback, returnUrl) { - const authStatus = await checkClientAuth(); + async function requireClientAuth(callback, returnUrl, context = 'general') { + const authStatus = await checkClientAuth(context); if (authStatus.authRequired && !authStatus.authenticated) { const targetUrl = returnUrl || window.location.href; @@ -145,8 +161,8 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital } } - async function showIfAuthenticated(selector) { - const authStatus = await checkClientAuth(); + async function showIfAuthenticated(selector, context = 'general') { + const authStatus = await checkClientAuth(context); const element = document.querySelector(selector); if (element) { @@ -157,11 +173,9 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital } function setupAuthButtons(selector = '[data-contribute-button]') { - // Use event delegation on document for dynamic content support document.addEventListener('click', async (e) => { if (!e.target) return; - // FIXED: Properly cast EventTarget to Element for closest() method const button = (e.target as Element).closest(selector); if (!button) return; @@ -169,10 +183,11 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital console.log('[AUTH] Contribute button clicked:', button.getAttribute('data-contribute-button')); + // ENHANCED: Use contributions context await requireClientAuth(() => { console.log('[AUTH] Navigation approved, redirecting to:', (button as HTMLAnchorElement).href); window.location.href = (button as HTMLAnchorElement).href; - }, (button as HTMLAnchorElement).href); + }, (button as HTMLAnchorElement).href, 'contributions'); }); } @@ -187,7 +202,7 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital setupAuthButtons('[data-contribute-button]'); const initAIButton = async () => { - await showIfAuthenticated('#ai-view-toggle'); + await showIfAuthenticated('#ai-view-toggle', 'ai'); }; initAIButton(); diff --git a/src/pages/api/ai/query.ts b/src/pages/api/ai/query.ts index 2600754..5d38ca7 100644 --- a/src/pages/api/ai/query.ts +++ b/src/pages/api/ai/query.ts @@ -278,7 +278,7 @@ Antworte NUR mit validen JSON. Keine zusätzlichen Erklärungen außerhalb des J export const POST: APIRoute = async ({ request }) => { try { // CONSOLIDATED: Replace 20+ lines with single function call (UNCHANGED) - const authResult = await withAPIAuth(request); + const authResult = await withAPIAuth(request, 'ai'); if (!authResult.authenticated) { return createAuthErrorResponse(); } diff --git a/src/pages/api/auth/status.ts b/src/pages/api/auth/status.ts index 3cba701..3dfb749 100644 --- a/src/pages/api/auth/status.ts +++ b/src/pages/api/auth/status.ts @@ -1,4 +1,4 @@ -// src/pages/api/auth/status.ts (FIXED - Updated imports and consolidated) +// src/pages/api/auth/status.ts import type { APIRoute } from 'astro'; import { withAPIAuth } from '../../../utils/auth.js'; import { apiResponse, handleAPIRequest } from '../../../utils/api.js'; @@ -7,14 +7,16 @@ export const prerender = false; export const GET: APIRoute = async ({ request }) => { return await handleAPIRequest(async () => { - // CONSOLIDATED: Single function call replaces 35+ lines - const authResult = await withAPIAuth(request); + const contributionAuth = await withAPIAuth(request, 'contributions'); + const aiAuth = await withAPIAuth(request, 'ai'); return apiResponse.success({ - authenticated: authResult.authenticated, - authRequired: authResult.authRequired, - expires: authResult.session?.exp ? new Date(authResult.session.exp * 1000).toISOString() : null + authenticated: contributionAuth.authenticated || aiAuth.authenticated, + contributionAuthRequired: contributionAuth.authRequired, + aiAuthRequired: aiAuth.authRequired, + contributionAuthenticated: contributionAuth.authenticated, + aiAuthenticated: aiAuth.authenticated, + expires: contributionAuth.session?.exp ? new Date(contributionAuth.session.exp * 1000).toISOString() : null }); - }, 'Status check failed'); }; \ No newline at end of file diff --git a/src/pages/api/contribute/knowledgebase.ts b/src/pages/api/contribute/knowledgebase.ts index ccdd56c..cfd46c7 100644 --- a/src/pages/api/contribute/knowledgebase.ts +++ b/src/pages/api/contribute/knowledgebase.ts @@ -84,12 +84,12 @@ function validateKnowledgebaseData(data: KnowledgebaseContributionData): { valid export const POST: APIRoute = async ({ request }) => { return await handleAPIRequest(async () => { // Check authentication - const authResult = await withAPIAuth(request); + const authResult = await withAPIAuth(request, 'contributions'); if (authResult.authRequired && !authResult.authenticated) { return apiError.unauthorized(); } - const userEmail = authResult.session?.email || 'anonymous@example.com'; + const userEmail = authResult.session?.email || 'anon@anon.anon'; // Rate limiting if (!checkRateLimit(userEmail)) { diff --git a/src/pages/api/contribute/tool.ts b/src/pages/api/contribute/tool.ts index 0961f0d..ccc0731 100644 --- a/src/pages/api/contribute/tool.ts +++ b/src/pages/api/contribute/tool.ts @@ -127,13 +127,13 @@ async function validateToolData(tool: any, action: string): Promise<{ valid: boo export const POST: APIRoute = async ({ request }) => { return await handleAPIRequest(async () => { // Authentication check - const authResult = await withAPIAuth(request); + const authResult = await withAPIAuth(request, 'contributions'); if (authResult.authRequired && !authResult.authenticated) { return apiError.unauthorized(); } const userId = authResult.session?.userId || 'anonymous'; - const userEmail = authResult.session?.email || 'anonymous@example.com'; + const userEmail = authResult.session?.email || 'anon@anon.anon'; // Rate limiting if (!checkRateLimit(userId)) { diff --git a/src/pages/api/upload/media.ts b/src/pages/api/upload/media.ts index 7b4b31f..74a5031 100644 --- a/src/pages/api/upload/media.ts +++ b/src/pages/api/upload/media.ts @@ -139,23 +139,19 @@ async function uploadToLocal(file: File, userType: string): Promise { return await handleAPIRequest(async () => { - // Authentication check - const authResult = await withAPIAuth(request); + const authResult = await withAPIAuth(request, 'contributions'); if (authResult.authRequired && !authResult.authenticated) { return apiError.unauthorized(); } - const userEmail = authResult.session?.email || 'anonymous@example.com'; + const userEmail = authResult.session?.email || 'anon@anon.anon'; - // Rate limiting if (!checkUploadRateLimit(userEmail)) { return apiError.rateLimit('Upload rate limit exceeded. Please wait before uploading again.'); } - // Parse multipart form data let formData; try { formData = await request.formData(); diff --git a/src/pages/contribute/index.astro b/src/pages/contribute/index.astro index 52ba58b..312480e 100644 --- a/src/pages/contribute/index.astro +++ b/src/pages/contribute/index.astro @@ -6,7 +6,7 @@ import { withAuth } from '../../utils/auth.js'; // Note: .js extension! export const prerender = false; // CONSOLIDATED: Replace 15+ lines with single function call -const authResult = await withAuth(Astro); +const authResult = await withAuth(Astro, 'contributions'); if (authResult instanceof Response) { return authResult; // Redirect to login } diff --git a/src/pages/contribute/knowledgebase.astro b/src/pages/contribute/knowledgebase.astro index 777a1d3..f53c379 100644 --- a/src/pages/contribute/knowledgebase.astro +++ b/src/pages/contribute/knowledgebase.astro @@ -7,7 +7,7 @@ import { getToolsData } from '../../utils/dataService.js'; export const prerender = false; // Check authentication -const authResult = await withAuth(Astro); +const authResult = await withAuth(Astro, 'contributions'); if (authResult instanceof Response) { return authResult; } diff --git a/src/pages/contribute/tool.astro b/src/pages/contribute/tool.astro index 88e55a4..f7f28b1 100644 --- a/src/pages/contribute/tool.astro +++ b/src/pages/contribute/tool.astro @@ -7,7 +7,7 @@ import { getToolsData } from '../../utils/dataService.js'; export const prerender = false; // Check authentication -const authResult = await withAuth(Astro); +const authResult = await withAuth(Astro, 'contributions'); if (authResult instanceof Response) { return authResult; } diff --git a/src/pages/index.astro b/src/pages/index.astro index c7c76e2..55510d3 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -121,15 +121,13 @@ const tools = data.tools; return; } - // AI Query Button Handler using consolidated auth system if (aiQueryBtn) { aiQueryBtn.addEventListener('click', async () => { - // Use the global auth system consistently if (typeof window.requireClientAuth === 'function') { - await window.requireClientAuth(() => switchToView('ai'), `${window.location.pathname}?view=ai`); + // ENHANCED: Use AI-specific authentication + await window.requireClientAuth(() => switchToView('ai'), `${window.location.pathname}?view=ai`, 'ai'); } else { - // Better fallback logging - console.warn('[AUTH] requireClientAuth not available - client-auth.js may not be loaded properly'); + console.warn('[AUTH] requireClientAuth not available'); switchToView('ai'); } }); diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 44139e0..35d6754 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -30,6 +30,8 @@ export interface AuthContext { userId: string; } +export type AuthContextType = 'contributions' | 'ai' | 'general'; + export interface UserInfo { sub?: string; preferred_username?: string; @@ -265,10 +267,19 @@ export function verifyAuthState(request: Request, receivedState: string): { return { isValid: true, stateData }; } -/** - * CONSOLIDATED: Create session with cookie headers - * Replaces duplicated session creation in callback.ts and process.ts - */ +function getAuthRequirement(context: AuthContextType): boolean { + switch (context) { + case 'contributions': + return process.env.AUTHENTICATION_NECESSARY_CONTRIBUTIONS !== 'false'; + case 'ai': + return process.env.AUTHENTICATION_NECESSARY_AI !== 'false'; + case 'general': + return process.env.AUTHENTICATION_NECESSARY !== 'false'; + default: + return true; + } +} + export async function createSessionWithCookie(userInfo: UserInfo): Promise<{ sessionToken: string; sessionCookie: string; @@ -292,27 +303,20 @@ export async function createSessionWithCookie(userInfo: UserInfo): Promise<{ }; } -/** - * CONSOLIDATED: Replace repeated auth patterns in .astro pages - * Usage: const authResult = await withAuth(Astro); - * if (authResult instanceof Response) return authResult; - */ -export async function withAuth(Astro: AstroGlobal): Promise { - const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false'; - console.log('[DEBUG PAGE] Auth required:', authRequired); +export async function withAuth(Astro: AstroGlobal, context: AuthContextType = 'general'): Promise { + const authRequired = getAuthRequirement(context); + console.log(`[DEBUG PAGE] Auth required for ${context}:`, authRequired); console.log('[DEBUG PAGE] Request URL:', Astro.url.toString()); - // If auth not required, return mock context if (!authRequired) { return { authenticated: true, session: null, - userEmail: 'anonymous@example.com', + userEmail: 'anon@anon.anon', userId: 'anonymous' }; } - // Check session const sessionToken = getSessionFromRequest(Astro.request); console.log('[DEBUG PAGE] Session token found:', !!sessionToken); @@ -337,7 +341,7 @@ export async function withAuth(Astro: AstroGlobal): Promise { - const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false'; + const authRequired = getAuthRequirement(context); if (!authRequired) { return { @@ -367,10 +367,10 @@ export async function withAPIAuth(request: Request): Promise<{ } const sessionToken = getSessionFromRequest(request); - console.log('[DEBUG] Session token found:', !!sessionToken); + console.log(`[DEBUG API] Session token found for ${context}:`, !!sessionToken); if (!sessionToken) { - console.log('[DEBUG] No session token found'); + console.log(`[DEBUG API] No session token found for ${context}`); return { authenticated: false, userId: '', @@ -379,10 +379,10 @@ export async function withAPIAuth(request: Request): Promise<{ } const session = await verifySession(sessionToken); - console.log('[DEBUG] Session verification result:', !!session); + console.log(`[DEBUG API] Session verification result for ${context}:`, !!session); if (!session) { - console.log('[DEBUG] Session verification failed'); + console.log(`[DEBUG API] Session verification failed for ${context}`); return { authenticated: false, userId: '', @@ -390,11 +390,15 @@ export async function withAPIAuth(request: Request): Promise<{ }; } - console.log('[DEBUG] Authentication successful for user:', session.userId); + console.log(`[DEBUG API] Authentication successful for ${context}:`, session.userId); return { authenticated: true, userId: session.userId, session, authRequired: true }; +} + +export function getAuthRequirementForContext(context: AuthContextType): boolean { + return getAuthRequirement(context); } \ No newline at end of file