diff --git a/tests/api/specs/hasura/app-verification.spec.ts b/tests/api/specs/hasura/app-verification.spec.ts index 5ed5fb4ee..6b7616205 100644 --- a/tests/api/specs/hasura/app-verification.spec.ts +++ b/tests/api/specs/hasura/app-verification.spec.ts @@ -20,7 +20,8 @@ describe.skip("Hasura API - App Verification", () => { beforeAll(async () => { // Set required AWS environment variables for tests - process.env.ASSETS_S3_BUCKET_NAME = "world-id-assets-staging"; + process.env.ASSETS_S3_BUCKET_NAME = + process.env.ASSETS_S3_BUCKET_NAME || "world-id-assets-staging"; process.env.ASSETS_S3_REGION = "us-east-1"; // Create test app and metadata diff --git a/tests/api/specs/hasura/get-image.spec.ts b/tests/api/specs/hasura/get-image.spec.ts index 4e2c9e2cf..39c933be7 100644 --- a/tests/api/specs/hasura/get-image.spec.ts +++ b/tests/api/specs/hasura/get-image.spec.ts @@ -36,7 +36,8 @@ describe("Hasura API - Get Image", () => { beforeAll(async () => { // Set required AWS environment variables for tests - process.env.ASSETS_S3_BUCKET_NAME = "world-id-assets-staging"; + process.env.ASSETS_S3_BUCKET_NAME = + process.env.ASSETS_S3_BUCKET_NAME || "world-id-assets-staging"; process.env.ASSETS_S3_REGION = "us-east-1"; // Create test team and user diff --git a/tests/api/specs/hasura/get-unverified-images.spec.ts b/tests/api/specs/hasura/get-unverified-images.spec.ts index 0a33a3550..1d692141d 100644 --- a/tests/api/specs/hasura/get-unverified-images.spec.ts +++ b/tests/api/specs/hasura/get-unverified-images.spec.ts @@ -32,7 +32,8 @@ describe("Hasura API - Get Unverified Images", () => { beforeAll(async () => { // Set required AWS environment variables for tests - process.env.ASSETS_S3_BUCKET_NAME = "world-id-assets-staging"; + process.env.ASSETS_S3_BUCKET_NAME = + process.env.ASSETS_S3_BUCKET_NAME || "world-id-assets-staging"; process.env.ASSETS_S3_REGION = "us-east-1"; // Create test team and user diff --git a/web/api/helpers/kms-eth.ts b/web/api/helpers/kms-eth.ts index 0644cf1ca..d4e09d4d2 100644 --- a/web/api/helpers/kms-eth.ts +++ b/web/api/helpers/kms-eth.ts @@ -22,7 +22,7 @@ import { toBeHex, zeroPadValue, } from "ethers"; -import { scheduleKeyDeletion } from "./kms"; +import { resolveKeyId, scheduleKeyDeletion } from "./kms"; // secp256k1 curve order const SECP256K1_N = BigInt( @@ -187,7 +187,7 @@ export async function getEthAddressFromKMS( keyId: string, ): Promise { const { PublicKey } = await client.send( - new GetPublicKeyCommand({ KeyId: keyId }), + new GetPublicKeyCommand({ KeyId: resolveKeyId(keyId) }), ); if (!PublicKey) { @@ -214,7 +214,7 @@ async function signWithKms( ): Promise { const { Signature: derSignature } = await client.send( new SignCommand({ - KeyId: keyId, + KeyId: resolveKeyId(keyId), Message: digest, MessageType: "DIGEST", SigningAlgorithm: "ECDSA_SHA_256", @@ -273,7 +273,7 @@ export async function createManagerKey( }), ); - const keyId = KeyMetadata?.KeyId; + const keyId = KeyMetadata?.Arn ?? KeyMetadata?.KeyId; const createdAt = KeyMetadata?.CreationDate; if (!keyId || !createdAt) { diff --git a/web/api/helpers/kms.ts b/web/api/helpers/kms.ts index 75a1ee7c6..fbe26d8e2 100644 --- a/web/api/helpers/kms.ts +++ b/web/api/helpers/kms.ts @@ -31,6 +31,30 @@ export const getKMSClient = async (region?: string) => { }); }; +/** + * Resolves a KMS key reference to a full ARN for cross-account access. + * + * - Full ARN (`arn:aws:kms:...`): returned as-is (new keys or already resolved) + * - Bare key ID (UUID): if `KMS_LEGACY_ACCOUNT_ID` is set, constructs a full + * ARN pointing to the legacy account; otherwise returns as-is + * + * This enables a seamless migration period where the application runs in a new + * AWS account but still uses KMS keys that remain in the old account. + */ +export function resolveKeyId(keyId: string, region?: string): string { + if (keyId.startsWith("arn:")) { + return keyId; + } + + const legacyAccountId = process.env.KMS_LEGACY_ACCOUNT_ID; + if (!legacyAccountId) { + return keyId; + } + + const resolvedRegion = region ?? process.env.AWS_REGION_NAME ?? "eu-west-1"; + return `arn:aws:kms:${resolvedRegion}:${legacyAccountId}:key/${keyId}`; +} + export const createKMSKey = async ( client: KMSClient, alg: KeySpec, @@ -45,7 +69,7 @@ export const createKMSKey = async ( }), ); - const keyId = KeyMetadata?.KeyId; + const keyId = KeyMetadata?.Arn ?? KeyMetadata?.KeyId; const createdAt = KeyMetadata?.CreationDate; if (keyId && createdAt) { @@ -70,7 +94,7 @@ export const getKMSKeyStatus = async (client: KMSClient, keyId: string) => { try { const { KeyMetadata } = await client.send( new DescribeKeyCommand({ - KeyId: keyId, + KeyId: resolveKeyId(keyId), }), ); return KeyMetadata?.Enabled; @@ -97,7 +121,7 @@ export const signJWTWithKMSKey = async ( const response = await client.send( new SignCommand({ - KeyId: kms_id, + KeyId: resolveKeyId(kms_id), Message: new Uint8Array(Buffer.from(encodedHeaderPayload)), MessageType: "RAW", SigningAlgorithm: "RSASSA_PKCS1_V1_5_SHA_256", @@ -122,7 +146,7 @@ export const scheduleKeyDeletion = async (client: KMSClient, keyId: string) => { try { await client.send( new ScheduleKeyDeletionCommand({ - KeyId: keyId, + KeyId: resolveKeyId(keyId), PendingWindowInDays: 7, // Note: 7 is the minimum allowed value }), ); diff --git a/web/lib/utils.ts b/web/lib/utils.ts index 0914d62aa..a07b7682b 100644 --- a/web/lib/utils.ts +++ b/web/lib/utils.ts @@ -259,8 +259,12 @@ export const isValidHostName = (request: Request) => { return false; } - // Skip check for development - if (process.env.NODE_ENV === "development") { + // Skip check for development and staging (staging may not have custom domains) + // TODO(CORPLAT-689): Remove staging bypass after DNS cutover + if ( + process.env.NODE_ENV === "development" || + process.env.NEXT_PUBLIC_APP_ENV === "staging" + ) { return true; } const cdnHost = process.env.NEXT_PUBLIC_IMAGES_CDN_URL;