diff --git a/tdrive/backend/node/src/services/console/clients/remote-jwks-verifier.ts b/tdrive/backend/node/src/services/console/clients/remote-jwks-verifier.ts index 9ce0e052..36d079d1 100644 --- a/tdrive/backend/node/src/services/console/clients/remote-jwks-verifier.ts +++ b/tdrive/backend/node/src/services/console/clients/remote-jwks-verifier.ts @@ -100,9 +100,7 @@ export class OidcJwtVerifier { async verifyLogoutToken(logoutTokenString: string) { const jwt = await this.verifyAsPromise(logoutTokenString); - // verifyAudience(expectedClientId, jwt.claims.aud); - // verifyIssuer(this.issuer, jwt.claims.iss); - + logger.info(`issuer is ${this.issuer} -- ${jwt.claims.iss} -- ${JSON.stringify(jwt)}`); return jwt; } } diff --git a/tdrive/backend/node/src/services/console/clients/remote.ts b/tdrive/backend/node/src/services/console/clients/remote.ts index 1ffc9105..e6d99578 100644 --- a/tdrive/backend/node/src/services/console/clients/remote.ts +++ b/tdrive/backend/node/src/services/console/clients/remote.ts @@ -281,16 +281,36 @@ export class ConsoleRemoteClient implements ConsoleServiceClient { async backChannelLogout(logoutToken: string): Promise { const payload = await this.verifier.verifyLogoutToken(logoutToken); - if (!payload.iss || !payload.aud || !payload.iat || !payload.jti || !payload.events) { - throw new CrudException("Missing required claims", 400); + if (!payload.iss) { + throw new CrudException("Missing required 'iss' claim", 400); + } + + if (!payload.aud) { + throw new CrudException("Missing required 'aud' claim", 400); + } + + if (!payload.iat) { + throw new CrudException("Missing required 'iat' claim", 400); + } + + if (!payload.jti) { + throw new CrudException("Missing required 'jti' claim", 400); + } + + if (!payload.events) { + throw new CrudException("Missing required 'events' claim", 400); } if (payload.nonce) { throw new CrudException("Nonce claim is prohibited", 400); } - if (!payload.sub && !payload.sid) { - throw new CrudException("Missing sub or sid claim", 400); + if (!payload.sub) { + throw new CrudException("Missing 'sub' claim", 400); + } + + if (!payload.sid) { + throw new CrudException("Missing 'sid' claim", 400); } const sessionRepository = gr.services.console.getSessionRepo(); diff --git a/tdrive/backend/node/test/unit/services/console/clients/remote.test.ts b/tdrive/backend/node/test/unit/services/console/clients/remote.test.ts new file mode 100644 index 00000000..af2a7cd2 --- /dev/null +++ b/tdrive/backend/node/test/unit/services/console/clients/remote.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from "@jest/globals"; +// @ts-ignore +import _ from "lodash"; +import { ConsoleRemoteClient } from "../../../../../src/services/console/clients/remote"; +import { OidcJwtVerifier } from "../../../../../src/services/console/clients/remote-jwks-verifier"; +import { ConsoleServiceImpl } from "../../../../../src/services/console/service"; + +describe("Test back-channel logout", () => { + + const subj = new ConsoleRemoteClient(new ConsoleServiceImpl({ + audience: "", + authority: "", + client_id: "", + client_secret: "", + disable_account_creation: false, + jwks_uri: "", + redirect_uris: [], + type: undefined, + issuer: "" })); + const verifyTokenMock = jest.spyOn(OidcJwtVerifier.prototype, "verifyLogoutToken"); + + const validToken = { + "sid": "Ev81/QODom0HXu9MGjCwnn1tDTpji+j0jopjRAE0ypo", + "aud": [ + "twakedrive" + ], + "sub": "shepilov3@stg.lin-saas.com", + "iat": 1716986356, + "jti": "F9R6DYPE", + "events": { + "http://schemas.openid.net/event/backchannel-logout": {} + }, + "iss": "https://sign-up.stg.lin-saas.com/" + }; + + const mockTokenWithoutProperty = (prop: string) => { + const token = _.cloneDeep(validToken); + token[prop] = null + verifyTokenMock.mockImplementation(() => { + return Promise.resolve(token); // Return the predefined payload + }); + } + + it("backChannelLogout should throw error if 'iss' claim is missing", () => { + //given + mockTokenWithoutProperty("iss") + //when + let logout = subj.backChannelLogout(''); + //then + expect(logout).rejects.toThrow("Missing required 'iss' claim"); + + }); + + it("backChannelLogout should throw error if 'aud' claim is missing", () => { + //given + mockTokenWithoutProperty("aud") + //when + let logout = subj.backChannelLogout(''); + //then + expect(logout).rejects.toThrow("Missing required 'aud' claim"); + }); + + it("backChannelLogout should throw error if 'jti' claim is missing", () => { + //given + mockTokenWithoutProperty("jti") + //when + let logout = subj.backChannelLogout(''); + //then + expect(logout).rejects.toThrow("Missing required 'jti' claim"); + }); + + it("backChannelLogout should throw error if 'iat' claim is missing", () => { + //given + mockTokenWithoutProperty("iat") + //when + let logout = subj.backChannelLogout(''); + //then + expect(logout).rejects.toThrow("Missing required 'iat' claim"); + }); + + it("backChannelLogout should throw error if 'events' claim is missing", () => { + //given + mockTokenWithoutProperty("events") + //when + let logout = subj.backChannelLogout(''); + //then + expect(logout).rejects.toThrowError("Missing required 'events' claim"); + }); + + it("backChannelLogout should throw error if 'sub' claim is missing", () => { + //given + mockTokenWithoutProperty("sub") + //when + let logout = subj.backChannelLogout(''); + //then + expect(logout).rejects.toThrowError("Missing 'sub' claim"); + }); + + it("backChannelLogout should throw error if 'sid' claim is missing", () => { + //given + mockTokenWithoutProperty("sid") + //when + let logout = subj.backChannelLogout(''); + //then + expect(logout).rejects.toThrowError("Missing 'sid' claim"); + }); +}) \ No newline at end of file