diff --git a/package.json b/package.json index 356f462..e6cf719 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "qs": "^6.16.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "tc-core-library-js": "github:topcoder-platform/tc-core-library-js#v4.0.0-rc.2", + "tc-core-library-js": "https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/c2df92b2227089909ab6e229df0ec6b658bb431a", "uuid": "^14.0.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7aad966..dbe965a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,8 +109,8 @@ importers: specifier: ^7.8.1 version: 7.8.2 tc-core-library-js: - specifier: github:topcoder-platform/tc-core-library-js#v4.0.0-rc.2 - version: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/58de37670180e183e8378c3d203df0112a0414f6(supports-color@8.1.1) + specifier: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/c2df92b2227089909ab6e229df0ec6b658bb431a + version: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/c2df92b2227089909ab6e229df0ec6b658bb431a(supports-color@8.1.1) uuid: specifier: 14.0.1 version: 14.0.1 @@ -3482,9 +3482,9 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/58de37670180e183e8378c3d203df0112a0414f6: - resolution: {gitHosted: true, tarball: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/58de37670180e183e8378c3d203df0112a0414f6} - version: 4.0.0-rc.2 + tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/c2df92b2227089909ab6e229df0ec6b658bb431a: + resolution: {gitHosted: true, integrity: sha512-n+4KUOeV4LEIsTI/R216dBc6bUkbSyO/2O0LpoY3Hk7eqKsRQFUHloB3y0hfK/v1rzf8qYQxTdv8ikeRGBn4pQ==, tarball: https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/c2df92b2227089909ab6e229df0ec6b658bb431a} + version: 4.0.0-rc.2.1 engines: {node: '>=22.13'} terser-webpack-plugin@5.6.1: @@ -7547,7 +7547,7 @@ snapshots: - bare-abort-controller - react-native-b4a - tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/58de37670180e183e8378c3d203df0112a0414f6(supports-color@8.1.1): + tc-core-library-js@https://codeload.github.com/topcoder-platform/tc-core-library-js/tar.gz/c2df92b2227089909ab6e229df0ec6b658bb431a(supports-color@8.1.1): dependencies: '@types/jsonwebtoken': 9.0.10 jsonwebtoken: 9.0.3 diff --git a/src/shared/modules/global/jwt.service.spec.ts b/src/shared/modules/global/jwt.service.spec.ts index 5b44ae0..46a959a 100644 --- a/src/shared/modules/global/jwt.service.spec.ts +++ b/src/shared/modules/global/jwt.service.spec.ts @@ -66,6 +66,7 @@ describe('JwtService validation', () => { }); const token = sign( { + emailVerified: true, roles: [UserRole.Admin], userId: '12345', scope: 'openid profile offline_access', @@ -197,6 +198,7 @@ describe('JwtService validation', () => { }); const token = sign( { + emailVerified: true, scope: Scope.ReadGroups, gty: 'client-credentials', roles: [UserRole.Admin], @@ -249,7 +251,7 @@ describe('JwtService validation', () => { getPublicKey: () => publicKey.export({ type: 'spki', format: 'pem' }), }); const token = sign( - { roles: [UserRole.Admin], userId: '12345' }, + { emailVerified: true, roles: [UserRole.Admin], userId: '12345' }, privateKey, { algorithm: 'RS256', @@ -271,13 +273,17 @@ describe('JwtService validation', () => { it('rejects an unlisted issuer before requesting its signing key', async () => { const signingKeySpy = jest.spyOn(JwksClient.prototype, 'getSigningKey'); - const token = sign({ roles: [UserRole.Admin] }, privateKey, { - algorithm: 'RS256', - keyid: 'test-key-id', - issuer: 'https://unlisted-issuer.example/', - audience: AUDIENCE, - expiresIn: 300, - }); + const token = sign( + { emailVerified: true, roles: [UserRole.Admin] }, + privateKey, + { + algorithm: 'RS256', + keyid: 'test-key-id', + issuer: 'https://unlisted-issuer.example/', + audience: AUDIENCE, + expiresIn: 300, + }, + ); await expect(service.validateToken(token)).rejects.toBeInstanceOf( UnauthorizedException, @@ -287,12 +293,16 @@ describe('JwtService validation', () => { it('rejects an RS256 token without a key ID', async () => { const signingKeySpy = jest.spyOn(JwksClient.prototype, 'getSigningKey'); - const token = sign({ roles: [UserRole.Admin] }, privateKey, { - algorithm: 'RS256', - issuer: ALLOWED_ISSUER, - audience: AUDIENCE, - expiresIn: 300, - }); + const token = sign( + { emailVerified: true, roles: [UserRole.Admin] }, + privateKey, + { + algorithm: 'RS256', + issuer: ALLOWED_ISSUER, + audience: AUDIENCE, + expiresIn: 300, + }, + ); await expect(service.validateToken(token)).rejects.toBeInstanceOf( UnauthorizedException, @@ -303,6 +313,7 @@ describe('JwtService validation', () => { it('accepts a legacy HS256 admin token without kid or audience', async () => { const token = sign( { + emailVerified: true, roles: [UserRole.Admin], userId: '12345', handle: 'admin-user', @@ -326,11 +337,15 @@ describe('JwtService validation', () => { }); it('rejects an HS256 token signed with a different secret', async () => { - const token = sign({ roles: [UserRole.Admin] }, 'wrong-secret', { - algorithm: 'HS256', - issuer: ALLOWED_ISSUER, - expiresIn: 300, - }); + const token = sign( + { emailVerified: true, roles: [UserRole.Admin] }, + 'wrong-secret', + { + algorithm: 'HS256', + issuer: ALLOWED_ISSUER, + expiresIn: 300, + }, + ); await expect(service.validateToken(token)).rejects.toBeInstanceOf( UnauthorizedException, @@ -339,11 +354,15 @@ describe('JwtService validation', () => { it('still verifies signatures when NODE_ENV is missing', async () => { delete process.env.NODE_ENV; - const token = sign({ roles: [UserRole.Admin] }, 'wrong-secret', { - algorithm: 'HS256', - issuer: ALLOWED_ISSUER, - expiresIn: 300, - }); + const token = sign( + { emailVerified: true, roles: [UserRole.Admin] }, + 'wrong-secret', + { + algorithm: 'HS256', + issuer: ALLOWED_ISSUER, + expiresIn: 300, + }, + ); await expect(service.validateToken(token)).rejects.toBeInstanceOf( UnauthorizedException, @@ -352,11 +371,15 @@ describe('JwtService validation', () => { it('rejects an HS256 token when AUTH_SECRET is unavailable', async () => { AuthConfig.authSecret = undefined; - const token = sign({ roles: [UserRole.Admin] }, AUTH_SECRET, { - algorithm: 'HS256', - issuer: ALLOWED_ISSUER, - expiresIn: 300, - }); + const token = sign( + { emailVerified: true, roles: [UserRole.Admin] }, + AUTH_SECRET, + { + algorithm: 'HS256', + issuer: ALLOWED_ISSUER, + expiresIn: 300, + }, + ); await expect(service.validateToken(token)).rejects.toBeInstanceOf( UnauthorizedException, @@ -364,11 +387,15 @@ describe('JwtService validation', () => { }); it('rejects an expired HS256 admin token', async () => { - const token = sign({ roles: [UserRole.Admin] }, AUTH_SECRET, { - algorithm: 'HS256', - issuer: ALLOWED_ISSUER, - expiresIn: -60, - }); + const token = sign( + { emailVerified: true, roles: [UserRole.Admin] }, + AUTH_SECRET, + { + algorithm: 'HS256', + issuer: ALLOWED_ISSUER, + expiresIn: -60, + }, + ); await expect(service.validateToken(token)).rejects.toBeInstanceOf( UnauthorizedException, @@ -378,6 +405,7 @@ describe('JwtService validation', () => { it('accepts an HS256 admin token with a non-M2M audience', async () => { const token = sign( { + emailVerified: true, roles: [UserRole.Admin], userId: '12345', scope: 'openid profile offline_access', @@ -423,11 +451,15 @@ describe('JwtService validation', () => { }); it('rejects unsupported signing algorithms', async () => { - const token = sign({ roles: [UserRole.Admin] }, AUTH_SECRET, { - algorithm: 'HS384', - issuer: ALLOWED_ISSUER, - expiresIn: 300, - }); + const token = sign( + { emailVerified: true, roles: [UserRole.Admin] }, + AUTH_SECRET, + { + algorithm: 'HS384', + issuer: ALLOWED_ISSUER, + expiresIn: 300, + }, + ); await expect(service.validateToken(token)).rejects.toBeInstanceOf( UnauthorizedException,