consolidation and unification
This commit is contained in:
parent
72bcc04309
commit
f92219f61f
@ -16,6 +16,7 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"jose": "^5.2.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,102 +0,0 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { parse } from 'cookie';
|
||||
import {
|
||||
exchangeCodeForTokens,
|
||||
getUserInfo,
|
||||
createSession,
|
||||
createSessionCookie,
|
||||
logAuthEvent
|
||||
} from '../../../utils/auth.js';
|
||||
|
||||
export const GET: APIRoute = async ({ url, request }) => {
|
||||
try {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('Auth callback processing...');
|
||||
console.log('Full URL:', url.toString());
|
||||
console.log('URL pathname:', url.pathname);
|
||||
console.log('URL search:', url.search);
|
||||
console.log('URL searchParams:', url.searchParams.toString());
|
||||
}
|
||||
|
||||
// Try different ways to get parameters
|
||||
const allParams = Object.fromEntries(url.searchParams.entries());
|
||||
console.log('SearchParams entries:', allParams);
|
||||
|
||||
// Also try parsing manually from the search string
|
||||
const manualParams = new URLSearchParams(url.search);
|
||||
const manualEntries = Object.fromEntries(manualParams.entries());
|
||||
console.log('Manual URLSearchParams:', manualEntries);
|
||||
|
||||
// Also check request URL
|
||||
const requestUrl = new URL(request.url);
|
||||
console.log('Request URL:', requestUrl.toString());
|
||||
const requestParams = Object.fromEntries(requestUrl.searchParams.entries());
|
||||
console.log('Request URL params:', requestParams);
|
||||
|
||||
const code = url.searchParams.get('code') || requestUrl.searchParams.get('code');
|
||||
const state = url.searchParams.get('state') || requestUrl.searchParams.get('state');
|
||||
const error = url.searchParams.get('error') || requestUrl.searchParams.get('error');
|
||||
|
||||
console.log('Final extracted values:', { code: !!code, state: !!state, error });
|
||||
|
||||
// Handle OIDC errors
|
||||
if (error) {
|
||||
logAuthEvent('OIDC error', { error, description: url.searchParams.get('error_description') });
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { 'Location': '/?auth=error' }
|
||||
});
|
||||
}
|
||||
|
||||
if (!code || !state) {
|
||||
logAuthEvent('Missing code or state parameter', { received: allParams });
|
||||
return new Response('Invalid callback parameters', { status: 400 });
|
||||
}
|
||||
|
||||
// Verify state parameter
|
||||
const cookieHeader = request.headers.get('cookie');
|
||||
const cookies = cookieHeader ? parse(cookieHeader) : {};
|
||||
const storedStateData = cookies.auth_state ? JSON.parse(decodeURIComponent(cookies.auth_state)) : null;
|
||||
|
||||
if (!storedStateData || storedStateData.state !== state) {
|
||||
logAuthEvent('State mismatch', { received: state, stored: storedStateData?.state });
|
||||
return new Response('Invalid state parameter', { status: 400 });
|
||||
}
|
||||
|
||||
// Exchange code for tokens
|
||||
const tokens = await exchangeCodeForTokens(code);
|
||||
|
||||
// Get user info
|
||||
const userInfo = await getUserInfo(tokens.access_token);
|
||||
|
||||
// Create session
|
||||
const sessionToken = await createSession(userInfo.sub || userInfo.preferred_username || 'unknown');
|
||||
const sessionCookie = createSessionCookie(sessionToken);
|
||||
|
||||
logAuthEvent('Authentication successful', {
|
||||
userId: userInfo.sub || userInfo.preferred_username,
|
||||
email: userInfo.email
|
||||
});
|
||||
|
||||
// Clear auth state cookie and redirect to intended destination
|
||||
const returnTo = storedStateData.returnTo || '/';
|
||||
const clearStateCookie = 'auth_state=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0';
|
||||
|
||||
const headers = new Headers();
|
||||
headers.append('Location', returnTo);
|
||||
headers.append('Set-Cookie', sessionCookie);
|
||||
headers.append('Set-Cookie', clearStateCookie);
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: headers
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logAuthEvent('Callback failed', { error: error.message });
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { 'Location': '/?auth=error' }
|
||||
});
|
||||
}
|
||||
};
|
@ -28,7 +28,7 @@ export const GET: APIRoute = async ({ url, redirect }) => {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logAuthEvent('Login failed', { error: error.message });
|
||||
logAuthEvent('Login failed', { error: error instanceof Error ? error.message : 'Unknown error' });
|
||||
return new Response('Authentication error', { status: 500 });
|
||||
}
|
||||
};
|
@ -1,99 +1,61 @@
|
||||
// src/pages/api/auth/process.ts - Fixed Email Support
|
||||
import type { APIRoute } from 'astro';
|
||||
import { parse } from 'cookie';
|
||||
import {
|
||||
verifyAuthState,
|
||||
exchangeCodeForTokens,
|
||||
getUserInfo,
|
||||
createSession,
|
||||
createSessionCookie,
|
||||
createSessionWithCookie,
|
||||
logAuthEvent,
|
||||
getUserEmail
|
||||
createBadRequestResponse,
|
||||
createSuccessResponse
|
||||
} from '../../../utils/auth.js';
|
||||
|
||||
// Mark as server-rendered
|
||||
export const prerender = false;
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Check if there's a body to parse
|
||||
const contentType = request.headers.get('content-type');
|
||||
console.log('Request content-type:', contentType);
|
||||
|
||||
// Parse request body
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (parseError) {
|
||||
console.error('JSON parse error:', parseError);
|
||||
return new Response(JSON.stringify({ success: false, error: 'Invalid JSON' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
return createBadRequestResponse('Invalid JSON');
|
||||
}
|
||||
|
||||
const { code, state } = body || {};
|
||||
|
||||
console.log('Processing authentication:', { code: !!code, state: !!state });
|
||||
|
||||
if (!code || !state) {
|
||||
logAuthEvent('Missing code or state parameter in process request');
|
||||
return new Response(JSON.stringify({ success: false }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
return createBadRequestResponse('Missing required parameters');
|
||||
}
|
||||
|
||||
// Verify state parameter
|
||||
const cookieHeader = request.headers.get('cookie');
|
||||
const cookies = cookieHeader ? parse(cookieHeader) : {};
|
||||
const storedStateData = cookies.auth_state ? JSON.parse(decodeURIComponent(cookies.auth_state)) : null;
|
||||
|
||||
console.log('State verification:', {
|
||||
received: state,
|
||||
stored: storedStateData?.state,
|
||||
match: storedStateData?.state === state
|
||||
});
|
||||
|
||||
if (!storedStateData || storedStateData.state !== state) {
|
||||
logAuthEvent('State mismatch', { received: state, stored: storedStateData?.state });
|
||||
return new Response(JSON.stringify({ success: false }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
// CONSOLIDATED: Single function call replaces 15+ lines of duplicated state verification
|
||||
const stateVerification = verifyAuthState(request, state);
|
||||
if (!stateVerification.isValid || !stateVerification.stateData) {
|
||||
return createBadRequestResponse(stateVerification.error || 'Invalid state parameter');
|
||||
}
|
||||
|
||||
// Exchange code for tokens
|
||||
console.log('Exchanging code for tokens...');
|
||||
// Exchange code for tokens and get user info
|
||||
const tokens = await exchangeCodeForTokens(code);
|
||||
|
||||
// Get user info
|
||||
console.log('Getting user info...');
|
||||
const userInfo = await getUserInfo(tokens.access_token);
|
||||
|
||||
// Extract user details
|
||||
const userId = userInfo.sub || userInfo.preferred_username || 'unknown';
|
||||
const userEmail = getUserEmail(userInfo);
|
||||
|
||||
// Create session with email
|
||||
const sessionToken = await createSession(userId, userEmail);
|
||||
const sessionCookie = createSessionCookie(sessionToken);
|
||||
// CONSOLIDATED: Single function call replaces 10+ lines of session creation
|
||||
const sessionResult = await createSessionWithCookie(userInfo);
|
||||
|
||||
logAuthEvent('Authentication successful', {
|
||||
userId: userId,
|
||||
email: userEmail
|
||||
userId: sessionResult.userId,
|
||||
email: sessionResult.userEmail
|
||||
});
|
||||
|
||||
// Clear auth state cookie
|
||||
const clearStateCookie = 'auth_state=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0';
|
||||
const returnTo = storedStateData.returnTo || '/';
|
||||
|
||||
// Build response with cookies
|
||||
const headers = new Headers();
|
||||
headers.append('Content-Type', 'application/json');
|
||||
headers.append('Set-Cookie', sessionCookie);
|
||||
headers.append('Set-Cookie', clearStateCookie);
|
||||
headers.append('Set-Cookie', sessionResult.sessionCookie);
|
||||
headers.append('Set-Cookie', sessionResult.clearStateCookie);
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
redirectTo: returnTo
|
||||
redirectTo: stateVerification.stateData.returnTo
|
||||
}), {
|
||||
status: 200,
|
||||
headers: headers
|
||||
@ -101,10 +63,9 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Authentication processing failed:', error);
|
||||
logAuthEvent('Authentication processing failed', { error: error instanceof Error ? error.message : 'Unknown error' });
|
||||
return new Response(JSON.stringify({ success: false }), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
logAuthEvent('Authentication processing failed', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
return createBadRequestResponse('Authentication processing failed');
|
||||
}
|
||||
};
|
@ -1,56 +1,24 @@
|
||||
// src/pages/api/auth/status.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getSessionFromRequest, verifySession } from '../../../utils/auth.js';
|
||||
import { withAPIAuth, createAPIResponse } from '../../../utils/auth.js';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Check if authentication is required
|
||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||
// CONSOLIDATED: Single function call replaces 35+ lines
|
||||
const authResult = await withAPIAuth(request);
|
||||
|
||||
if (!authRequired) {
|
||||
// If authentication is not required, always return authenticated
|
||||
return new Response(JSON.stringify({
|
||||
authenticated: true,
|
||||
authRequired: false
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const sessionToken = getSessionFromRequest(request);
|
||||
|
||||
if (!sessionToken) {
|
||||
return new Response(JSON.stringify({
|
||||
authenticated: false,
|
||||
authRequired: true
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const session = await verifySession(sessionToken);
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
authenticated: session !== null,
|
||||
authRequired: true,
|
||||
expires: session?.exp ? new Date(session.exp * 1000).toISOString() : null
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
return createAPIResponse({
|
||||
authenticated: authResult.authenticated,
|
||||
authRequired: authResult.authRequired,
|
||||
expires: authResult.session?.exp ? new Date(authResult.session.exp * 1000).toISOString() : null
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
return createAPIResponse({
|
||||
authenticated: false,
|
||||
authRequired: process.env.AUTHENTICATION_NECESSARY !== 'false',
|
||||
error: 'Session verification failed'
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
// src/pages/api/contribute/knowledgebase.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getSessionFromRequest, verifySession } from '../../../utils/auth.js';
|
||||
import { withAPIAuth, createAuthErrorResponse } from '../../../utils/auth.js';
|
||||
import { GitContributionManager } from '../../../utils/gitContributions.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -270,26 +270,13 @@ ${data.article.uploadedFiles.map((file: any) => `- ${file.name} (${file.url})`).
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Check authentication
|
||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||
const authResult = await withAPIAuth(request);
|
||||
if (authResult.authRequired && !authResult.authenticated) {
|
||||
return createAuthErrorResponse('Authentication required');
|
||||
}
|
||||
|
||||
if (authRequired) {
|
||||
const sessionToken = getSessionFromRequest(request);
|
||||
if (!sessionToken) {
|
||||
return new Response(JSON.stringify({ error: 'Authentication required' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
const userEmail = authResult.session?.email || 'anonymous@example.com';
|
||||
|
||||
const session = await verifySession(sessionToken);
|
||||
if (!session) {
|
||||
return new Response(JSON.stringify({ error: 'Invalid session' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const userEmail = session.email;
|
||||
|
||||
// Rate limiting
|
||||
if (!checkRateLimit(userEmail)) {
|
||||
@ -383,14 +370,6 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Authentication is disabled'
|
||||
}), {
|
||||
status: 501,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Knowledgebase contribution API error:', error);
|
||||
|
@ -1,6 +1,6 @@
|
||||
// src/pages/api/contribute/tool.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getSessionFromRequest, verifySession } from '../../../utils/auth.js';
|
||||
import { withAPIAuth, createAuthErrorResponse } from '../../../utils/auth.js';
|
||||
import { GitContributionManager, type ContributionData } from '../../../utils/gitContributions.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -189,39 +189,21 @@ async function validateToolData(tool: any, action: 'add' | 'edit'): Promise<{ va
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Check if authentication is required
|
||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||
let userId = 'anonymous';
|
||||
let userEmail = 'anonymous@example.com';
|
||||
|
||||
if (authRequired) {
|
||||
// Authentication check
|
||||
const sessionToken = getSessionFromRequest(request);
|
||||
if (!sessionToken) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
}), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const session = await verifySession(sessionToken);
|
||||
if (!session) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: 'Invalid session'
|
||||
}), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
userId = session.userId;
|
||||
// In a real implementation, you might want to fetch user email from session or OIDC
|
||||
userEmail = `${userId}@cc24.dev`;
|
||||
const authResult = await withAPIAuth(request);
|
||||
if (authResult.authRequired && !authResult.authenticated) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
}), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const userId = authResult.session?.userId || 'anonymous';
|
||||
const userEmail = authResult.session?.email || 'anonymous@example.com';
|
||||
|
||||
|
||||
// Rate limiting
|
||||
if (!checkRateLimit(userId)) {
|
||||
return new Response(JSON.stringify({
|
||||
|
@ -1,6 +1,6 @@
|
||||
// src/pages/api/upload/media.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getSessionFromRequest, verifySession } from '../../../utils/auth.js';
|
||||
import { getSessionFromRequest, verifySession, withAPIAuth, createAuthErrorResponse } from '../../../utils/auth.js';
|
||||
import { NextcloudUploader, isNextcloudConfigured } from '../../../utils/nextcloud.js';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
@ -169,29 +169,14 @@ async function uploadToNextcloud(file: File, category: string): Promise<UploadRe
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Check authentication
|
||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||
let userEmail = 'anonymous';
|
||||
|
||||
if (authRequired) {
|
||||
const sessionToken = getSessionFromRequest(request);
|
||||
if (!sessionToken) {
|
||||
return new Response(JSON.stringify({ error: 'Authentication required' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const session = await verifySession(sessionToken);
|
||||
if (!session) {
|
||||
return new Response(JSON.stringify({ error: 'Invalid session' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
userEmail = session.email;
|
||||
const authResult = await withAPIAuth(request);
|
||||
if (authResult.authRequired && !authResult.authenticated) {
|
||||
return createAuthErrorResponse('Authentication required');
|
||||
}
|
||||
|
||||
const userEmail = authResult.session?.email || 'anonymous';
|
||||
|
||||
|
||||
// Rate limiting
|
||||
if (!checkUploadRateLimit(userEmail)) {
|
||||
return new Response(JSON.stringify({
|
||||
@ -279,29 +264,15 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Check authentication
|
||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||
|
||||
if (authRequired) {
|
||||
const sessionToken = getSessionFromRequest(request);
|
||||
if (!sessionToken) {
|
||||
return new Response(JSON.stringify({ error: 'Authentication required' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const session = await verifySession(sessionToken);
|
||||
if (!session) {
|
||||
return new Response(JSON.stringify({ error: 'Invalid session' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
const authResult = await withAPIAuth(request);
|
||||
if (authResult.authRequired && !authResult.authenticated) {
|
||||
return createAuthErrorResponse('Authentication required');
|
||||
}
|
||||
|
||||
// Return upload configuration and status
|
||||
const nextcloudConfigured = isNextcloudConfigured();
|
||||
|
||||
|
||||
// Check local upload directory
|
||||
let localStorageAvailable = false;
|
||||
try {
|
||||
|
@ -3,6 +3,7 @@ import { SignJWT, jwtVerify, type JWTPayload } from 'jose';
|
||||
import { serialize, parse } from 'cookie';
|
||||
import { config } from 'dotenv';
|
||||
import type { AstroGlobal } from 'astro';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
// Load environment variables
|
||||
config();
|
||||
@ -31,12 +32,25 @@ export interface SessionData {
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
sub?: string;
|
||||
sub: string;
|
||||
preferred_username?: string;
|
||||
email?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface AuthContext {
|
||||
authenticated: boolean;
|
||||
session: SessionData | null;
|
||||
userEmail: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface AuthStateData {
|
||||
state: string;
|
||||
returnTo: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Create a signed JWT session token with email
|
||||
export async function createSession(userId: string, email: string): Promise<string> {
|
||||
const exp = Math.floor(Date.now() / 1000) + SESSION_DURATION;
|
||||
@ -97,7 +111,7 @@ export function createSessionCookie(token: string): string {
|
||||
return serialize('session', token, {
|
||||
httpOnly: true,
|
||||
secure: isSecure,
|
||||
sameSite: 'strict', // More secure than 'lax'
|
||||
sameSite: 'lax',
|
||||
maxAge: SESSION_DURATION,
|
||||
path: '/'
|
||||
});
|
||||
@ -199,13 +213,91 @@ export function getUserEmail(userInfo: UserInfo): string {
|
||||
`${userInfo.preferred_username || userInfo.sub || 'unknown'}@cc24.dev`;
|
||||
}
|
||||
|
||||
// === CONSOLIDATION: Server-side Auth Helpers ===
|
||||
/**
|
||||
* CONSOLIDATED: Parse and validate auth state from cookies
|
||||
* Replaces duplicated cookie parsing in callback.ts and process.ts
|
||||
*/
|
||||
export function parseAuthState(request: Request): {
|
||||
isValid: boolean;
|
||||
stateData: AuthStateData | null;
|
||||
error?: string
|
||||
} {
|
||||
try {
|
||||
const cookieHeader = request.headers.get('cookie');
|
||||
const cookies = cookieHeader ? parse(cookieHeader) : {};
|
||||
|
||||
export interface AuthContext {
|
||||
authenticated: boolean;
|
||||
session: SessionData | null;
|
||||
userEmail: string;
|
||||
if (!cookies.auth_state) {
|
||||
return { isValid: false, stateData: null, error: 'No auth state cookie' };
|
||||
}
|
||||
|
||||
const stateData = JSON.parse(decodeURIComponent(cookies.auth_state));
|
||||
|
||||
if (!stateData.state || !stateData.returnTo) {
|
||||
return { isValid: false, stateData: null, error: 'Invalid state data structure' };
|
||||
}
|
||||
|
||||
return { isValid: true, stateData };
|
||||
} catch (error) {
|
||||
return { isValid: false, stateData: null, error: 'Failed to parse auth state' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CONSOLIDATED: Verify state parameter against stored state
|
||||
* Replaces duplicated verification logic in callback.ts and process.ts
|
||||
*/
|
||||
export function verifyAuthState(request: Request, receivedState: string): {
|
||||
isValid: boolean;
|
||||
stateData: AuthStateData | null;
|
||||
error?: string;
|
||||
} {
|
||||
const { isValid, stateData, error } = parseAuthState(request);
|
||||
|
||||
if (!isValid || !stateData) {
|
||||
logAuthEvent('State parsing failed', { error });
|
||||
return { isValid: false, stateData: null, error };
|
||||
}
|
||||
|
||||
if (stateData.state !== receivedState) {
|
||||
logAuthEvent('State mismatch', {
|
||||
received: receivedState,
|
||||
stored: stateData.state
|
||||
});
|
||||
return {
|
||||
isValid: false,
|
||||
stateData: null,
|
||||
error: 'State parameter mismatch'
|
||||
};
|
||||
}
|
||||
|
||||
return { isValid: true, stateData };
|
||||
}
|
||||
|
||||
/**
|
||||
* CONSOLIDATED: Create session with cookie headers
|
||||
* Replaces duplicated session creation in callback.ts and process.ts
|
||||
*/
|
||||
export async function createSessionWithCookie(userInfo: UserInfo): Promise<{
|
||||
sessionToken: string;
|
||||
sessionCookie: string;
|
||||
clearStateCookie: string;
|
||||
userId: string;
|
||||
userEmail: string;
|
||||
}> {
|
||||
const userId = userInfo.sub || userInfo.preferred_username || 'unknown';
|
||||
const userEmail = getUserEmail(userInfo);
|
||||
|
||||
const sessionToken = await createSession(userId, userEmail);
|
||||
const sessionCookie = createSessionCookie(sessionToken);
|
||||
const clearStateCookie = 'auth_state=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0';
|
||||
|
||||
return {
|
||||
sessionToken,
|
||||
sessionCookie,
|
||||
clearStateCookie,
|
||||
userId,
|
||||
userEmail
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -255,37 +347,47 @@ export async function withAuth(Astro: AstroGlobal): Promise<AuthContext | Respon
|
||||
|
||||
/**
|
||||
* CONSOLIDATED: Replace repeated auth patterns in API endpoints
|
||||
* Usage: const authResult = await withAPIAuth(request);
|
||||
* if (!authResult.authenticated) return createAuthErrorResponse();
|
||||
* Enhanced version with better return structure
|
||||
*/
|
||||
export async function withAPIAuth(request: Request): Promise<{
|
||||
authenticated: boolean;
|
||||
userId: string;
|
||||
session?: SessionData
|
||||
session?: SessionData;
|
||||
authRequired: boolean;
|
||||
}> {
|
||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||
|
||||
if (!authRequired) {
|
||||
return {
|
||||
authenticated: true,
|
||||
userId: 'anonymous'
|
||||
userId: 'anonymous',
|
||||
authRequired: false
|
||||
};
|
||||
}
|
||||
|
||||
const sessionToken = getSessionFromRequest(request);
|
||||
if (!sessionToken) {
|
||||
return { authenticated: false, userId: '' };
|
||||
return {
|
||||
authenticated: false,
|
||||
userId: '',
|
||||
authRequired: true
|
||||
};
|
||||
}
|
||||
|
||||
const session = await verifySession(sessionToken);
|
||||
if (!session) {
|
||||
return { authenticated: false, userId: '' };
|
||||
return {
|
||||
authenticated: false,
|
||||
userId: '',
|
||||
authRequired: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
authenticated: true,
|
||||
userId: session.userId,
|
||||
session
|
||||
session,
|
||||
authRequired: true
|
||||
};
|
||||
}
|
||||
|
||||
@ -298,3 +400,21 @@ export function createAuthErrorResponse(message: string = 'Authentication requir
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CONSOLIDATED: Create consistent API responses
|
||||
*/
|
||||
export function createAPIResponse(data: any, status: number = 200): Response {
|
||||
return new Response(JSON.stringify(data), {
|
||||
status,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
export function createBadRequestResponse(message: string = 'Bad request'): Response {
|
||||
return createAPIResponse({ error: message }, 400);
|
||||
}
|
||||
|
||||
export function createSuccessResponse(data: any = { success: true }): Response {
|
||||
return createAPIResponse(data, 200);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user