gatedcontent
This commit is contained in:
parent
d49b031eb9
commit
a52c0781e1
@ -32,8 +32,8 @@ AUTHENTICATION_NECESSARY_CONTRIBUTIONS=false
|
||||
AUTHENTICATION_NECESSARY_AI=false
|
||||
AUTHENTICATION_NECESSARY_GATEDCONTENT=true
|
||||
|
||||
# OIDC Provider Configuration
|
||||
OIDC_ENDPOINT=https://your-nextcloud.com/index.php/apps/oidc
|
||||
# OIDC Provider Configuration - Server appends endpoint (e.g. auth/callback) automatically
|
||||
OIDC_ENDPOINT=https://cloud.cc24.dev/index.php
|
||||
OIDC_CLIENT_ID=your-client-id
|
||||
OIDC_CLIENT_SECRET=your-client-secret
|
||||
|
||||
|
@ -229,6 +229,12 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
||||
authRequired: data.aiAuthRequired,
|
||||
expires: data.expires
|
||||
};
|
||||
case 'gatedcontent': // ADD THIS CASE
|
||||
return {
|
||||
authenticated: data.gatedContentAuthenticated,
|
||||
authRequired: data.gatedContentAuthRequired,
|
||||
expires: data.expires
|
||||
};
|
||||
default:
|
||||
return {
|
||||
authenticated: data.authenticated,
|
||||
|
@ -44,16 +44,12 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
});
|
||||
|
||||
const responseHeaders = new Headers();
|
||||
responseHeaders.set('Content-Type', 'application/json');
|
||||
|
||||
responseHeaders.set('Location', stateVerification.stateData.returnTo);
|
||||
responseHeaders.append('Set-Cookie', sessionResult.sessionCookie);
|
||||
responseHeaders.append('Set-Cookie', sessionResult.clearStateCookie);
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
success: true,
|
||||
redirectTo: stateVerification.stateData.returnTo
|
||||
}), {
|
||||
status: 200,
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: responseHeaders
|
||||
});
|
||||
|
||||
|
@ -174,7 +174,9 @@ const publicCount = knowledgebaseEntries.length - gatedCount;
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 flex-shrink-0" onclick="event.stopPropagation();">
|
||||
<a href={articleUrl} class="btn btn-primary btn-sm">
|
||||
<a href={articleUrl}
|
||||
class="btn btn-primary btn-sm"
|
||||
title={isGated && isGatedContentAuthRequired() ? "Geschützter Inhalt - Anmeldung erforderlich" : "Artikel öffnen"}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.375rem;">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
|
@ -68,6 +68,19 @@ const currentUrl = Astro.url.href;
|
||||
|
||||
console.log('[GATED CONTENT] Checking client-side auth for: ' + articleTitle);
|
||||
|
||||
// Hide content immediately while checking auth
|
||||
const contentArea = document.querySelector('.article-content');
|
||||
const sidebar = document.querySelector('.article-sidebar');
|
||||
|
||||
|
||||
if (contentArea) {
|
||||
contentArea.style.display = 'none';
|
||||
}
|
||||
// DON'T hide the sidebar container - just prevent TOC generation
|
||||
//if (sidebar) {
|
||||
//sidebar.innerHTML = ''; // Clear any content instead of hiding
|
||||
//}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/status');
|
||||
const authStatus = await response.json();
|
||||
@ -78,44 +91,64 @@ const currentUrl = Astro.url.href;
|
||||
console.log('[GATED CONTENT] Auth status - Required: ' + authRequired + ', Authenticated: ' + isAuthenticated);
|
||||
|
||||
if (authRequired && !isAuthenticated) {
|
||||
console.log('[GATED CONTENT] Redirecting for authentication: ' + articleTitle);
|
||||
console.log('[GATED CONTENT] Access denied - showing auth required message: ' + articleTitle);
|
||||
|
||||
// Show loading message briefly
|
||||
const contentArea = document.querySelector('.article-content');
|
||||
// Show authentication required message (no auto-redirect)
|
||||
if (contentArea) {
|
||||
const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href);
|
||||
contentArea.innerHTML = [
|
||||
'<div style="text-align: center; padding: 3rem;">',
|
||||
'<div style="font-size: 3rem; margin-bottom: 1rem;">🔒</div>',
|
||||
'<h3 style="margin-bottom: 1rem;">Authentifizierung erforderlich</h3>',
|
||||
'<p style="margin-bottom: 2rem;">Sie werden zur Anmeldung weitergeleitet...</p>',
|
||||
'<div class="gated-content-block">',
|
||||
'<div class="gated-icon">🔒</div>',
|
||||
'<h3 class="gated-title">Authentifizierung erforderlich</h3>',
|
||||
'<p class="gated-description">Dieser Artikel enthält geschützte Inhalte und ist nur für authentifizierte Benutzer zugänglich.</p>',
|
||||
'<div class="gated-actions">',
|
||||
'<a href="' + loginUrl + '" class="btn btn-primary">',
|
||||
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">',
|
||||
'<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>',
|
||||
'<polyline points="10 17 15 12 10 7"/>',
|
||||
'<line x1="15" y1="12" x2="3" y2="12"/>',
|
||||
'</svg>',
|
||||
'Anmelden',
|
||||
'</a>',
|
||||
'<a href="/knowledgebase" class="btn btn-secondary">Zurück zur Übersicht</a>',
|
||||
'</div>',
|
||||
'<div class="gated-help">',
|
||||
'<small>Nach der Anmeldung werden Sie automatisch zu diesem Artikel zurückgeleitet.</small>',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
contentArea.style.display = 'block';
|
||||
}
|
||||
|
||||
// Redirect to login after brief delay
|
||||
setTimeout(() => {
|
||||
const currentUrl = encodeURIComponent(window.location.href);
|
||||
window.location.href = '/api/auth/login?returnTo=' + currentUrl;
|
||||
}, 1000);
|
||||
} else {
|
||||
console.log('[GATED CONTENT] Access granted for: ' + articleTitle);
|
||||
// Show content for authenticated users
|
||||
if (contentArea) {
|
||||
contentArea.style.display = 'block';
|
||||
}
|
||||
// Let TOC generate normally for authenticated users
|
||||
setTimeout(() => {
|
||||
if (typeof generateTOCContent === 'function') {
|
||||
generateTOCContent();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[GATED CONTENT] Auth check failed:', error);
|
||||
// On error, show auth required message
|
||||
if (requiresAuth) {
|
||||
const contentArea = document.querySelector('.article-content');
|
||||
if (contentArea) {
|
||||
const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href);
|
||||
contentArea.innerHTML = [
|
||||
'<div style="text-align: center; padding: 3rem;">',
|
||||
'<div style="font-size: 3rem; margin-bottom: 1rem;">⚠️</div>',
|
||||
'<h3 style="margin-bottom: 1rem;">Authentifizierungsfehler</h3>',
|
||||
'<p style="margin-bottom: 2rem;">Bitte versuchen Sie es später erneut oder melden Sie sich an.</p>',
|
||||
if (requiresAuth && contentArea) {
|
||||
const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href);
|
||||
contentArea.innerHTML = [
|
||||
'<div class="gated-content-block">',
|
||||
'<div class="gated-icon error">⚠️</div>',
|
||||
'<h3 class="gated-title">Authentifizierungsfehler</h3>',
|
||||
'<p class="gated-description">Es gab ein Problem bei der Überprüfung Ihrer Berechtigung. Bitte versuchen Sie es erneut.</p>',
|
||||
'<div class="gated-actions">',
|
||||
'<a href="' + loginUrl + '" class="btn btn-primary">Anmelden</a>',
|
||||
'</div>'
|
||||
].join('');
|
||||
}
|
||||
'<button onclick="location.reload()" class="btn btn-secondary">Seite neu laden</button>',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
contentArea.style.display = 'block';
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -245,7 +278,7 @@ const currentUrl = Astro.url.href;
|
||||
|
||||
<!-- Article Content -->
|
||||
<main class="article-main">
|
||||
<article class="article-content">
|
||||
<article class="article-content" style={requiresAuth ? "display: none;" : ""}>
|
||||
<div class="markdown-content">
|
||||
<Content />
|
||||
</div>
|
||||
@ -337,7 +370,7 @@ const currentUrl = Astro.url.href;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script define:vars={{ requiresAuth }}>
|
||||
/** @template {Element} T
|
||||
* @param {string} sel
|
||||
* @param {Document|Element} [root=document]
|
||||
@ -369,6 +402,33 @@ const currentUrl = Astro.url.href;
|
||||
}
|
||||
|
||||
function generateSidebarTOC() {
|
||||
// NEW: Don't generate TOC for gated content that requires auth
|
||||
if (requiresAuth) {
|
||||
fetch('/api/auth/status')
|
||||
.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;
|
||||
}
|
||||
|
||||
// For non-gated content, generate TOC normally
|
||||
generateTOCContent();
|
||||
}
|
||||
|
||||
function generateTOCContent() {
|
||||
/** @type {HTMLElement|null} */
|
||||
const article = qs('.markdown-content');
|
||||
/** @type {HTMLElement|null} */
|
||||
@ -582,5 +642,70 @@ const currentUrl = Astro.url.href;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.gated-content-block {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gated-icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1.5rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.gated-icon.error {
|
||||
color: var(--color-error, #dc3545);
|
||||
}
|
||||
|
||||
.gated-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.gated-description {
|
||||
font-size: 1rem;
|
||||
color: var(--color-text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.gated-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.gated-help {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.gated-help small {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 640px) {
|
||||
.gated-content-block {
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
.gated-actions {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.gated-actions .btn {
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</BaseLayout>
|
@ -511,14 +511,14 @@
|
||||
/* Content wrapper with sidebar */
|
||||
.article-content-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
grid-template-columns: var(--main-grid-columns, 280px 1fr);
|
||||
gap: 3rem;
|
||||
align-items: start;
|
||||
}
|
||||
.article-sidebar {
|
||||
position: sticky;
|
||||
top: 6rem;
|
||||
max-height: calc(100vh - 8rem);
|
||||
top: 0.1rem;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@ -567,19 +567,44 @@
|
||||
.toc-level-6 { padding-left: 3rem; font-size: 0.8125rem; opacity: 0.8; }
|
||||
|
||||
/* Main article column */
|
||||
.article-main { min-width: 0; max-width: 95ch; }
|
||||
.article-content { margin-bottom: 3rem; }
|
||||
|
||||
/* Footer */
|
||||
.article-footer {
|
||||
border-top: 2px solid var(--color-border);
|
||||
padding-top: 2rem;
|
||||
margin-top: 3rem;
|
||||
.article-main {
|
||||
min-width: 0;
|
||||
max-width: 95ch;
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.article-footer h3 { margin: 0 0 1.5rem 0; color: var(--color-primary); }
|
||||
.footer-actions-grid { display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem; }
|
||||
.footer-actions-grid .btn { flex: 1; min-width: 200px; }
|
||||
|
||||
.article-content {
|
||||
padding: 2.5rem;
|
||||
margin-bottom: 0;
|
||||
background: linear-gradient(135deg, var(--color-bg) 0%, var(--color-bg-secondary) 100%);
|
||||
}
|
||||
.article-footer {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: 2rem 2.5rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
.article-footer h3 {
|
||||
margin: 0 0 1.5rem 0;
|
||||
color: var(--color-primary);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.footer-actions-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.footer-actions-grid .btn {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
justify-content: center;
|
||||
}
|
||||
/* Tool button variants (keep since template uses them) */
|
||||
.btn-concept { background-color: var(--color-concept); color: white; border-color: var(--color-concept); }
|
||||
.btn-concept:hover { opacity: 0.9; }
|
||||
@ -628,6 +653,10 @@
|
||||
.article-actions { flex-direction: column; }
|
||||
.footer-actions-grid { flex-direction: column; }
|
||||
.footer-actions-grid .btn { min-width: auto; }
|
||||
.article-content { padding: 1.5rem; }
|
||||
.article-footer { padding: 1.5rem; }
|
||||
.footer-actions-grid { flex-direction: column; }
|
||||
.footer-actions-grid .btn { min-width: auto; }
|
||||
|
||||
:where(.markdown-content) {
|
||||
font-size: 1rem;
|
||||
|
Loading…
x
Reference in New Issue
Block a user