try refactor auth
This commit is contained in:
		
							parent
							
								
									0ac31484d5
								
							
						
					
					
						commit
						32fca8a06f
					
				@ -14,5 +14,6 @@ export default defineConfig({
 | 
			
		||||
  server: {
 | 
			
		||||
    port: 4321,
 | 
			
		||||
    host: true
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
  },
 | 
			
		||||
  allowImportingTsExtensions: true
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -150,24 +150,12 @@ const sortedTags = Object.entries(tagFrequency)
 | 
			
		||||
    let isTagCloudExpanded = false;
 | 
			
		||||
    
 | 
			
		||||
    // Check authentication status and show/hide AI button
 | 
			
		||||
    async function checkAuthAndShowAIButton() {
 | 
			
		||||
      try {
 | 
			
		||||
        const response = await fetch('/api/auth/status');
 | 
			
		||||
        const data = await response.json();
 | 
			
		||||
        
 | 
			
		||||
        // Show AI button if authentication is not required OR if user is authenticated
 | 
			
		||||
        if (!data.authRequired || data.authenticated) {
 | 
			
		||||
          if (aiViewToggle) {
 | 
			
		||||
            aiViewToggle.style.display = 'inline-flex';
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.log('Auth check failed, AI button remains hidden');
 | 
			
		||||
      }
 | 
			
		||||
    async function initAIButton() {
 | 
			
		||||
      await showIfAuthenticated('#ai-view-toggle');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
        
 | 
			
		||||
    // Call auth check on page load
 | 
			
		||||
    checkAuthAndShowAIButton();
 | 
			
		||||
    initAIButton();
 | 
			
		||||
    
 | 
			
		||||
    // Initialize tag cloud state
 | 
			
		||||
    function initTagCloud() {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
// src/pages/api/ai/query.ts
 | 
			
		||||
import type { APIRoute } from 'astro';
 | 
			
		||||
import { getSessionFromRequest, verifySession } from '../../../utils/auth.js';
 | 
			
		||||
import { withAPIAuth, createAuthErrorResponse } from '../../../utils/auth.js';
 | 
			
		||||
import { getCompressedToolsDataForAI } from '../../../utils/dataService.js';
 | 
			
		||||
 | 
			
		||||
export const prerender = false;
 | 
			
		||||
@ -275,30 +275,13 @@ Antworte NUR mit validen JSON. Keine zusätzlichen Erklärungen außerhalb des J
 | 
			
		||||
 | 
			
		||||
export const POST: APIRoute = async ({ request }) => {
 | 
			
		||||
  try {
 | 
			
		||||
    // Check if authentication is required
 | 
			
		||||
    const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
 | 
			
		||||
    let userId = 'test-user';
 | 
			
		||||
 | 
			
		||||
    if (authRequired) {
 | 
			
		||||
      // Authentication check
 | 
			
		||||
      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' }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      userId = session.userId;
 | 
			
		||||
    // CONSOLIDATED: Replace 20+ lines with single function call
 | 
			
		||||
    const authResult = await withAPIAuth(request);
 | 
			
		||||
    if (!authResult.authenticated) {
 | 
			
		||||
      return createAuthErrorResponse();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const userId = authResult.userId;
 | 
			
		||||
 | 
			
		||||
    // Rate limiting
 | 
			
		||||
    if (!checkRateLimit(userId)) {
 | 
			
		||||
 | 
			
		||||
@ -1,30 +1,17 @@
 | 
			
		||||
---
 | 
			
		||||
// src/pages/contribute/index.astro - Updated for Phase 3
 | 
			
		||||
// src/pages/contribute/index.astro - Consolidated Auth
 | 
			
		||||
import BaseLayout from '../../layouts/BaseLayout.astro';
 | 
			
		||||
import { getSessionFromRequest, verifySession } from '../../utils/auth.js';
 | 
			
		||||
import { getAuthContext, requireAuth } from '../../utils/serverAuth.js';
 | 
			
		||||
import { withAuth } from '../../utils/auth.js'; // Note: .js extension!
 | 
			
		||||
 | 
			
		||||
export const prerender = false;
 | 
			
		||||
 | 
			
		||||
// Check authentication
 | 
			
		||||
const authRequired = import.meta.env.AUTHENTICATION_NECESSARY !== 'false';
 | 
			
		||||
let isAuthenticated = false;
 | 
			
		||||
let userEmail = '';
 | 
			
		||||
 | 
			
		||||
if (authRequired) {
 | 
			
		||||
  const sessionToken = getSessionFromRequest(Astro.request);
 | 
			
		||||
  if (sessionToken) {
 | 
			
		||||
    const session = await verifySession(sessionToken);
 | 
			
		||||
    if (session) {
 | 
			
		||||
      isAuthenticated = true;
 | 
			
		||||
      userEmail = session.email;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  const authContext = await getAuthContext(Astro);
 | 
			
		||||
  const authRedirect = requireAuth(authContext, Astro.url.toString());
 | 
			
		||||
  if (authRedirect) return authRedirect;
 | 
			
		||||
// CONSOLIDATED: Replace 15+ lines with single function call
 | 
			
		||||
const authResult = await withAuth(Astro);
 | 
			
		||||
if (authResult instanceof Response) {
 | 
			
		||||
  return authResult; // Redirect to login
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const { authenticated, userEmail, userId } = authResult;
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<BaseLayout title="Contribute" description="Contribute tools, methods, concepts, and knowledge articles to CC24-Guide">
 | 
			
		||||
 | 
			
		||||
@ -1,32 +1,19 @@
 | 
			
		||||
---
 | 
			
		||||
// src/pages/contribute/knowledgebase.astro
 | 
			
		||||
import BaseLayout from '../../layouts/BaseLayout.astro';
 | 
			
		||||
import { getSessionFromRequest, verifySession } from '../../utils/auth.js';
 | 
			
		||||
import { getAuthContext, requireAuth } from '../../utils/serverAuth.js';
 | 
			
		||||
import { withAuth } from '../../utils/auth.js';
 | 
			
		||||
import { getToolsData } from '../../utils/dataService.js';
 | 
			
		||||
 | 
			
		||||
export const prerender = false;
 | 
			
		||||
 | 
			
		||||
// Check authentication
 | 
			
		||||
const authRequired = import.meta.env.AUTHENTICATION_NECESSARY !== 'false';
 | 
			
		||||
let isAuthenticated = false;
 | 
			
		||||
let userEmail = '';
 | 
			
		||||
 | 
			
		||||
if (authRequired) {
 | 
			
		||||
  const sessionToken = getSessionFromRequest(Astro.request);
 | 
			
		||||
  if (sessionToken) {
 | 
			
		||||
    const session = await verifySession(sessionToken);
 | 
			
		||||
    if (session) {
 | 
			
		||||
      isAuthenticated = true;
 | 
			
		||||
      userEmail = session.email;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  const authContext = await getAuthContext(Astro);
 | 
			
		||||
  const authRedirect = requireAuth(authContext, Astro.url.toString());
 | 
			
		||||
  if (authRedirect) return authRedirect;
 | 
			
		||||
const authResult = await withAuth(Astro);
 | 
			
		||||
if (authResult instanceof Response) {
 | 
			
		||||
  return authResult; // Redirect to login
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const { authenticated, userEmail, userId } = authResult;
 | 
			
		||||
 | 
			
		||||
const data = await getToolsData();
 | 
			
		||||
const sortedTools = data.tools.sort((a: any, b: any) => a.name.localeCompare(b.name));
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,16 @@
 | 
			
		||||
---
 | 
			
		||||
// src/pages/contribute/tool.astro
 | 
			
		||||
import BaseLayout from '../../layouts/BaseLayout.astro';
 | 
			
		||||
import { getAuthContext, requireAuth } from '../../utils/serverAuth.js';
 | 
			
		||||
import { withAuth } from '../../utils/auth.js';
 | 
			
		||||
import { getToolsData } from '../../utils/dataService.js';
 | 
			
		||||
 | 
			
		||||
// Check authentication
 | 
			
		||||
const authContext = await getAuthContext(Astro);
 | 
			
		||||
const authRedirect = requireAuth(authContext, Astro.url.toString());
 | 
			
		||||
if (authRedirect) return authRedirect;
 | 
			
		||||
const authResult = await withAuth(Astro);
 | 
			
		||||
if (authResult instanceof Response) {
 | 
			
		||||
  return authResult; // Redirect to login
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const { authenticated, userEmail, userId } = authResult;
 | 
			
		||||
 | 
			
		||||
// Load existing data for validation and editing
 | 
			
		||||
const data = await getToolsData();
 | 
			
		||||
 | 
			
		||||
@ -119,7 +119,8 @@ const tools = data.tools;
 | 
			
		||||
      navigateToMatrix: (toolName: string) => void;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  import { requireClientAuth  } from '../utils/auth.js';
 | 
			
		||||
  // Handle view changes and filtering
 | 
			
		||||
  document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
    const toolsContainer = document.getElementById('tools-container') as HTMLElement;
 | 
			
		||||
@ -177,17 +178,11 @@ const tools = data.tools;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // AI Query Button Handler
 | 
			
		||||
    if (aiQueryBtn) {
 | 
			
		||||
      aiQueryBtn.addEventListener('click', async () => {
 | 
			
		||||
        const authStatus = await checkAuthentication();
 | 
			
		||||
        
 | 
			
		||||
        if (authStatus.authRequired && !authStatus.authenticated) {
 | 
			
		||||
          const returnUrl = `${window.location.pathname}?view=ai`;
 | 
			
		||||
          window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(returnUrl)}`;
 | 
			
		||||
        } else {
 | 
			
		||||
          switchToView('ai');
 | 
			
		||||
        }
 | 
			
		||||
        await requireClientAuth(() => switchToView('ai'), `${window.location.pathname}?view=ai`);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,8 @@
 | 
			
		||||
import { SignJWT, jwtVerify, type JWTPayload } from 'jose';
 | 
			
		||||
import { serialize, parse } from 'cookie';
 | 
			
		||||
import { config } from 'dotenv';
 | 
			
		||||
import type { AstroGlobal, APIRoute } from 'astro';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Load environment variables
 | 
			
		||||
config();
 | 
			
		||||
@ -196,4 +198,144 @@ export function logAuthEvent(event: string, details?: any) {
 | 
			
		||||
export function getUserEmail(userInfo: UserInfo): string {
 | 
			
		||||
  return userInfo.email || 
 | 
			
		||||
         `${userInfo.preferred_username || userInfo.sub || 'unknown'}@cc24.dev`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// === CONSOLIDATION: Server-side Auth Helpers ===
 | 
			
		||||
 | 
			
		||||
export interface AuthContext {
 | 
			
		||||
  authenticated: boolean;
 | 
			
		||||
  session: SessionData | null;
 | 
			
		||||
  userEmail: string;
 | 
			
		||||
  userId: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Consolidated auth check for Astro pages
 | 
			
		||||
 * Replaces repeated auth patterns in contribute pages
 | 
			
		||||
 */
 | 
			
		||||
export async function withAuth(Astro: AstroGlobal): Promise<AuthContext | Response> {
 | 
			
		||||
  const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
 | 
			
		||||
  
 | 
			
		||||
  // If auth not required, return mock context
 | 
			
		||||
  if (!authRequired) {
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: true,
 | 
			
		||||
      session: null,
 | 
			
		||||
      userEmail: 'anonymous@example.com',
 | 
			
		||||
      userId: 'anonymous'
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Check session
 | 
			
		||||
  const sessionToken = getSessionFromRequest(Astro.request);
 | 
			
		||||
  if (!sessionToken) {
 | 
			
		||||
    const loginUrl = `/api/auth/login?returnTo=${encodeURIComponent(Astro.url.toString())}`;
 | 
			
		||||
    return new Response(null, {
 | 
			
		||||
      status: 302,
 | 
			
		||||
      headers: { 'Location': loginUrl }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const session = await verifySession(sessionToken);
 | 
			
		||||
  if (!session) {
 | 
			
		||||
    const loginUrl = `/api/auth/login?returnTo=${encodeURIComponent(Astro.url.toString())}`;
 | 
			
		||||
    return new Response(null, {
 | 
			
		||||
      status: 302,
 | 
			
		||||
      headers: { 'Location': loginUrl }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    authenticated: true,
 | 
			
		||||
    session,
 | 
			
		||||
    userEmail: session.email,
 | 
			
		||||
    userId: session.userId
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Consolidated auth check for API endpoints
 | 
			
		||||
 * Replaces repeated auth patterns in API routes
 | 
			
		||||
 */
 | 
			
		||||
export async function withAPIAuth(request: Request): Promise<{ authenticated: boolean; userId: string; session?: SessionData }> {
 | 
			
		||||
  const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
 | 
			
		||||
  
 | 
			
		||||
  if (!authRequired) {
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: true,
 | 
			
		||||
      userId: 'anonymous'
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const sessionToken = getSessionFromRequest(request);
 | 
			
		||||
  if (!sessionToken) {
 | 
			
		||||
    return { authenticated: false, userId: '' };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const session = await verifySession(sessionToken);
 | 
			
		||||
  if (!session) {
 | 
			
		||||
    return { authenticated: false, userId: '' };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    authenticated: true,
 | 
			
		||||
    userId: session.userId,
 | 
			
		||||
    session
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper to create consistent API auth error responses
 | 
			
		||||
 */
 | 
			
		||||
export function createAuthErrorResponse(message: string = 'Authentication required'): Response {
 | 
			
		||||
  return new Response(JSON.stringify({ error: message }), {
 | 
			
		||||
    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';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/utils/client-auth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/utils/client-auth.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
			
		||||
// src/scripts/client-auth.js - Client-side auth utilities
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Consolidated client-side auth status check
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
 */
 | 
			
		||||
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';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make functions available globally
 | 
			
		||||
window.checkClientAuth = checkClientAuth;
 | 
			
		||||
window.requireClientAuth = requireClientAuth;
 | 
			
		||||
window.showIfAuthenticated = showIfAuthenticated;
 | 
			
		||||
@ -1,40 +0,0 @@
 | 
			
		||||
import type { AstroGlobal } from 'astro';
 | 
			
		||||
import { getSessionFromRequest, verifySession, type SessionData } from './auth.js';
 | 
			
		||||
 | 
			
		||||
export interface AuthContext {
 | 
			
		||||
  authenticated: boolean;
 | 
			
		||||
  session: SessionData | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check authentication status for server-side pages
 | 
			
		||||
export async function getAuthContext(Astro: AstroGlobal): Promise<AuthContext> {
 | 
			
		||||
  try {
 | 
			
		||||
    const sessionToken = getSessionFromRequest(Astro.request);
 | 
			
		||||
    
 | 
			
		||||
    if (!sessionToken) {
 | 
			
		||||
      return { authenticated: false, session: null };
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const session = await verifySession(sessionToken);
 | 
			
		||||
    
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: session !== null,
 | 
			
		||||
      session
 | 
			
		||||
    };
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Failed to get auth context:', error);
 | 
			
		||||
    return { authenticated: false, session: null };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Redirect to login if not authenticated
 | 
			
		||||
export function requireAuth(authContext: AuthContext, currentUrl: string): Response | null {
 | 
			
		||||
  if (!authContext.authenticated) {
 | 
			
		||||
    const loginUrl = `/api/auth/login?returnTo=${encodeURIComponent(currentUrl)}`;
 | 
			
		||||
    return new Response(null, {
 | 
			
		||||
      status: 302,
 | 
			
		||||
      headers: { 'Location': loginUrl }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user