API-Explorer-II/server/app.ts

224 lines
8.0 KiB
TypeScript
Raw Normal View History

2024-05-14 11:41:52 +00:00
/*
* Open Bank Project - API Explorer II
* Copyright (C) 2023-2024, TESOBE GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Email: contact@tesobe.com
* TESOBE GmbH
* Osloerstrasse 16/17
* Berlin 13359, Germany
*
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
*
*/
2023-04-20 06:03:07 +00:00
import 'reflect-metadata'
import 'dotenv/config'
import session from 'express-session'
2024-05-07 16:06:51 +00:00
import RedisStore from 'connect-redis'
import { createClient } from 'redis'
2025-12-11 19:44:07 +00:00
import express from 'express'
import type { Application } from 'express'
2023-04-20 06:03:07 +00:00
import { Container } from 'typedi'
import path from 'path'
import { execSync } from 'child_process'
import { OAuth2ProviderManager } from './services/OAuth2ProviderManager.js'
2025-12-11 19:44:07 +00:00
import { fileURLToPath } from 'url'
import { dirname } from 'path'
2025-12-29 08:02:37 +00:00
// Controllers removed - all routes migrated to plain Express
2025-12-11 19:44:07 +00:00
2025-12-29 07:51:10 +00:00
// Import routes (plain Express, not routing-controllers)
2025-12-29 07:44:36 +00:00
import oauth2Routes from './routes/oauth2.js'
2025-12-29 07:51:10 +00:00
import userRoutes from './routes/user.js'
import statusRoutes from './routes/status.js'
2025-12-29 07:59:10 +00:00
import obpRoutes from './routes/obp.js'
2025-12-29 08:02:37 +00:00
import opeyRoutes from './routes/opey.js'
2025-12-11 19:44:07 +00:00
// ES module equivalent of __dirname
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
2025-11-30 09:11:51 +00:00
2023-04-20 06:03:07 +00:00
const port = 8085
const app: Application = express()
2024-05-07 16:06:51 +00:00
2025-12-12 09:32:14 +00:00
// Commit ID variable (declared here to avoid TDZ issues)
let commitId = ''
2024-10-10 10:52:14 +00:00
// Initialize Redis client.
2024-05-07 16:06:51 +00:00
console.log(`--- Redis setup -------------------------------------------------`)
process.env.VITE_OBP_REDIS_URL
? console.log(`VITE_OBP_REDIS_URL: ${process.env.VITE_OBP_REDIS_URL}`)
: console.log(`VITE_OBP_REDIS_URL: undefined connects to localhost on port 6379`)
const redisPassword = process.env.VITE_OBP_REDIS_PASSWORD
? process.env.VITE_OBP_REDIS_PASSWORD // Redis instance is protected with a password
: '' // Specify an empty password (i.e., no password) when connecting to Redis
if (!redisPassword) {
console.warn(`VITE_OBP_REDIS_PASSWORD is not provided.`)
}
const redisUsername = process.env.VITE_OBP_REDIS_USERNAME
? process.env.VITE_OBP_REDIS_USERNAME // Redis instance is protected with a username/password
: '' // Specify an empty username (i.e., no username) when connecting to Redis
if (!redisUsername) {
console.warn(`VITE_OBP_REDIS_USERNAME is not provided.`)
}
2024-05-07 16:06:51 +00:00
console.log(`-----------------------------------------------------------------`)
const redisClient = process.env.VITE_OBP_REDIS_URL
? createClient({
url: process.env.VITE_OBP_REDIS_URL,
username: redisUsername,
password: redisPassword
})
2024-05-07 16:06:51 +00:00
: createClient()
redisClient.connect().catch(console.error)
const redisUrl = process.env.VITE_OBP_REDIS_URL
? process.env.VITE_OBP_REDIS_URL
: 'localhost on port 6379'
2024-10-10 10:52:14 +00:00
// Provide feedback in case of successful connection to Redis
redisClient.on('connect', () => {
console.log(`Connected to Redis instance: ${redisUrl}`)
})
2024-10-10 10:52:14 +00:00
// Provide feedback in case of unsuccessful connection to Redis
redisClient.on('error', (err) => {
console.error(`Error connecting to Redis instance: ${redisUrl}`, err)
})
2024-05-07 16:06:51 +00:00
// Initialize store.
2025-12-18 02:21:16 +00:00
// Calculate session max age in seconds (for Redis TTL)
const sessionMaxAgeSeconds = process.env.VITE_SESSION_MAX_AGE
? parseInt(process.env.VITE_SESSION_MAX_AGE)
: 60 * 60 // Default: 1 hour in seconds
// CRITICAL: Set Redis TTL to match session maxAge
// Without this, Redis uses its own default TTL which may expire sessions prematurely
2024-05-07 16:06:51 +00:00
let redisStore = new RedisStore({
client: redisClient,
2025-12-18 02:21:16 +00:00
prefix: 'api-explorer-ii:',
ttl: sessionMaxAgeSeconds // TTL in seconds - MUST match cookie maxAge
2024-05-07 16:06:51 +00:00
})
console.info(`Environment: ${app.get('env')}`)
2025-12-18 02:21:16 +00:00
console.info(
`Session maxAge configured: ${sessionMaxAgeSeconds} seconds (${sessionMaxAgeSeconds / 60} minutes)`
)
2023-04-20 06:03:07 +00:00
app.use(express.json())
let sessionObject = {
2024-05-07 16:06:51 +00:00
store: redisStore,
2025-12-18 02:40:16 +00:00
name: 'obp-api-explorer-ii.sid', // CRITICAL: Unique cookie name to prevent conflicts with other apps on localhost
2026-01-31 09:06:01 +00:00
secret: process.env.VITE_OBP_SERVER_SESSION_PASSWORD,
resave: false,
saveUninitialized: false, // Don't save empty sessions (better for authenticated apps)
cookie: {
httpOnly: true,
secure: false,
2025-12-18 02:21:16 +00:00
maxAge: sessionMaxAgeSeconds * 1000 // maxAge in milliseconds
}
}
if (app.get('env') === 'production') {
app.set('trust proxy', 1) // trust first proxy
sessionObject.cookie.secure = true // serve secure cookies
}
2024-05-07 16:06:51 +00:00
app.use(session(sessionObject))
2023-04-20 06:03:07 +00:00
2025-12-29 19:04:47 +00:00
// OAuth2 Multi-Provider Setup only - no legacy fallback
2025-11-30 09:11:51 +00:00
2025-12-02 13:26:19 +00:00
// Async IIFE to initialize OAuth2 and start server
let instance: any
;(async function initializeAndStartServer() {
// Initialize Multi-Provider OAuth2 Manager
console.log('--- OAuth2 Multi-Provider Setup ---------------------------------')
const providerManager = Container.get(OAuth2ProviderManager)
try {
const success = await providerManager.initializeProviders()
if (success) {
const availableProviders = providerManager.getAvailableProviders()
2025-12-29 17:01:29 +00:00
console.log(`OK Initialized ${availableProviders.length} OAuth2 providers:`)
availableProviders.forEach((name) => console.log(` - ${name}`))
// Start health monitoring
providerManager.startHealthCheck(60000) // Check every 60 seconds
2025-12-29 17:01:29 +00:00
console.log('OK Provider health monitoring started (every 60s)')
} else {
2025-12-29 19:04:47 +00:00
console.error('ERROR: No OAuth2 providers initialized from OBP API')
console.error(
'ERROR: Check that OBP API is running and returns providers from /obp/v5.1.0/well-known'
)
console.error('ERROR: Server will start but login will not work')
}
} catch (error) {
2025-12-29 17:01:29 +00:00
console.error('ERROR Failed to initialize OAuth2 multi-provider:', error)
2025-12-29 19:04:47 +00:00
console.error('ERROR: Server will start but login will not work')
2024-12-05 10:17:30 +00:00
}
2025-12-02 13:26:19 +00:00
console.log(`-----------------------------------------------------------------`)
const routePrefix = '/api'
2025-12-29 08:02:37 +00:00
// Register all routes (plain Express)
2025-12-29 07:44:36 +00:00
app.use(routePrefix, oauth2Routes)
2025-12-29 07:51:10 +00:00
app.use(routePrefix, userRoutes)
app.use(routePrefix, statusRoutes)
2025-12-29 07:59:10 +00:00
app.use(routePrefix, obpRoutes)
2025-12-29 08:02:37 +00:00
app.use(routePrefix, opeyRoutes)
2025-12-29 07:44:36 +00:00
console.log('OAuth2 routes registered (plain Express)')
2025-12-29 07:51:10 +00:00
console.log('User routes registered (plain Express)')
console.log('Status routes registered (plain Express)')
2025-12-29 07:59:10 +00:00
console.log('OBP routes registered (plain Express)')
2025-12-29 08:02:37 +00:00
console.log('Opey routes registered (plain Express)')
console.log('All routes migrated to plain Express - routing-controllers removed')
2025-12-29 07:44:36 +00:00
2025-12-29 08:02:37 +00:00
instance = app.listen(port)
2025-12-02 13:26:19 +00:00
console.log(
`Backend is running. You can check a status at http://localhost:${port}${routePrefix}/status`
)
// Get commit ID
try {
// Try to get the commit ID
commitId = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim()
console.log('Current Commit ID:', commitId)
2025-12-12 09:32:14 +00:00
} catch (error: any) {
2025-12-02 13:26:19 +00:00
// Log the error but do not terminate the process
console.error('Warning: Failed to retrieve the commit ID. Proceeding without it.')
console.error('Error details:', error.message)
commitId = 'unknown' // Assign a fallback value
}
// Continue execution with or without a valid commit ID
console.log('Execution continues with commitId:', commitId)
// Error Handling to Shut Down the App
instance.on('error', (err) => {
redisClient.disconnect()
if (err.code === 'EADDRINUSE') {
console.error(`Port ${port} is already in use.`)
process.exit(1)
// Shut down the app
} else {
console.error('An error occurred:', err)
}
})
})()
// Export instance for use in other modules
2025-12-12 09:32:14 +00:00
export { instance, commitId }
2024-12-05 10:17:30 +00:00
2023-04-20 06:03:07 +00:00
export default app