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