diff --git a/src/api/functions/discord.ts b/src/api/functions/discord.ts index f77031cd..f184308d 100644 --- a/src/api/functions/discord.ts +++ b/src/api/functions/discord.ts @@ -13,9 +13,7 @@ import moment from "moment-timezone"; import { FastifyBaseLogger } from "fastify"; import { DiscordEventError } from "../../common/errors/index.js"; -import { getSecretValue } from "../plugins/auth.js"; -import { genericConfig, SecretConfig } from "../../common/config.js"; -import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; +import { type SecretConfig } from "../../common/config.js"; // https://stackoverflow.com/a/3809435/5684541 // https://calendar-buff.acmuiuc.pages.dev/calendar?id=dd7af73a-3df6-4e12-b228-0d2dac34fda7&date=2024-08-30 @@ -26,7 +24,7 @@ export type IUpdateDiscord = EventPostRequest & { id: string }; const urlRegex = /https:\/\/[a-z0-9.-]+\/calendar\?id=([a-f0-9-]+)/; export const updateDiscord = async ( - secretApiConfig: SecretConfig, + config: { botToken: string; guildId: string }, event: IUpdateDiscord, actor: string, isDelete: boolean = false, @@ -36,7 +34,7 @@ export const updateDiscord = async ( let payload: GuildScheduledEventCreateOptions | null = null; client.once(Events.ClientReady, async (readyClient: Client) => { logger.debug(`Logged in as ${readyClient.user.tag}`); - const guildID = secretApiConfig.discord_guild_id; + const guildID = config.guildId; const guild = await client.guilds.fetch(guildID?.toString() || ""); const discordEvents = await guild.scheduledEvents.fetch(); const snowflakeMeetingLookup = discordEvents.reduce( @@ -110,7 +108,7 @@ export const updateDiscord = async ( return payload; }); - const token = secretApiConfig.discord_bot_token; + const token = config.botToken; if (!token) { logger.error("No Discord bot token found in secrets!"); diff --git a/src/api/index.ts b/src/api/index.ts index 6914745d..86b324e7 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -62,12 +62,6 @@ async function init(prettyPrint: boolean = false) { const secretsManagerClient = new SecretsManagerClient({ region: genericConfig.AwsRegion, }); - const secret = (await getSecretValue( - secretsManagerClient, - genericConfig.ConfigSecretName, - )) as SecretConfig; - const redisClient = new RedisModule.default(secret.redis_url); - const transport = prettyPrint ? { target: "pino-pretty", @@ -252,21 +246,22 @@ async function init(prettyPrint: boolean = false) { app.nodeCache = new NodeCache({ checkperiod: 30 }); app.dynamoClient = dynamoClient; app.secretsManagerClient = secretsManagerClient; - app.redisClient = redisClient; app.refreshSecretConfig = async () => { - app.secretConfig = (await getSecretValue( - app.secretsManagerClient, - genericConfig.ConfigSecretName, - )) as SecretConfig; - if (app.environmentConfig.TestingCredentialsSecret) { - const temp = (await getSecretValue( - app.secretsManagerClient, - app.environmentConfig.TestingCredentialsSecret, - )) as SecretTesting; - app.secretConfig = { ...app.secretConfig, ...temp }; - } + app.log.debug( + `Getting secrets: ${JSON.stringify(app.environmentConfig.ConfigurationSecretIds)}.`, + ); + const allSecrets = await Promise.all( + app.environmentConfig.ConfigurationSecretIds.map((secretName) => + getSecretValue(app.secretsManagerClient, secretName), + ), + ); + app.secretConfig = allSecrets.reduce( + (acc, currentSecret) => ({ ...acc, ...currentSecret }), + {}, + ) as SecretConfig; }; - app.refreshSecretConfig(); + await app.refreshSecretConfig(); + app.redisClient = new RedisModule.default(app.secretConfig.redis_url); app.addHook("onRequest", (req, _, done) => { req.startTime = now(); const hostname = req.hostname; diff --git a/src/api/routes/events.ts b/src/api/routes/events.ts index aa936fc1..e2acac48 100644 --- a/src/api/routes/events.ts +++ b/src/api/routes/events.ts @@ -369,7 +369,10 @@ const eventsPlugin: FastifyPluginAsyncZodOpenApi = async ( try { if (request.body.featured && !request.body.repeats) { await updateDiscord( - fastify.secretConfig, + { + botToken: fastify.secretConfig.discord_bot_token, + guildId: fastify.environmentConfig.DiscordGuildId, + }, entry, request.username, false, @@ -507,7 +510,10 @@ const eventsPlugin: FastifyPluginAsyncZodOpenApi = async ( }), ); await updateDiscord( - fastify.secretConfig, + { + botToken: fastify.secretConfig.discord_bot_token, + guildId: fastify.environmentConfig.DiscordGuildId, + }, { id } as IUpdateDiscord, request.username, true, diff --git a/src/common/config.ts b/src/common/config.ts index ed118980..c8c5474f 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -23,7 +23,8 @@ export type ConfigType = { PaidMemberPriceId: string; AadValidReadOnlyClientId: string; LinkryCloudfrontKvArn?: string; - TestingCredentialsSecret?: string; + ConfigurationSecretIds: string[]; + DiscordGuildId: string; }; export type GenericConfigType = { @@ -31,7 +32,6 @@ export type GenericConfigType = { CacheDynamoTableName: string; LinkryDynamoTableName: string; StripeLinksDynamoTableName: string; - ConfigSecretName: string; EntraSecretName: string; UpcomingEventThresholdSeconds: number; AwsRegion: string; @@ -49,6 +49,8 @@ export type GenericConfigType = { EntraReadOnlySecretName: string; AuditLogTable: string; ApiKeyTable: string; + ConfigSecretName: string; + TestingCredentialsSecret: string; }; type EnvironmentConfigType = { @@ -69,7 +71,6 @@ const genericConfig: GenericConfigType = { StripeLinksDynamoTableName: "infra-core-api-stripe-links", CacheDynamoTableName: "infra-core-api-cache", LinkryDynamoTableName: "infra-core-api-linkry", - ConfigSecretName: "infra-core-api-config", EntraSecretName: "infra-core-api-entra", EntraReadOnlySecretName: "infra-core-api-ro-entra", UpcomingEventThresholdSeconds: 1800, // 30 mins @@ -87,6 +88,8 @@ const genericConfig: GenericConfigType = { RoomRequestsStatusTableName: "infra-core-api-room-requests-status", AuditLogTable: "infra-core-api-audit-log", ApiKeyTable: "infra-core-api-keys", + ConfigSecretName: "infra-core-api-config", + TestingCredentialsSecret: "infra-core-api-testing-credentials", } as const; const environmentConfig: EnvironmentConfigType = { @@ -99,7 +102,7 @@ const environmentConfig: EnvironmentConfigType = { /^https:\/\/(?:.*\.)?acmuiuc\.pages\.dev$/, /http:\/\/localhost:\d+$/, ], - TestingCredentialsSecret: "infra-core-api-testing-credentials", + ConfigurationSecretIds: [genericConfig.TestingCredentialsSecret, genericConfig.ConfigSecretName], AadValidClientId: "39c28870-94e4-47ee-b4fb-affe0bf96c9f", LinkryBaseUrl: "https://core.aws.qa.acmuiuc.org", PasskitIdentifier: "pass.org.acmuiuc.qa.membership", @@ -112,11 +115,13 @@ const environmentConfig: EnvironmentConfigType = { PaidMemberGroupId: "9222451f-b354-4e64-ba28-c0f367a277c2", PaidMemberPriceId: "price_1R4TcTDGHrJxx3mKI6XF9cNG", AadValidReadOnlyClientId: "2c6a0057-5acc-496c-a4e5-4adbf88387ba", - LinkryCloudfrontKvArn: "arn:aws:cloudfront::427040638965:key-value-store/0c2c02fd-7c47-4029-975d-bc5d0376bba1" + LinkryCloudfrontKvArn: "arn:aws:cloudfront::427040638965:key-value-store/0c2c02fd-7c47-4029-975d-bc5d0376bba1", + DiscordGuildId: "1278798685706391664" }, prod: { UserFacingUrl: "https://core.acm.illinois.edu", AzureRoleMapping: { AutonomousWriters: [AppRoles.EVENTS_MANAGER] }, + ConfigurationSecretIds: [genericConfig.ConfigSecretName], ValidCorsOrigins: [ /^https:\/\/(?:.*\.)?acmuiuc-academic-web\.pages\.dev$/, /^https:\/\/(?:.*\.)?acmuiuc\.pages\.dev$/, @@ -134,12 +139,12 @@ const environmentConfig: EnvironmentConfigType = { "https://sqs.us-east-1.amazonaws.com/298118738376/infra-core-api-sqs", PaidMemberGroupId: "172fd9ee-69f0-4384-9786-41ff1a43cf8e", PaidMemberPriceId: "price_1MUGIRDiGOXU9RuSChPYK6wZ", - AadValidReadOnlyClientId: "2c6a0057-5acc-496c-a4e5-4adbf88387ba" + AadValidReadOnlyClientId: "2c6a0057-5acc-496c-a4e5-4adbf88387ba", + DiscordGuildId: "718945436332720229" }, }; export type SecretConfig = { - discord_guild_id: string; discord_bot_token: string; entra_id_private_key?: string; entra_id_thumbprint?: string; diff --git a/tests/unit/secret.testdata.ts b/tests/unit/secret.testdata.ts index c8172c8a..8b73c6c0 100644 --- a/tests/unit/secret.testdata.ts +++ b/tests/unit/secret.testdata.ts @@ -1,7 +1,6 @@ import { SecretConfig } from "../../src/common/config.js"; const secretObject = { - discord_guild_id: "12345", discord_bot_token: "12345", entra_id_private_key: "", entra_id_thumbprint: "", diff --git a/tests/unit/vitest.setup.ts b/tests/unit/vitest.setup.ts index abb9b65e..8ca37dd3 100644 --- a/tests/unit/vitest.setup.ts +++ b/tests/unit/vitest.setup.ts @@ -114,10 +114,10 @@ smMock.on(GetSecretValueCommand).callsFake((command) => { if (command.SecretId == genericConfig.ConfigSecretName) { return Promise.resolve({ SecretString: secretJson }); } - if (command.SecretId == environmentConfig["dev"].TestingCredentialsSecret) { + if (command.SecretId == genericConfig.TestingCredentialsSecret) { return Promise.resolve({ SecretString: testSecretJson }); } - return Promise.reject(new Error("Secret ID not mocked")); + return Promise.reject(new Error(`Secret ID ${command.SecretID} not mocked`)); }); vi.mock("ioredis", () => import("ioredis-mock"));