177 lines
4.6 KiB
TypeScript
177 lines
4.6 KiB
TypeScript
|
|
// 🤠 Heady Authentication Status Endpoint - System Health & Stats
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns authentication system status and statistics
|
||
|
|
* Used for health checks, monitoring, and admin dashboards
|
||
|
|
*/
|
||
|
|
|
||
|
|
import type { APIRoute } from 'astro';
|
||
|
|
import {
|
||
|
|
getActiveSessionCount,
|
||
|
|
getSessionManager,
|
||
|
|
} from '../../../lib/auth/session-manager.js';
|
||
|
|
import {
|
||
|
|
getRoleMappingConfig,
|
||
|
|
loadAuthentikConfig,
|
||
|
|
validateAuthentikConfig,
|
||
|
|
} from '../../../lib/config/authentik.js';
|
||
|
|
|
||
|
|
export const GET: APIRoute = async ({ url, cookies }) => {
|
||
|
|
try {
|
||
|
|
// During build/prerender, return build-time response
|
||
|
|
if (
|
||
|
|
process.env.NODE_ENV === 'development' &&
|
||
|
|
!process.env.CI &&
|
||
|
|
!process.env.AUTHENTIK_ISSUER
|
||
|
|
) {
|
||
|
|
return new Response(
|
||
|
|
JSON.stringify({
|
||
|
|
service: 'Heady Authentication',
|
||
|
|
status: 'build_mode',
|
||
|
|
message: 'Authentication system available at runtime',
|
||
|
|
timestamp: new Date().toISOString(),
|
||
|
|
}),
|
||
|
|
{
|
||
|
|
status: 200,
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load configuration
|
||
|
|
const config = loadAuthentikConfig();
|
||
|
|
const validation = validateAuthentikConfig();
|
||
|
|
const roleMappingConfig = getRoleMappingConfig();
|
||
|
|
|
||
|
|
// Check if detailed stats are requested (requires admin access)
|
||
|
|
const includeStats = url.searchParams.get('stats') === 'true';
|
||
|
|
|
||
|
|
// Basic status available to everyone
|
||
|
|
const basicStatus = {
|
||
|
|
service: 'Heady Authentication',
|
||
|
|
status: validation.valid ? 'healthy' : 'configuration_error',
|
||
|
|
version: '1.0.0',
|
||
|
|
authentik_configured: !!config.issuer,
|
||
|
|
authentication: {
|
||
|
|
provider: 'Authentik OIDC',
|
||
|
|
issuer: config.issuer,
|
||
|
|
redirect_uri: config.redirectUri,
|
||
|
|
},
|
||
|
|
timestamp: new Date().toISOString(),
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!validation.valid) {
|
||
|
|
return new Response(
|
||
|
|
JSON.stringify({
|
||
|
|
...basicStatus,
|
||
|
|
errors: validation.errors,
|
||
|
|
}),
|
||
|
|
{
|
||
|
|
status: 503, // Service Unavailable
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// If detailed stats not requested, return basic status
|
||
|
|
if (!includeStats) {
|
||
|
|
return new Response(JSON.stringify(basicStatus), {
|
||
|
|
status: 200,
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Detailed stats require authentication
|
||
|
|
const sessionMgr = getSessionManager();
|
||
|
|
const validationResult = await sessionMgr.validateSession({ cookies });
|
||
|
|
|
||
|
|
if (!validationResult.valid || !validationResult.user) {
|
||
|
|
return new Response(
|
||
|
|
JSON.stringify({
|
||
|
|
...basicStatus,
|
||
|
|
error: 'Authentication required for detailed statistics',
|
||
|
|
login_url: '/api/auth/login',
|
||
|
|
}),
|
||
|
|
{
|
||
|
|
status: 401,
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const user = validationResult.user;
|
||
|
|
|
||
|
|
// Check if user can view statistics (admin/auditor roles)
|
||
|
|
const canViewStats = ['owner', 'admin', 'auditor'].includes(user.role);
|
||
|
|
|
||
|
|
if (!canViewStats) {
|
||
|
|
return new Response(
|
||
|
|
JSON.stringify({
|
||
|
|
...basicStatus,
|
||
|
|
error: 'Insufficient permissions for detailed statistics',
|
||
|
|
required_roles: ['owner', 'admin', 'auditor'],
|
||
|
|
user_role: user.role,
|
||
|
|
}),
|
||
|
|
{
|
||
|
|
status: 403,
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get session statistics (simplified for now)
|
||
|
|
const activeSessionCount = getActiveSessionCount();
|
||
|
|
|
||
|
|
const detailedStatus = {
|
||
|
|
...basicStatus,
|
||
|
|
configuration: {
|
||
|
|
scopes: config.scopes,
|
||
|
|
role_mapping: {
|
||
|
|
environment_configured: Object.values(roleMappingConfig).some(
|
||
|
|
(groups) => groups.length > 0,
|
||
|
|
),
|
||
|
|
owner_groups: roleMappingConfig.ownerGroups,
|
||
|
|
admin_groups: roleMappingConfig.adminGroups,
|
||
|
|
network_admin_groups: roleMappingConfig.networkGroups,
|
||
|
|
it_admin_groups: roleMappingConfig.itGroups,
|
||
|
|
auditor_groups: roleMappingConfig.auditorGroups,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
sessions: {
|
||
|
|
total_active: activeSessionCount,
|
||
|
|
// Additional session stats would go here
|
||
|
|
},
|
||
|
|
system: {
|
||
|
|
uptime: process.uptime(),
|
||
|
|
memory_usage: process.memoryUsage(),
|
||
|
|
node_version: process.version,
|
||
|
|
environment: process.env.NODE_ENV || 'unknown',
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
console.log(`✓ Auth status requested by ${user.email} (${user.role})`);
|
||
|
|
console.log(` Active sessions: ${activeSessionCount}`);
|
||
|
|
|
||
|
|
return new Response(JSON.stringify(detailedStatus), {
|
||
|
|
status: 200,
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ Auth status error:', error);
|
||
|
|
|
||
|
|
return new Response(
|
||
|
|
JSON.stringify({
|
||
|
|
service: 'Heady Authentication',
|
||
|
|
status: 'error',
|
||
|
|
error: 'Status check failed',
|
||
|
|
message: error instanceof Error ? error.message : 'Unknown error',
|
||
|
|
timestamp: new Date().toISOString(),
|
||
|
|
}),
|
||
|
|
{
|
||
|
|
status: 500,
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
};
|