consolidation of auth mechanism
This commit is contained in:
parent
32fca8a06f
commit
72bcc04309
@ -19,7 +19,13 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital
|
||||
<meta name="description" content={description}>
|
||||
<title>{title} - CC24-Guide</title>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||
|
||||
<!-- CONSOLIDATED: Load theme script -->
|
||||
<script src="/src/scripts/theme.js"></script>
|
||||
|
||||
<!-- CONSOLIDATED: Load client-side auth utilities -->
|
||||
<script src="/src/scripts/client-auth.js"></script>
|
||||
|
||||
<script>
|
||||
// Initialize theme immediately to prevent flash
|
||||
(window as any).themeUtils?.initTheme();
|
||||
|
@ -6,7 +6,6 @@ import ToolMatrix from '../components/ToolMatrix.astro';
|
||||
import AIQueryInterface from '../components/AIQueryInterface.astro';
|
||||
import { getToolsData } from '../utils/dataService.js';
|
||||
|
||||
|
||||
// Load tools data
|
||||
const data = await getToolsData();
|
||||
const tools = data.tools;
|
||||
@ -53,8 +52,8 @@ const tools = data.tools;
|
||||
KI befragen
|
||||
</button>
|
||||
|
||||
<!-- NEW: Contribution Button -->
|
||||
<a href="/contribute" class="btn" style="padding: 0.75rem 1.5rem; background-color: var(--color-warning); color: white; border-color: var(--color-warning);" data-contribute-button="new">
|
||||
<!-- Contribution Button - FIXED: Use data-contribute-button -->
|
||||
<a href="/contribute" class="btn" style="padding: 0.75rem 1.5rem; background-color: var(--color-warning); color: white; border-color: var(--color-warning);" data-contribute-button>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="8.5" cy="7" r="4"/>
|
||||
@ -78,13 +77,12 @@ const tools = data.tools;
|
||||
|
||||
<!-- Filters Section -->
|
||||
<section id="filters-section" style="padding: 2rem 0;">
|
||||
<ToolFilters />
|
||||
<ToolFilters data={data} />
|
||||
</section>
|
||||
|
||||
<!-- AI Query Interface -->
|
||||
<AIQueryInterface />
|
||||
|
||||
|
||||
<!-- Tools Grid -->
|
||||
<section id="tools-grid" style="padding-bottom: 2rem;">
|
||||
<div class="grid-auto-fit" id="tools-container">
|
||||
@ -100,36 +98,22 @@ const tools = data.tools;
|
||||
</section>
|
||||
|
||||
<!-- Matrix View -->
|
||||
<ToolMatrix />
|
||||
<ToolMatrix data={data} />
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
// Extend Window interface for custom properties
|
||||
declare global {
|
||||
interface Window {
|
||||
toolsData: any[];
|
||||
showToolDetails: (toolName: string, modalType?: string) => void;
|
||||
hideToolDetails: (modalType?: string) => void;
|
||||
hideAllToolDetails: () => void;
|
||||
clearAllFilters?: () => void;
|
||||
restoreAIResults?: () => void;
|
||||
switchToAIView?: () => void;
|
||||
showShareDialog: (shareButton: HTMLElement) => void;
|
||||
navigateToGrid: (toolName: string) => void;
|
||||
navigateToMatrix: (toolName: string) => void;
|
||||
}
|
||||
}
|
||||
<script define:vars={{ toolsData: data.tools }}>
|
||||
// Store tools data globally
|
||||
window.toolsData = toolsData;
|
||||
|
||||
import { requireClientAuth } from '../utils/auth.js';
|
||||
// Handle view changes and filtering
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const toolsContainer = document.getElementById('tools-container') as HTMLElement;
|
||||
const toolsGrid = document.getElementById('tools-grid') as HTMLElement;
|
||||
const matrixContainer = document.getElementById('matrix-container') as HTMLElement;
|
||||
const aiInterface = document.getElementById('ai-interface') as HTMLElement;
|
||||
const filtersSection = document.getElementById('filters-section') as HTMLElement;
|
||||
const noResults = document.getElementById('no-results') as HTMLElement;
|
||||
const aiQueryBtn = document.getElementById('ai-query-btn') as HTMLButtonElement;
|
||||
const toolsContainer = document.getElementById('tools-container');
|
||||
const toolsGrid = document.getElementById('tools-grid');
|
||||
const matrixContainer = document.getElementById('matrix-container');
|
||||
const aiInterface = document.getElementById('ai-interface');
|
||||
const filtersSection = document.getElementById('filters-section');
|
||||
const noResults = document.getElementById('no-results');
|
||||
const aiQueryBtn = document.getElementById('ai-query-btn');
|
||||
|
||||
// Guard against null elements
|
||||
if (!toolsContainer || !toolsGrid || !matrixContainer || !noResults || !aiInterface || !filtersSection) {
|
||||
@ -138,7 +122,7 @@ const tools = data.tools;
|
||||
}
|
||||
|
||||
// Simple sorting function
|
||||
function sortTools(tools: any[], sortBy = 'default') {
|
||||
function sortTools(tools, sortBy = 'default') {
|
||||
const sorted = [...tools];
|
||||
|
||||
switch (sortBy) {
|
||||
@ -147,12 +131,12 @@ const tools = data.tools;
|
||||
case 'difficulty':
|
||||
const difficultyOrder = { 'novice': 0, 'beginner': 1, 'intermediate': 2, 'advanced': 3, 'expert': 4 };
|
||||
return sorted.sort((a, b) =>
|
||||
(difficultyOrder[a.skillLevel as keyof typeof difficultyOrder] || 999) - (difficultyOrder[b.skillLevel as keyof typeof difficultyOrder] || 999)
|
||||
(difficultyOrder[a.skillLevel] || 999) - (difficultyOrder[b.skillLevel] || 999)
|
||||
);
|
||||
case 'type':
|
||||
const typeOrder = { 'concept': 0, 'method': 1, 'software': 2 };
|
||||
return sorted.sort((a, b) =>
|
||||
(typeOrder[a.type as keyof typeof typeOrder] || 999) - (typeOrder[b.type as keyof typeof typeOrder] || 999)
|
||||
(typeOrder[a.type] || 999) - (typeOrder[b.type] || 999)
|
||||
);
|
||||
case 'default':
|
||||
default:
|
||||
@ -160,34 +144,22 @@ const tools = data.tools;
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication check function
|
||||
async function checkAuthentication() {
|
||||
try {
|
||||
const response = await fetch('/api/auth/status');
|
||||
const data = await response.json();
|
||||
return {
|
||||
authenticated: data.authenticated,
|
||||
authRequired: data.authRequired
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Auth check failed:', error);
|
||||
return {
|
||||
authenticated: false,
|
||||
authRequired: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// AI Query Button Handler
|
||||
// FIXED: AI Query Button Handler using global client-side auth function
|
||||
if (aiQueryBtn) {
|
||||
aiQueryBtn.addEventListener('click', async () => {
|
||||
await requireClientAuth(() => switchToView('ai'), `${window.location.pathname}?view=ai`);
|
||||
// Wait for client-side auth functions to be available
|
||||
if (typeof window.requireClientAuth === 'function') {
|
||||
await window.requireClientAuth(() => switchToView('ai'), `${window.location.pathname}?view=ai`);
|
||||
} else {
|
||||
console.error('requireClientAuth not available - client-auth.js may not be loaded');
|
||||
// Fallback - try switching anyway
|
||||
switchToView('ai');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to switch between different views
|
||||
function switchToView(view: string) {
|
||||
function switchToView(view) {
|
||||
// Hide all views first
|
||||
toolsGrid.style.display = 'none';
|
||||
matrixContainer.style.display = 'none';
|
||||
@ -209,7 +181,7 @@ const tools = data.tools;
|
||||
if (window.restoreAIResults) {
|
||||
window.restoreAIResults();
|
||||
}
|
||||
const aiInput = document.getElementById('ai-query-input') as HTMLTextAreaElement;
|
||||
const aiInput = document.getElementById('ai-query-input');
|
||||
if (aiInput) {
|
||||
setTimeout(() => aiInput.focus(), 100);
|
||||
}
|
||||
@ -243,19 +215,19 @@ const tools = data.tools;
|
||||
];
|
||||
|
||||
elements.forEach(selector => {
|
||||
const element = document.querySelector(selector) as HTMLElement;
|
||||
const element = document.querySelector(selector);
|
||||
if (element) element.style.display = 'none';
|
||||
});
|
||||
|
||||
const allInputs = filtersSection.querySelectorAll('input, select, textarea');
|
||||
allInputs.forEach(input => (input as HTMLElement).style.display = 'none');
|
||||
allInputs.forEach(input => input.style.display = 'none');
|
||||
}
|
||||
|
||||
function showFilterControls() {
|
||||
const domainPhaseContainer = document.querySelector('.domain-phase-container') as HTMLElement;
|
||||
const searchInput = document.getElementById('search-input') as HTMLElement;
|
||||
const tagCloud = document.querySelector('.tag-cloud') as HTMLElement;
|
||||
const tagHeader = document.querySelector('.tag-header') as HTMLElement;
|
||||
const domainPhaseContainer = document.querySelector('.domain-phase-container');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const tagCloud = document.querySelector('.tag-cloud');
|
||||
const tagHeader = document.querySelector('.tag-header');
|
||||
const checkboxWrappers = document.querySelectorAll('.checkbox-wrapper');
|
||||
const allInputs = filtersSection.querySelectorAll('input, select, textarea');
|
||||
|
||||
@ -264,12 +236,12 @@ const tools = data.tools;
|
||||
if (tagCloud) tagCloud.style.display = 'flex';
|
||||
if (tagHeader) tagHeader.style.display = 'flex';
|
||||
|
||||
allInputs.forEach(input => (input as HTMLElement).style.display = 'block');
|
||||
checkboxWrappers.forEach(wrapper => (wrapper as HTMLElement).style.display = 'flex');
|
||||
allInputs.forEach(input => input.style.display = 'block');
|
||||
checkboxWrappers.forEach(wrapper => wrapper.style.display = 'flex');
|
||||
}
|
||||
|
||||
// Create tool slug from name
|
||||
function createToolSlug(toolName: string): string {
|
||||
function createToolSlug(toolName) {
|
||||
return toolName.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
@ -278,15 +250,15 @@ const tools = data.tools;
|
||||
}
|
||||
|
||||
// Find tool by name or slug
|
||||
function findTool(identifier: string) {
|
||||
function findTool(identifier) {
|
||||
return window.toolsData.find(tool =>
|
||||
tool.name === identifier ||
|
||||
createToolSlug(tool.name) === identifier.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Navigation functions for sharing
|
||||
window.navigateToGrid = function(toolName: string) {
|
||||
// RESTORED: Navigation functions for sharing - EXACT ORIGINAL VERSIONS
|
||||
window.navigateToGrid = function(toolName) {
|
||||
console.log('Navigating to grid for tool:', toolName);
|
||||
|
||||
// Switch to grid view first
|
||||
@ -302,7 +274,7 @@ const tools = data.tools;
|
||||
// Wait for filters to clear and re-render
|
||||
setTimeout(() => {
|
||||
const toolCards = document.querySelectorAll('.tool-card');
|
||||
let targetCard: Element | null = null;
|
||||
let targetCard = null;
|
||||
|
||||
toolCards.forEach(card => {
|
||||
const cardTitle = card.querySelector('h3');
|
||||
@ -317,13 +289,12 @@ const tools = data.tools;
|
||||
|
||||
if (targetCard) {
|
||||
console.log('Found target card, scrolling...');
|
||||
// Cast to Element to fix TypeScript issue
|
||||
(targetCard as Element).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
(targetCard as HTMLElement).style.animation = 'highlight-flash 2s ease-out';
|
||||
targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
targetCard.style.animation = 'highlight-flash 2s ease-out';
|
||||
|
||||
setTimeout(() => {
|
||||
if (targetCard) {
|
||||
(targetCard as HTMLElement).style.animation = '';
|
||||
targetCard.style.animation = '';
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
@ -333,7 +304,7 @@ const tools = data.tools;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
window.navigateToMatrix = function(toolName: string) {
|
||||
window.navigateToMatrix = function(toolName) {
|
||||
console.log('Navigating to matrix for tool:', toolName);
|
||||
|
||||
// Switch to matrix view
|
||||
@ -342,7 +313,7 @@ const tools = data.tools;
|
||||
// Wait for view switch and matrix to render
|
||||
setTimeout(() => {
|
||||
const toolChips = document.querySelectorAll('.tool-chip');
|
||||
let firstMatch: Element | null = null;
|
||||
let firstMatch = null;
|
||||
let matchCount = 0;
|
||||
|
||||
toolChips.forEach(chip => {
|
||||
@ -350,7 +321,7 @@ const tools = data.tools;
|
||||
const chipText = chip.textContent?.replace(/📖/g, '').replace(/[^\w\s\-\.]/g, '').trim();
|
||||
if (chipText === toolName) {
|
||||
// Highlight this occurrence
|
||||
(chip as HTMLElement).style.animation = 'highlight-flash 2s ease-out';
|
||||
chip.style.animation = 'highlight-flash 2s ease-out';
|
||||
matchCount++;
|
||||
|
||||
// Remember the first match for scrolling
|
||||
@ -360,22 +331,21 @@ const tools = data.tools;
|
||||
|
||||
// Clean up animation after it completes
|
||||
setTimeout(() => {
|
||||
(chip as HTMLElement).style.animation = '';
|
||||
chip.style.animation = '';
|
||||
}, 8000);
|
||||
}
|
||||
});
|
||||
|
||||
if (firstMatch) {
|
||||
console.log(`Found ${matchCount} occurrences of tool, highlighting all and scrolling to first`);
|
||||
// Cast to Element to fix TypeScript issue
|
||||
(firstMatch as Element).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
firstMatch.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
} else {
|
||||
console.warn('Tool chip not found in matrix:', toolName);
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// Handle URL parameters on page load
|
||||
// RESTORED: Handle URL parameters on page load - EXACT ORIGINAL VERSION
|
||||
function handleSharedURL() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const toolParam = urlParams.get('tool');
|
||||
@ -425,8 +395,7 @@ const tools = data.tools;
|
||||
|
||||
// Handle filtered results
|
||||
window.addEventListener('toolsFiltered', (event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
const filtered = customEvent.detail;
|
||||
const filtered = event.detail;
|
||||
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
|
||||
|
||||
if (currentView === 'matrix' || currentView === 'ai') {
|
||||
@ -443,7 +412,7 @@ const tools = data.tools;
|
||||
|
||||
const sortedTools = sortTools(filtered, 'default');
|
||||
|
||||
sortedTools.forEach((tool: any) => {
|
||||
sortedTools.forEach((tool) => {
|
||||
const toolCard = createToolCard(tool);
|
||||
toolsContainer.appendChild(toolCard);
|
||||
});
|
||||
@ -452,8 +421,7 @@ const tools = data.tools;
|
||||
|
||||
// Handle view changes
|
||||
window.addEventListener('viewChanged', (event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
const view = customEvent.detail;
|
||||
const view = event.detail;
|
||||
switchToView(view);
|
||||
});
|
||||
|
||||
@ -461,7 +429,7 @@ const tools = data.tools;
|
||||
window.switchToAIView = () => switchToView('ai');
|
||||
|
||||
// Tool card creation function
|
||||
function createToolCard(tool: any): HTMLElement {
|
||||
function createToolCard(tool) {
|
||||
const isMethod = tool.type === 'method';
|
||||
const isConcept = tool.type === 'concept';
|
||||
const hasValidProjectUrl = tool.projectUrl !== undefined &&
|
||||
@ -545,7 +513,7 @@ const tools = data.tools;
|
||||
</div>
|
||||
|
||||
<div class="tool-tags-container">
|
||||
${(tool.tags || []).slice(0, 8).map((tag: string) => `<span class="tag">${tag}</span>`).join('')}
|
||||
${(tool.tags || []).slice(0, 8).map((tag) => `<span class="tag">${tag}</span>`).join('')}
|
||||
</div>
|
||||
|
||||
<div class="tool-card-buttons" onclick="event.stopPropagation();">
|
||||
@ -577,7 +545,7 @@ const tools = data.tools;
|
||||
return cardDiv;
|
||||
}
|
||||
|
||||
// Initialize URL handling
|
||||
// RESTORED: Initialize URL handling - EXACT ORIGINAL
|
||||
handleSharedURL();
|
||||
});
|
||||
</script>
|
@ -1,30 +0,0 @@
|
||||
// src/scripts/auth-utils.js
|
||||
export async function checkAuthAndRedirect(targetUrl) {
|
||||
try {
|
||||
const response = await fetch('/api/auth/status');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.authRequired && !data.authenticated) {
|
||||
const returnUrl = encodeURIComponent(targetUrl);
|
||||
window.location.href = `/api/auth/login?returnTo=${returnUrl}`;
|
||||
return false;
|
||||
} else {
|
||||
window.location.href = targetUrl;
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Auth check failed:', error);
|
||||
window.location.href = targetUrl; // Fallback
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function setupAuthButtons(selector = '[data-contribute-button]') {
|
||||
document.addEventListener('click', async (e) => {
|
||||
const button = e.target.closest(selector);
|
||||
if (!button) return;
|
||||
|
||||
e.preventDefault();
|
||||
await checkAuthAndRedirect(button.href);
|
||||
});
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
// src/scripts/client-auth.js - Client-side auth utilities
|
||||
// src/scripts/client-auth.js - CONSOLIDATED client-side auth utilities
|
||||
// This file REPLACES auth-utils.js and any client-side auth functions
|
||||
|
||||
/**
|
||||
* Consolidated client-side auth status check
|
||||
* Check authentication status
|
||||
*/
|
||||
async function checkClientAuth() {
|
||||
try {
|
||||
@ -30,8 +31,12 @@ async function requireClientAuth(callback, returnUrl) {
|
||||
if (authStatus.authRequired && !authStatus.authenticated) {
|
||||
const targetUrl = returnUrl || window.location.href;
|
||||
window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
|
||||
return false;
|
||||
} else {
|
||||
callback();
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +54,30 @@ async function showIfAuthenticated(selector) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle contribute button clicks with auth check
|
||||
*/
|
||||
function setupAuthButtons(selector = '[data-contribute-button]') {
|
||||
document.addEventListener('click', async (e) => {
|
||||
const button = e.target.closest(selector);
|
||||
if (!button) return;
|
||||
|
||||
e.preventDefault();
|
||||
await requireClientAuth(() => {
|
||||
window.location.href = button.href;
|
||||
}, button.href);
|
||||
});
|
||||
}
|
||||
|
||||
// Make functions available globally
|
||||
window.checkClientAuth = checkClientAuth;
|
||||
window.requireClientAuth = requireClientAuth;
|
||||
window.showIfAuthenticated = showIfAuthenticated;
|
||||
window.showIfAuthenticated = showIfAuthenticated;
|
||||
window.setupAuthButtons = setupAuthButtons;
|
||||
|
||||
// Auto-setup contribute buttons when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setupAuthButtons('[data-contribute-button]');
|
||||
});
|
||||
|
||||
console.log('Client auth utilities loaded');
|
@ -1,9 +1,8 @@
|
||||
// src/utils/auth.ts - Enhanced with Email Support
|
||||
// src/utils/auth.ts - SERVER-SIDE ONLY (remove client-side functions)
|
||||
import { SignJWT, jwtVerify, type JWTPayload } from 'jose';
|
||||
import { serialize, parse } from 'cookie';
|
||||
import { config } from 'dotenv';
|
||||
import type { AstroGlobal, APIRoute } from 'astro';
|
||||
|
||||
import type { AstroGlobal } from 'astro';
|
||||
|
||||
// Load environment variables
|
||||
config();
|
||||
@ -210,8 +209,9 @@ export interface AuthContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidated auth check for Astro pages
|
||||
* Replaces repeated auth patterns in contribute pages
|
||||
* 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';
|
||||
@ -254,10 +254,15 @@ export async function withAuth(Astro: AstroGlobal): Promise<AuthContext | Respon
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidated auth check for API endpoints
|
||||
* Replaces repeated auth patterns in API routes
|
||||
* CONSOLIDATED: Replace repeated auth patterns in API endpoints
|
||||
* Usage: const authResult = await withAPIAuth(request);
|
||||
* if (!authResult.authenticated) return createAuthErrorResponse();
|
||||
*/
|
||||
export async function withAPIAuth(request: Request): Promise<{ authenticated: boolean; userId: string; session?: SessionData }> {
|
||||
export async function withAPIAuth(request: Request): Promise<{
|
||||
authenticated: boolean;
|
||||
userId: string;
|
||||
session?: SessionData
|
||||
}> {
|
||||
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
|
||||
|
||||
if (!authRequired) {
|
||||
@ -292,50 +297,4 @@ export function createAuthErrorResponse(message: string = 'Authentication requir
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
async function checkClientAuth() {
|
||||
try {
|
||||
const response = await fetch('/api/auth/status');
|
||||
const data = await response.json();
|
||||
return {
|
||||
authenticated: data.authenticated,
|
||||
authRequired: data.authRequired,
|
||||
expires: data.expires
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Auth check failed:', error);
|
||||
return {
|
||||
authenticated: false,
|
||||
authRequired: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to login if not authenticated, otherwise execute callback
|
||||
*/
|
||||
export async function requireClientAuth(callback, returnUrl) {
|
||||
const authStatus = await checkClientAuth();
|
||||
|
||||
if (authStatus.authRequired && !authStatus.authenticated) {
|
||||
const targetUrl = returnUrl || window.location.href;
|
||||
window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/hide element based on authentication
|
||||
*/
|
||||
export async function showIfAuthenticated(selector) {
|
||||
const authStatus = await checkClientAuth();
|
||||
const element = document.querySelector(selector);
|
||||
|
||||
if (element) {
|
||||
element.style.display = (!authStatus.authRequired || authStatus.authenticated)
|
||||
? 'inline-flex'
|
||||
: 'none';
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user