auth splitting
This commit is contained in:
parent
a4f4e03cba
commit
d2fdeccce3
@ -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
|
||||
|
@ -64,7 +64,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
<line x1="12" y1="8" x2="12" y2="12"/>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
||||
</svg>
|
||||
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
|
||||
<a href="https://mistral.ai/privacy-policy/" target="_blank" rel="noopener noreferrer" style="color: var(--color-primary); text-decoration: underline;">Datenschutzrichtlinien</a>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -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();
|
||||
|
||||
switch (context) {
|
||||
case 'contributions':
|
||||
return {
|
||||
authenticated: data.authenticated,
|
||||
authRequired: data.authRequired,
|
||||
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();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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');
|
||||
};
|
@ -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)) {
|
||||
|
@ -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)) {
|
||||
|
@ -139,23 +139,19 @@ async function uploadToLocal(file: File, userType: string): Promise<UploadResult
|
||||
}
|
||||
}
|
||||
|
||||
// POST endpoint for file uploads
|
||||
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 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();
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
});
|
||||
|
@ -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<AuthContext | Response> {
|
||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||
console.log('[DEBUG PAGE] Auth required:', authRequired);
|
||||
export async function withAuth(Astro: AstroGlobal, context: AuthContextType = 'general'): Promise<AuthContext | Response> {
|
||||
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<AuthContext | Respon
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[DEBUG PAGE] Page authentication successful for user:', session.userId);
|
||||
console.log(`[DEBUG PAGE] Page authentication successful for ${context}:`, session.userId);
|
||||
return {
|
||||
authenticated: true,
|
||||
session,
|
||||
@ -346,17 +350,13 @@ export async function withAuth(Astro: AstroGlobal): Promise<AuthContext | Respon
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* CONSOLIDATED: Replace repeated auth patterns in API endpoints
|
||||
* Enhanced version with better return structure
|
||||
*/
|
||||
export async function withAPIAuth(request: Request): Promise<{
|
||||
export async function withAPIAuth(request: Request, context: AuthContextType = 'general'): Promise<{
|
||||
authenticated: boolean;
|
||||
userId: string;
|
||||
session?: SessionData;
|
||||
authRequired: boolean;
|
||||
}> {
|
||||
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,7 +390,7 @@ 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,
|
||||
@ -398,3 +398,7 @@ export async function withAPIAuth(request: Request): Promise<{
|
||||
authRequired: true
|
||||
};
|
||||
}
|
||||
|
||||
export function getAuthRequirementForContext(context: AuthContextType): boolean {
|
||||
return getAuthRequirement(context);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user