in controlplane/src/core/build-server.ts [153:506]
export default async function build(opts: BuildConfig) {
opts.logger = {
timestamp: stdTimeFunctions.isoTime,
formatters: {
level: (label) => {
return {
level: label,
};
},
},
...opts.logger,
};
const logger = pino(opts.production ? opts.logger : { ...developmentLoggerOpts, ...opts.logger });
const fastify = Fastify({
logger,
// The maximum amount of time in *milliseconds* in which a plugin can load
pluginTimeout: 10_000, // 10s
});
/**
* Plugin registration
*/
await fastify.register(fastifyHealth);
if (opts.prometheus?.enabled) {
await fastify.register(fastifyMetrics, {
path: opts.prometheus.path,
});
await fastify.metricsServer.listen({
host: opts.prometheus.host,
port: opts.prometheus.port,
});
}
await fastify.register(fastifyDatabase, {
databaseConnectionUrl: opts.database.url,
gracefulTimeoutSec: 15,
tls: opts.database.tls,
debugSQL: opts.debugSQL,
});
await fastify.register(fastifyCors, {
// Produce an error if allowedOrigins is undefined
origin: opts.allowedOrigins || [],
methods: [...cors.allowedMethods],
allowedHeaders: [...cors.allowedHeaders, 'cosmo-org-slug', 'user-agent'],
exposedHeaders: [...cors.exposedHeaders, 'Trailer-Response-Id'],
credentials: true,
// Let browsers cache CORS information to reduce the number of
// preflight requests. Modern Chrome caps the value at 2h.
maxAge: 2 * 60 * 60,
});
if (opts.clickhouseDsn) {
await fastify.register(fastifyClickHouse, {
dsn: opts.clickhouseDsn,
logger,
});
} else {
logger.warn('ClickHouse connection not configured');
}
const authUtils = new AuthUtils(fastify.db, {
jwtSecret: opts.auth.secret,
session: {
cookieName: userSessionCookieName,
},
oauth: {
clientID: opts.keycloak.clientId,
redirectUri: opts.auth.redirectUri,
openIdApiBaseUrl: opts.keycloak.apiUrl + '/realms/' + opts.keycloak.realm,
openIdFrontendUrl: opts.keycloak.frontendUrl + '/realms/' + opts.keycloak.realm,
logoutRedirectUri: opts.auth.webBaseUrl + '/login',
},
pkce: {
cookieName: pkceCodeVerifierCookieName,
},
webBaseUrl: opts.auth.webBaseUrl,
webErrorPath: opts.auth.webErrorPath,
});
const organizationRepository = new OrganizationRepository(logger, fastify.db, opts.stripe?.defaultPlanId);
const orgInvitationRepository = new OrganizationInvitationRepository(logger, fastify.db, opts.stripe?.defaultPlanId);
const apiKeyAuth = new ApiKeyAuthenticator(fastify.db, organizationRepository);
const userRepo = new UserRepository(logger, fastify.db);
const apiKeyRepository = new ApiKeyRepository(fastify.db);
const webAuth = new WebSessionAuthenticator(opts.auth.secret, userRepo);
const graphKeyAuth = new GraphApiTokenAuthenticator(opts.auth.secret);
const accessTokenAuth = new AccessTokenAuthenticator(organizationRepository, authUtils);
const authenticator = new Authentication(webAuth, apiKeyAuth, accessTokenAuth, graphKeyAuth, organizationRepository);
const authorizer = new Authorization(logger, opts.stripe?.defaultPlanId);
const keycloakClient = new Keycloak({
apiUrl: opts.keycloak.apiUrl,
realm: opts.keycloak.loginRealm,
clientId: opts.keycloak.clientId,
adminUser: opts.keycloak.adminUser,
adminPassword: opts.keycloak.adminPassword,
});
let mailerClient: Mailer | undefined;
if (opts.mailer.smtpEnabled) {
const { smtpHost, smtpPort, smtpSecure, smtpRequireTls, smtpUsername, smtpPassword } = opts.mailer;
const isSmtpHostSet = smtpHost && smtpPort;
const isSmtpAuthSet = smtpUsername && smtpPassword;
if (!isSmtpHostSet) {
throw new Error(`smtp host or port not set properly! Please ensure to do so!`);
}
if (!isSmtpAuthSet) {
throw new Error(`smtp username and host not set properly!`);
}
mailerClient = new Mailer({
smtpHost,
smtpPort,
smtpSecure,
smtpRequireTls,
smtpUsername,
smtpPassword,
});
try {
const verified = await mailerClient.verifyConnection();
if (verified) {
logger.info('Email client ready to send emails');
} else {
logger.error('Email client failed to verify connection');
}
} catch (error) {
logger.error(error, 'Email client could not verify connection');
}
}
const bullWorkers: Worker[] = [];
await fastify.register(fastifyRedis, {
host: opts.redis.host,
port: opts.redis.port,
password: opts.redis.password,
tls: opts.redis.tls,
});
if (!opts.s3Storage || !opts.s3Storage.url) {
throw new Error('S3 storage URL is required');
}
const bucketName = extractS3BucketName(opts.s3Storage);
const s3Config = createS3ClientConfig(bucketName, opts.s3Storage);
const s3Client = new S3Client(s3Config);
const blobStorage = new S3BlobStorage(s3Client, bucketName);
const platformWebhooks = new PlatformWebhookService(opts.webhook?.url, opts.webhook?.key, logger);
const readmeQueue = new AIGraphReadmeQueue(logger, fastify.redisForQueue);
if (opts.openaiAPIKey) {
bullWorkers.push(
createAIGraphReadmeWorker({
redisConnection: fastify.redisForWorker,
db: fastify.db,
logger,
openAiApiKey: opts.openaiAPIKey,
}),
);
}
const deleteOrganizationQueue = new DeleteOrganizationQueue(logger, fastify.redisForQueue);
bullWorkers.push(
createDeleteOrganizationWorker({
redisConnection: fastify.redisForWorker,
db: fastify.db,
logger,
keycloakClient,
keycloakRealm: opts.keycloak.realm,
blobStorage,
}),
);
const deactivateOrganizationQueue = new DeactivateOrganizationQueue(logger, fastify.redisForQueue);
bullWorkers.push(
createDeactivateOrganizationWorker({
redisConnection: fastify.redisForWorker,
db: fastify.db,
logger,
keycloakClient,
keycloakRealm: opts.keycloak.realm,
deleteOrganizationQueue,
}),
);
const reactivateOrganizationQueue = new ReactivateOrganizationQueue(logger, fastify.redisForQueue);
bullWorkers.push(
createReactivateOrganizationWorker({
redisConnection: fastify.redisForWorker,
db: fastify.db,
logger,
deleteOrganizationQueue,
}),
);
const deleteUserQueue = new DeleteUserQueue(logger, fastify.redisForQueue);
bullWorkers.push(
createDeleteUserWorker({
redisConnection: fastify.redisForWorker,
db: fastify.db,
logger,
keycloakClient,
keycloakRealm: opts.keycloak.realm,
blobStorage,
platformWebhooks,
}),
);
// required to verify webhook payloads
await fastify.register(import('fastify-raw-body'), {
field: 'rawBody',
global: false,
encoding: 'utf8',
});
let githubApp: App | undefined;
if (opts.githubApp?.clientId) {
githubApp = new App({
appId: opts.githubApp?.id ?? '',
privateKey: Buffer.from(opts.githubApp?.privateKey ?? '', 'base64').toString(),
oauth: {
clientId: opts.githubApp?.clientId ?? '',
clientSecret: opts.githubApp?.clientSecret ?? '',
},
});
const githubRepository = new GitHubRepository(fastify.db, githubApp);
await fastify.register(GitHubWebhookController, {
prefix: '/webhook/github',
githubRepository,
webhookSecret: opts.githubApp?.webhookSecret ?? '',
logger,
});
}
if (opts.stripe?.secret && opts.stripe?.webhookSecret) {
const billingRepo = new BillingRepository(fastify.db);
const billingService = new BillingService(fastify.db, billingRepo);
await fastify.register(StripeWebhookController, {
prefix: '/webhook/stripe',
billingService,
webhookSecret: opts.stripe.webhookSecret,
logger,
});
}
/**
* Controllers registration
*/
await fastify.register(AuthController, {
organizationRepository,
orgInvitationRepository,
webAuth,
authUtils,
prefix: '/v1/auth',
db: fastify.db,
jwtSecret: opts.auth.secret,
session: {
cookieName: userSessionCookieName,
},
pkce: {
cookieName: pkceCodeVerifierCookieName,
},
webBaseUrl: opts.auth.webBaseUrl,
keycloakClient,
keycloakRealm: opts.keycloak.realm,
platformWebhooks,
defaultBillingPlanId: opts.stripe?.defaultPlanId,
});
await fastify.register(ScimController, {
organizationRepository,
userRepository: userRepo,
apiKeyRepository,
authenticator: apiKeyAuth,
prefix: '/scim/v2',
db: fastify.db,
keycloakClient,
keycloakRealm: opts.keycloak.realm,
});
// Must be registered after custom fastify routes
// Because it registers an all-catch route for connect handlers
await fastify.register(fastifyConnectPlugin, {
routes: routes({
db: fastify.db,
logger,
jwtSecret: opts.auth.secret,
keycloakRealm: opts.keycloak.realm,
keycloakApiUrl: opts.keycloak.apiUrl,
chClient: fastify.ch,
authenticator,
authorizer,
keycloakClient,
platformWebhooks,
githubApp,
webBaseUrl: opts.auth.webBaseUrl,
slack: opts.slack,
blobStorage,
mailerClient,
billingDefaultPlanId: opts.stripe?.defaultPlanId,
openaiApiKey: opts.openaiAPIKey,
queues: {
readmeQueue,
deleteOrganizationQueue,
deactivateOrganizationQueue,
reactivateOrganizationQueue,
deleteUserQueue,
},
stripeSecretKey: opts.stripe?.secret,
admissionWebhookJWTSecret: opts.admissionWebhook.secret,
cdnBaseUrl: opts.cdnBaseUrl,
}),
contextValues(req) {
return createContextValues().set<FastifyBaseLogger>({ id: fastifyLoggerId, defaultValue: req.log }, req.log);
},
logLevel: opts.logger.level as pino.LevelWithSilent,
// Avoid compression for small requests
compressMinBytes: 1024,
maxTimeoutMs: 80_000,
shutdownTimeoutMs: 30_000,
// The default limit is the maximum supported value of ~4GiB
// We go with 32MiB to avoid allocating too much memory for large requests
writeMaxBytes: 32 * 1024 * 1024,
acceptCompression: [compressionBrotli, compressionGzip],
});
await fastify.register(fastifyGracefulShutdown, {
timeout: 60_000,
});
fastify.gracefulShutdown(async () => {
fastify.log.debug('Shutting down bull workers');
await Promise.all(bullWorkers.map((worker) => worker.close()));
fastify.log.debug('Bull workers shut down');
});
return fastify;
}