Add authentication, user management, and database migration features

- Implement OAuth 2.0 and PAT authentication methods
- Add user management, roles, and profile functionality
- Add database migrations and admin user scripts
- Update services for authentication and user settings
- Add protected routes and permission hooks
- Update documentation for authentication and database access
This commit is contained in:
2026-01-15 03:20:50 +01:00
parent f3637b85e1
commit 1fa424efb9
70 changed files with 15597 additions and 2098 deletions

View File

@@ -14,10 +14,15 @@ import referenceDataRouter from './routes/referenceData.js';
import dashboardRouter from './routes/dashboard.js';
import configurationRouter from './routes/configuration.js';
import authRouter, { authMiddleware } from './routes/auth.js';
import usersRouter from './routes/users.js';
import rolesRouter from './routes/roles.js';
import userSettingsRouter from './routes/userSettings.js';
import profileRouter from './routes/profile.js';
import searchRouter from './routes/search.js';
import cacheRouter from './routes/cache.js';
import objectsRouter from './routes/objects.js';
import schemaRouter from './routes/schema.js';
import { runMigrations } from './services/database/migrations.js';
// Validate configuration
validateConfig();
@@ -55,13 +60,49 @@ app.use((req, res, next) => {
// Auth middleware - extract session info for all requests
app.use(authMiddleware);
// Set user token on CMDBService for each request (for user-specific OAuth)
app.use((req, res, next) => {
// Set user's OAuth token if available
// Set user token and settings on services for each request
app.use(async (req, res, next) => {
// Set user's OAuth token if available (for OAuth sessions)
if (req.accessToken) {
cmdbService.setUserToken(req.accessToken);
}
// Set user's Jira PAT and AI keys if user is authenticated and has local account
if (req.user && 'id' in req.user) {
try {
const { userSettingsService } = await import('./services/userSettingsService.js');
const settings = await userSettingsService.getUserSettings(req.user.id);
if (settings?.jira_pat) {
// Use user's Jira PAT from profile settings (preferred for writes)
cmdbService.setUserToken(settings.jira_pat);
} else if (config.jiraServiceAccountToken) {
// Fallback to service account token if user doesn't have PAT configured
// This allows writes to work when JIRA_SERVICE_ACCOUNT_TOKEN is set in .env
cmdbService.setUserToken(config.jiraServiceAccountToken);
logger.debug('Using service account token as fallback (user PAT not configured)');
} else {
// No token available - clear token
cmdbService.setUserToken(null);
}
// Store user settings in request for services to access
(req as any).userSettings = settings;
} catch (error) {
// If user settings can't be loaded, try service account token as fallback
logger.debug('Failed to load user settings:', error);
if (config.jiraServiceAccountToken) {
cmdbService.setUserToken(config.jiraServiceAccountToken);
logger.debug('Using service account token as fallback (user settings load failed)');
} else {
cmdbService.setUserToken(null);
}
}
} else {
// No user authenticated - clear token
cmdbService.setUserToken(null);
}
// Clear token after response is sent
res.on('finish', () => {
cmdbService.clearUserToken();
@@ -80,7 +121,7 @@ app.get('/health', async (req, res) => {
timestamp: new Date().toISOString(),
dataSource: dataService.isUsingJiraAssets() ? 'jira-assets-cached' : 'mock-data',
jiraConnected: dataService.isUsingJiraAssets() ? jiraConnected : null,
aiConfigured: !!config.anthropicApiKey,
aiConfigured: true, // AI is configured per-user in profile settings
cache: {
isWarm: cacheStatus.isWarm,
objectCount: cacheStatus.totalObjects,
@@ -98,6 +139,10 @@ app.get('/api/config', (req, res) => {
// API routes
app.use('/api/auth', authRouter);
app.use('/api/users', usersRouter);
app.use('/api/roles', rolesRouter);
app.use('/api/user-settings', userSettingsRouter);
app.use('/api/profile', profileRouter);
app.use('/api/applications', applicationsRouter);
app.use('/api/classifications', classificationsRouter);
app.use('/api/reference-data', referenceDataRouter);
@@ -127,14 +172,24 @@ const PORT = config.port;
app.listen(PORT, async () => {
logger.info(`Server running on http://localhost:${PORT}`);
logger.info(`Environment: ${config.nodeEnv}`);
logger.info(`AI Classification: ${config.anthropicApiKey ? 'Configured' : 'Not configured'}`);
logger.info(`Jira Assets: ${config.jiraPat ? 'Configured with caching' : 'Using mock data'}`);
logger.info(`AI Classification: Configured per-user in profile settings`);
logger.info(`Jira Assets: ${config.jiraSchemaId ? 'Schema configured - users configure PAT in profile' : 'Schema not configured'}`);
// Initialize sync engine if using Jira Assets
if (config.jiraPat && config.jiraSchemaId) {
// Run database migrations
try {
await runMigrations();
logger.info('Database migrations completed');
} catch (error) {
logger.error('Failed to run database migrations', error);
}
// Initialize sync engine if Jira schema is configured
// Note: Sync engine will only sync when users with configured Jira PATs make requests
// This prevents unauthorized Jira API calls
if (config.jiraSchemaId) {
try {
await syncEngine.initialize();
logger.info('Sync Engine: Initialized and running');
logger.info('Sync Engine: Initialized (sync on-demand per user request)');
} catch (error) {
logger.error('Failed to initialize sync engine', error);
}