videos #17

Merged
mstoeck3 merged 7 commits from videos into main 2025-08-12 20:35:06 +00:00
3 changed files with 58 additions and 35 deletions
Showing only changes of commit e8daa37d08 - Show all commits

View File

@ -1,5 +1,7 @@
// src/pages/api/auth/login.ts (ENHANCED - Consistent cookie handling)
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
import { generateAuthUrl, generateState, logAuthEvent } from '../../../utils/auth.js'; import { generateAuthUrl, generateState, logAuthEvent } from '../../../utils/auth.js';
import { serialize } from 'cookie';
export const prerender = false; export const prerender = false;
@ -8,14 +10,28 @@ export const GET: APIRoute = async ({ url, redirect }) => {
const state = generateState(); const state = generateState();
const authUrl = generateAuthUrl(state); const authUrl = generateAuthUrl(state);
console.log('Generated auth URL:', authUrl); console.log('[AUTH] Generated auth URL:', authUrl);
const returnTo = url.searchParams.get('returnTo') || '/'; const returnTo = url.searchParams.get('returnTo') || '/';
logAuthEvent('Login initiated', { returnTo, authUrl }); logAuthEvent('Login initiated', { returnTo, authUrl });
const stateData = JSON.stringify({ state, returnTo }); const stateData = JSON.stringify({ state, returnTo });
const stateCookie = `auth_state=${encodeURIComponent(stateData)}; HttpOnly; SameSite=Lax; Path=/; Max-Age=600`;
// Use consistent cookie serialization (same as session cookies)
const publicBaseUrl = process.env.PUBLIC_BASE_URL || '';
const isProduction = process.env.NODE_ENV === 'production';
const isSecure = publicBaseUrl.startsWith('https://') || isProduction;
const stateCookie = serialize('auth_state', stateData, {
httpOnly: true,
secure: isSecure,
sameSite: 'lax',
maxAge: 600, // 10 minutes
path: '/'
});
console.log('[AUTH] Setting auth state cookie:', stateCookie.substring(0, 50) + '...');
return new Response(null, { return new Response(null, {
status: 302, status: 302,

View File

@ -1,4 +1,4 @@
// src/pages/api/auth/process.ts (FIXED - Proper cookie handling) // src/pages/api/auth/process.ts (ENHANCED - Proper auth success indication)
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
import { import {
verifyAuthState, verifyAuthState,
@ -7,7 +7,7 @@ import {
createSessionWithCookie, createSessionWithCookie,
logAuthEvent logAuthEvent
} from '../../../utils/auth.js'; } from '../../../utils/auth.js';
import { apiError, apiSpecial, apiWithHeaders, handleAPIRequest } from '../../../utils/api.js'; import { apiError, apiSpecial, handleAPIRequest } from '../../../utils/api.js';
export const prerender = false; export const prerender = false;
@ -30,9 +30,15 @@ export const POST: APIRoute = async ({ request }) => {
const stateVerification = verifyAuthState(request, state); const stateVerification = verifyAuthState(request, state);
if (!stateVerification.isValid || !stateVerification.stateData) { if (!stateVerification.isValid || !stateVerification.stateData) {
logAuthEvent('State verification failed', {
error: stateVerification.error,
hasStateData: !!stateVerification.stateData
});
return apiError.badRequest(stateVerification.error || 'Invalid state parameter'); return apiError.badRequest(stateVerification.error || 'Invalid state parameter');
} }
console.log('[AUTH] State verification successful, exchanging code for tokens');
const tokens = await exchangeCodeForTokens(code); const tokens = await exchangeCodeForTokens(code);
const userInfo = await getUserInfo(tokens.access_token); const userInfo = await getUserInfo(tokens.access_token);
@ -43,6 +49,13 @@ export const POST: APIRoute = async ({ request }) => {
email: sessionResult.userEmail email: sessionResult.userEmail
}); });
// Add auth success indicator to the return URL
const returnUrl = new URL(stateVerification.stateData.returnTo, request.url);
returnUrl.searchParams.set('auth', 'success');
const redirectUrl = returnUrl.toString();
console.log('[AUTH] Redirecting to:', redirectUrl);
const responseHeaders = new Headers(); const responseHeaders = new Headers();
responseHeaders.set('Content-Type', 'application/json'); responseHeaders.set('Content-Type', 'application/json');
@ -51,7 +64,7 @@ export const POST: APIRoute = async ({ request }) => {
return new Response(JSON.stringify({ return new Response(JSON.stringify({
success: true, success: true,
redirectTo: stateVerification.stateData.returnTo redirectTo: redirectUrl
}), { }), {
status: 200, status: 200,
headers: responseHeaders headers: responseHeaders

View File

@ -62,24 +62,33 @@ const currentUrl = Astro.url.href;
<BaseLayout title={entry.data.title} description={entry.data.description}> <BaseLayout title={entry.data.title} description={entry.data.description}>
{requiresAuth && ( {requiresAuth && (
<script define:vars={{ requiresAuth, articleTitle: entry.data.title }}> <script define:vars={{ requiresAuth, articleTitle: entry.data.title }}>
// Client-side authentication check for gated content // Enhanced client-side authentication check for gated content with improved error handling
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
if (!requiresAuth) return; if (!requiresAuth) return;
console.log('[GATED CONTENT] Checking client-side auth for: ' + articleTitle); console.log('[GATED CONTENT] Checking client-side auth for: ' + articleTitle);
// Check for auth success indicator in URL (from callback)
const urlParams = new URLSearchParams(window.location.search);
const authSuccess = urlParams.get('auth') === 'success';
// Hide content immediately while checking auth // Hide content immediately while checking auth
const contentArea = document.querySelector('.article-content'); const contentArea = document.querySelector('.article-content');
const sidebar = document.querySelector('.article-sidebar'); const sidebar = document.querySelector('.article-sidebar');
if (contentArea) { if (contentArea) {
contentArea.style.display = 'none'; contentArea.style.display = 'none';
} }
// DON'T hide the sidebar container - just prevent TOC generation
//if (sidebar) { // If this is a redirect from successful auth, wait a bit for session to be available
//sidebar.innerHTML = ''; // Clear any content instead of hiding if (authSuccess) {
//} console.log('[GATED CONTENT] Auth success detected, waiting for session...');
await new Promise(resolve => setTimeout(resolve, 1000));
// Clean the URL
const cleanUrl = window.location.protocol + "//" + window.location.host + window.location.pathname;
window.history.replaceState({}, document.title, cleanUrl);
}
try { try {
const response = await fetch('/api/auth/status'); const response = await fetch('/api/auth/status');
@ -93,7 +102,7 @@ const currentUrl = Astro.url.href;
if (authRequired && !isAuthenticated) { if (authRequired && !isAuthenticated) {
console.log('[GATED CONTENT] Access denied - showing auth required message: ' + articleTitle); console.log('[GATED CONTENT] Access denied - showing auth required message: ' + articleTitle);
// Show authentication required message (no auto-redirect) // Show authentication required message
if (contentArea) { if (contentArea) {
const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href); const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href);
contentArea.innerHTML = [ contentArea.innerHTML = [
@ -125,7 +134,7 @@ const currentUrl = Astro.url.href;
if (contentArea) { if (contentArea) {
contentArea.style.display = 'block'; contentArea.style.display = 'block';
} }
// Let TOC generate normally for authenticated users // Generate TOC for authenticated users
setTimeout(() => { setTimeout(() => {
if (typeof generateTOCContent === 'function') { if (typeof generateTOCContent === 'function') {
generateTOCContent(); generateTOCContent();
@ -134,7 +143,7 @@ const currentUrl = Astro.url.href;
} }
} catch (error) { } catch (error) {
console.error('[GATED CONTENT] Auth check failed:', error); console.error('[GATED CONTENT] Auth check failed:', error);
// On error, show auth required message // On error, show auth required message with retry option
if (requiresAuth && contentArea) { if (requiresAuth && contentArea) {
const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href); const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href);
contentArea.innerHTML = [ contentArea.innerHTML = [
@ -402,25 +411,9 @@ const currentUrl = Astro.url.href;
} }
function generateSidebarTOC() { function generateSidebarTOC() {
// NEW: Don't generate TOC for gated content that requires auth // Only generate TOC if not gated content OR user is authenticated
if (requiresAuth) { if (requiresAuth) {
fetch('/api/auth/status') // For gated content, TOC will be generated by the auth check script
.then(response => response.json())
.then(authStatus => {
const isAuthenticated = authStatus.gatedContentAuthenticated || false;
const authRequired = authStatus.gatedContentAuthRequired || false;
// Only generate TOC if user is authenticated for gated content
if (authRequired && !isAuthenticated) {
return; // Don't generate TOC
} else {
generateTOCContent(); // Generate TOC for authenticated users
}
})
.catch(() => {
// On error, don't generate TOC for gated content
return;
});
return; return;
} }
@ -614,12 +607,13 @@ const currentUrl = Astro.url.href;
}); });
} }
// keep your existing DOMContentLoaded; just ensure this is called // Make generateTOCContent available globally for the auth check script
window.generateTOCContent = generateTOCContent;
// Initialize everything on page load
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// existing:
calculateReadingTime(); calculateReadingTime();
generateSidebarTOC(); generateSidebarTOC();
// new/updated:
enhanceCodeCopy(); enhanceCodeCopy();
}); });
</script> </script>