diff --git a/.jenkins/projectBuilder.Jenkinsfile b/.jenkins/projectBuilder.Jenkinsfile index 67e3160e8a..b711d4bd33 100644 --- a/.jenkins/projectBuilder.Jenkinsfile +++ b/.jenkins/projectBuilder.Jenkinsfile @@ -48,6 +48,7 @@ projectBuilderV5 ( API_BASE_URL: '@vault(secret/configs/upgrade/${environment}/API_BASE_URL)', BASE_HREF_PREFIX: '@vault(secret/configs/upgrade/${environment}/BASE_HREF_PREFIX)', GOOGLE_CLIENT_ID: '@vault(secret/configs/upgrade/${environment}/GOOGLE_CLIENT_ID)', + GOOGLE_SERVICE_ACCOUNT_ID: '@vault(secret/configs/upgrade/${environment}/GOOGLE_SERVICE_ACCOUNT_ID)', ], ], "scheduler-lambda": [ diff --git a/backend/packages/Upgrade/.env.docker.local.example b/backend/packages/Upgrade/.env.docker.local.example index 10f9b36661..bb5c4717b8 100644 --- a/backend/packages/Upgrade/.env.docker.local.example +++ b/backend/packages/Upgrade/.env.docker.local.example @@ -55,9 +55,12 @@ SWAGGER_API=**/controllers/*.ts SWAGGER_JSON=/swagger.json # -# Google Client Id +# Google Authentication Configuration +# Note: GOOGLE_CLIENT_ID and GOOGLE_SERVICE_ACCOUNT_ID can be a single ID or multiple comma-separated IDs (no spaces) +# Example: id1,id2,id3 # GOOGLE_CLIENT_ID=google_project_id +GOOGLE_SERVICE_ACCOUNT_ID=google_service_account_id DOMAIN_NAME=domain_name SCHEDULER_STEP_FUNCTION=arn_name AWS_REGION=aws-region diff --git a/backend/packages/Upgrade/.env.example b/backend/packages/Upgrade/.env.example index 315c9e82ed..9ee235cd3e 100644 --- a/backend/packages/Upgrade/.env.example +++ b/backend/packages/Upgrade/.env.example @@ -54,9 +54,12 @@ SWAGGER_API=**/controllers/*.ts SWAGGER_JSON=/swagger.json # -# Google Client Id +# Google Authentication Configuration +# Note: GOOGLE_CLIENT_ID and GOOGLE_SERVICE_ACCOUNT_ID can be a single ID or multiple comma-separated IDs (no spaces) +# Example: id1,id2,id3 # GOOGLE_CLIENT_ID=google_project_id +GOOGLE_SERVICE_ACCOUNT_ID=google_service_account_id DOMAIN_NAME=domain_name SCHEDULER_STEP_FUNCTION=arn_name AWS_REGION=aws-region diff --git a/backend/packages/Upgrade/.env.test b/backend/packages/Upgrade/.env.test index 22b925bd5a..b43efe9d7d 100644 --- a/backend/packages/Upgrade/.env.test +++ b/backend/packages/Upgrade/.env.test @@ -67,6 +67,7 @@ MONITOR_PASSWORD= # Google Client Id # GOOGLE_CLIENT_ID=google_project_id +GOOGLE_SERVICE_ACCOUNT_ID=google_service_account_id DOMAIN_NAME = SCHEDULER_STEP_FUNCTION=arn:aws:states:us-east-1:781188149671:stateMachine:development-upgrade-experiment-scheduler # SCHEDULER_STEP_FUNCTION=arn:aws:states:us-east-1:781188149671:stateMachine:staging-upgrade-experiment-scheduler diff --git a/backend/packages/Upgrade/src/auth/AuthService.ts b/backend/packages/Upgrade/src/auth/AuthService.ts index e47e2d0799..1ba8a48444 100644 --- a/backend/packages/Upgrade/src/auth/AuthService.ts +++ b/backend/packages/Upgrade/src/auth/AuthService.ts @@ -20,25 +20,31 @@ export class AuthService { } public async validateUser(token: string, request: express.Request): Promise { - const client = new OAuth2Client(env.google.clientId); - request.logger.info({ message: 'Validating Token' }); + // env.google.clientId can be a single client ID or multiple comma-separated client IDs + const clientIds = env.google.clientId.split(','); + const client = new OAuth2Client(clientIds[0]); + request.logger.info({ message: 'Validating token' }); let payload; try { // First, try to verify the token as an ID token const ticket = await client.verifyIdToken({ idToken: token, - audience: env.google.clientId, // Specify the CLIENT_ID of the app that accesses the backend - // Or, if multiple clients access the backend: - // [CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3] + audience: clientIds, }); payload = ticket.getPayload(); - request.logger.info({ message: 'ID Token Validated' }); + request.logger.info({ message: 'ID token validated' }); } catch (error) { // If ID token verification fails, try to verify it as an access token try { - await client.getTokenInfo(token); - request.logger.info({ message: 'Access Token Validated' }); + // env.google.serviceAccountId can be a single service account ID or multiple comma-separated service account IDs + const serviceAccountIds = env.google.serviceAccountId.split(','); + const tokenInfo = await client.getTokenInfo(token); + + if (!tokenInfo || !serviceAccountIds.includes(tokenInfo.aud)) { + throw new Error('Invalid or unauthorized access token'); + } + request.logger.info({ message: 'Access token validated' }); // For service account access tokens, we'll return null // We might want to implement specific handling for service accounts here return null; diff --git a/backend/packages/Upgrade/src/env.ts b/backend/packages/Upgrade/src/env.ts index 2dbe91fd1c..e68e531649 100644 --- a/backend/packages/Upgrade/src/env.ts +++ b/backend/packages/Upgrade/src/env.ts @@ -79,6 +79,7 @@ export const env = { }, google: { clientId: getOsEnv('GOOGLE_CLIENT_ID'), + serviceAccountId: getOsEnv('GOOGLE_SERVICE_ACCOUNT_ID'), domainName: getOsEnvOptional('DOMAIN_NAME'), }, scheduler: { diff --git a/cloudformation/backend/app-infrastructure.yml b/cloudformation/backend/app-infrastructure.yml index d3367ec341..ae061b69f9 100644 --- a/cloudformation/backend/app-infrastructure.yml +++ b/cloudformation/backend/app-infrastructure.yml @@ -205,6 +205,8 @@ Resources: ValueFrom: !Sub arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/UPGRADE_CLIENT_API_SECRET - Name: GOOGLE_CLIENT_ID ValueFrom: !Sub arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/UPGRADE_GOOGLE_CLIENT_ID + - Name: GOOGLE_SERVICE_ACCOUNT_ID + ValueFrom: !Sub arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/UPGRADE_GOOGLE_SERVICE_ACCOUNT_ID - Name: RDS_PASSWORD ValueFrom: !Sub arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/UPGRADE_DB_PASSWORD - Name: RDS_USERNAME