diff --git a/package.json b/package.json index aea52771eca..db416b84e90 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@peculiar/x509": "1.9.6", "@wireapp/avs": "9.6.11", "@wireapp/commons": "5.2.4", - "@wireapp/core": "44.0.13", + "@wireapp/core": "45.0.0", "@wireapp/react-ui-kit": "9.15.4", "@wireapp/store-engine-dexie": "2.1.7", "@wireapp/webapp-events": "0.20.1", @@ -36,7 +36,7 @@ "long": "5.2.3", "markdown-it": "14.0.0", "murmurhash": "2.0.1", - "oidc-client-ts": "^2.4.0", + "oidc-client-ts": "3.0.1", "platform": "1.3.6", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/src/script/E2EIdentity/E2EIdentityEnrollment.test.ts b/src/script/E2EIdentity/E2EIdentityEnrollment.test.ts index 461a3e2df67..f118ab804a5 100644 --- a/src/script/E2EIdentity/E2EIdentityEnrollment.test.ts +++ b/src/script/E2EIdentity/E2EIdentityEnrollment.test.ts @@ -26,7 +26,7 @@ import {UserState} from 'src/script/user/UserState'; import {getCertificateDetails} from 'Util/certificateDetails'; import * as util from 'Util/util'; -import {E2EIHandler, E2EIHandlerStep} from './E2EIdentityEnrollment'; +import {E2EIHandler} from './E2EIdentityEnrollment'; import {hasActiveCertificate} from './E2EIdentityVerification'; import {getModalOptions, ModalType} from './Modals'; import {OIDCServiceStore} from './OIDCService/OIDCServiceStorage'; @@ -122,26 +122,6 @@ describe('E2EIHandler', () => { void instance.attemptEnrollment(); await wait(1); expect(container.resolve(Core).service?.e2eIdentity?.initialize).toHaveBeenCalled(); - expect(instance['currentStep']).toBe(E2EIHandlerStep.INITIALIZED); - }); - - it('should set currentStep to SUCCESS when enrollE2EI is called and enrollment succeeds', async () => { - jest.spyOn(container.resolve(Core), 'enrollE2EI').mockResolvedValueOnce({status: 'successful'}); - - const instance = await E2EIHandler.getInstance().initialize(params); - void instance['enroll'](); - await wait(1); - expect(instance['currentStep']).toBe(E2EIHandlerStep.SUCCESS); - }); - - it('should set currentStep to ERROR when enrolE2EI is called and enrolment fails', async () => { - // Mock the Core service to return an error - jest.spyOn(container.resolve(Core), 'enrollE2EI').mockImplementationOnce(jest.fn(() => Promise.reject())); - - const instance = await E2EIHandler.getInstance().initialize(params); - void instance['enroll'](); - await wait(1); - expect(instance['currentStep']).toBe(E2EIHandlerStep.ERROR); }); it('should display user info message when initialized', async () => { @@ -156,7 +136,7 @@ describe('E2EIHandler', () => { }); it('should throw error if trying to enroll with no config given', async () => { - await expect(E2EIHandler.getInstance().enroll()).rejects.toEqual( + await expect(E2EIHandler.getInstance()['enroll']()).rejects.toEqual( new Error('Trying to enroll for E2EI without initializing the E2EIHandler'), ); }); @@ -229,7 +209,7 @@ describe('E2EIHandler', () => { jest.spyOn(container.resolve(Core).service!.e2eIdentity!, 'isEnrollmentInProgress').mockResolvedValue(true); // Spy on enroll to check if it's called - const enrollSpy = jest.spyOn(handler, 'enroll'); + const enrollSpy = jest.spyOn(handler as any, 'enroll'); // Initialize E2EI await handler.initialize(params); diff --git a/src/script/E2EIdentity/E2EIdentityEnrollment.ts b/src/script/E2EIdentity/E2EIdentityEnrollment.ts index ccee6652e57..963d45a332d 100644 --- a/src/script/E2EIdentity/E2EIdentityEnrollment.ts +++ b/src/script/E2EIdentity/E2EIdentityEnrollment.ts @@ -19,7 +19,6 @@ import {TimeInMillis} from '@wireapp/commons/lib/util/TimeUtil'; import {amplify} from 'amplify'; -import {User} from 'oidc-client-ts'; import {container} from 'tsyringe'; import {TypedEventEmitter} from '@wireapp/commons'; @@ -72,7 +71,6 @@ export class E2EIHandler extends TypedEventEmitter { private readonly core = container.resolve(Core); private readonly userState = container.resolve(UserState); private config?: EnrollmentConfig; - private currentStep: E2EIHandlerStep = E2EIHandlerStep.UNINITIALIZED; private oidcService?: OIDCService; public certificateTtl?: number; @@ -119,7 +117,7 @@ export class E2EIHandler extends TypedEventEmitter { * @returns */ public isE2EIEnabled() { - return this.currentStep !== E2EIHandlerStep.UNINITIALIZED; + return !!this.config; } public async initialize({discoveryUrl, gracePeriodInSeconds}: E2EIHandlerParams) { @@ -136,7 +134,6 @@ export class E2EIHandler extends TypedEventEmitter { await this.coreE2EIService.initialize(discoveryUrl); - this.currentStep = E2EIHandlerStep.INITIALIZED; this.emit('initialized', {enrollmentConfig: this.config}); return this; } @@ -200,15 +197,7 @@ export class E2EIHandler extends TypedEventEmitter { */ private async renewCertificate(): Promise { try { - this.oidcService = this.createOIDCService(); - // Use the oidc service to get the user data via silent authentication (refresh token) - const userData = await this.oidcService.handleSilentAuthentication(); - - if (!userData) { - throw new Error('Received no user data from OIDC service'); - } - // renew without user action - await this.enroll(userData); + await this.enroll(true); } catch (error) { this.logger.error('Silent authentication with refresh token failed', error); @@ -252,29 +241,39 @@ export class E2EIHandler extends TypedEventEmitter { await this.coreE2EIService.clearAllProgress(); } - public async enroll(userData?: User) { + private async getOAuthToken( + silent: boolean, + challengeData?: {keyAuth: string; challenge: {url: string; target: string}}, + ) { + let userData; + + if (challengeData) { + // If a challengeData is provided, that means we are at the beginning of the enrollment process + // We need to first authenticate the user (either silently if we are renewing the certificate, or by redirection if it an initial enrollment) + const {challenge, keyAuth} = challengeData; + OIDCServiceStore.store.targetURL(challenge.target); + const oidcService = this.createOIDCService(); + userData = await oidcService.authenticate(keyAuth, challenge.url, silent); + } else { + const oidcService = this.createOIDCService(); + // If there is no challengeData, that means we have already authenticated the user and we just need to get the userdata + userData = await oidcService.getUser(); + } + if (!userData) { + throw new Error('No user data received'); + } + return userData.id_token; + } + + private async enroll(attemptSilentAuth = false) { if (!this.config) { throw new Error('Trying to enroll for E2EI without initializing the E2EIHandler'); } try { // Notify user about E2EI enrolment in progress - this.currentStep = E2EIHandlerStep.ENROLL; const isCertificateRenewal = await hasActiveCertificate(); this.showLoadingMessage(); - if (!userData) { - // If the enrolment is in progress, we need to get the id token from the oidc service, since oauth should have already been completed - if (await this.coreE2EIService.isEnrollmentInProgress()) { - // The redirect-url which is needed inside the OIDCService is stored in the OIDCServiceStore previously - this.oidcService = this.createOIDCService(); - userData = await this.oidcService.handleAuthentication(); - if (!userData) { - throw new Error('Received no user data from OIDC service'); - } - } - } - const oAuthIdToken = userData?.id_token; - const displayName = this.userState.self()?.name(); const handle = this.userState.self()?.username(); const teamId = this.userState.self()?.teamId; @@ -282,36 +281,28 @@ export class E2EIHandler extends TypedEventEmitter { if (!displayName || !handle || !teamId) { throw new Error('Username, handle or teamId not found'); } - const enrollmentState = await this.core.enrollE2EI({ + + await this.core.enrollE2EI({ discoveryUrl: this.config.discoveryUrl, displayName, handle, teamId, - oAuthIdToken, + getOAuthToken: async authenticationChallenge => { + return this.getOAuthToken(attemptSilentAuth, authenticationChallenge); + }, certificateTtl: this.certificateTtl, }); - // If the data is false or we dont get the ACMEChallenge, enrolment failed - - if (enrollmentState.status === 'authentication') { - // If the data is authentication flow data, we need to kick off the oauth flow to get an oauth token - const {challenge, keyAuth} = enrollmentState.authenticationChallenge; - OIDCServiceStore.store.targetURL(challenge.target); - this.oidcService = this.createOIDCService(); - await this.oidcService.authenticate(keyAuth, challenge.url); - } // Notify user about E2EI enrolment success // This setTimeout is needed because there was a timing with the success modal and the loading modal setTimeout(removeCurrentModal, 0); - this.currentStep = E2EIHandlerStep.SUCCESS; // clear the oidc service progress/data and successful enrolment await this.cleanUp(false); await this.showSuccessMessage(isCertificateRenewal); this.emit('identityUpdated', {enrollmentConfig: this.config!}); } catch (error) { - this.currentStep = E2EIHandlerStep.ERROR; this.logger.error('E2EI enrollment failed', error); setTimeout(removeCurrentModal, 0); @@ -320,10 +311,6 @@ export class E2EIHandler extends TypedEventEmitter { } private showLoadingMessage(isCertificateRenewal = false): void { - if (this.currentStep !== E2EIHandlerStep.ENROLL) { - return; - } - const {modalOptions, modalType} = getModalOptions({ type: ModalType.LOADING, hideClose: true, @@ -335,10 +322,6 @@ export class E2EIHandler extends TypedEventEmitter { } private async showSuccessMessage(isCertificateRenewal = false) { - if (this.currentStep !== E2EIHandlerStep.SUCCESS) { - return; - } - return new Promise(resolve => { const {modalOptions, modalType} = getModalOptions({ type: ModalType.SUCCESS, @@ -357,10 +340,6 @@ export class E2EIHandler extends TypedEventEmitter { } private async showErrorMessage(): Promise { - if (this.currentStep !== E2EIHandlerStep.ERROR) { - return; - } - // Remove the url parameters of the failed enrolment removeUrlParameters(); // Clear the oidc service progress @@ -376,12 +355,12 @@ export class E2EIHandler extends TypedEventEmitter { hideClose: true, hideSecondary: disableSnooze, primaryActionFn: async () => { - this.currentStep = E2EIHandlerStep.INITIALIZED; await this.enroll(); resolve(); }, secondaryActionFn: async () => { - await this.startEnrollment(ModalType.ENROLL); + this.config?.timer.snooze(); + this.showSnoozeConfirmationModal(); resolve(); }, extraParams: { @@ -407,7 +386,6 @@ export class E2EIHandler extends TypedEventEmitter { resolve(); }, secondaryActionFn: () => { - this.currentStep = E2EIHandlerStep.SNOOZE; this.config?.timer.snooze(); this.showSnoozeConfirmationModal(); resolve(); diff --git a/src/script/E2EIdentity/OIDCService/OIDCService.ts b/src/script/E2EIdentity/OIDCService/OIDCService.ts index b31c52006b3..24a8f7b3663 100644 --- a/src/script/E2EIdentity/OIDCService/OIDCService.ts +++ b/src/script/E2EIdentity/OIDCService/OIDCService.ts @@ -18,7 +18,7 @@ */ import {KeyAuth} from '@wireapp/core/lib/messagingProtocols/mls'; -import {UserManager, User, UserManagerSettings, WebStorageStateStore} from 'oidc-client-ts'; +import {UserManager, UserManagerSettings, WebStorageStateStore} from 'oidc-client-ts'; import {clearKeysStartingWith} from 'Util/localStorage'; @@ -61,7 +61,7 @@ export class OIDCService { this.userManager = new UserManager(dexioConfig); } - public async authenticate(keyAuth: KeyAuth, challengeUrl: string): Promise { + public async authenticate(keyAuth: KeyAuth, challengeUrl: string, silent: boolean) { // New claims value for keycloak const claims = { id_token: { @@ -70,22 +70,16 @@ export class OIDCService { }, }; - await this.userManager.signinRedirect({ - extraQueryParams: {shouldBeRedirectedByProxy: true, claims: JSON.stringify(claims)}, - }); + const params = {shouldBeRedirectedByProxy: true, claims: JSON.stringify(claims)}; + return silent + ? this.userManager.signinSilent({extraTokenParams: params}) + : this.userManager.signinRedirect({extraQueryParams: params}); } - public async handleAuthentication(): Promise { + public getUser() { // Remove the hash (hash router) from the url before processing const url = window.location.href.replace('/#', ''); - - const user = await this.userManager.signinCallback(url); - - if (!user) { - return undefined; - } - - return user; + return this.userManager.signinCallback(url); } public clearProgress(includeUserData: boolean = false): Promise { @@ -97,8 +91,4 @@ export class OIDCService { } return this.userManager.clearStaleState(); } - - public async handleSilentAuthentication() { - return this.userManager.signinSilent(); - } } diff --git a/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.tsx b/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.tsx index 85fc09eec2b..5c456f49b04 100644 --- a/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.tsx +++ b/src/script/components/MessagesList/Message/E2EIVerificationMessage/E2EIVerificationMessage.tsx @@ -86,7 +86,7 @@ export const E2EIVerificationMessage = ({message, conversation}: E2EIVerificatio const getCertificate = async () => { try { - await E2EIHandler.getInstance().enroll(); + await E2EIHandler.getInstance().attemptEnrollment(); } catch (error) { logger.error('Failed to enroll user certificate: ', error); } diff --git a/src/script/page/MainContent/panels/preferences/DevicesPreferences/components/E2EICertificateDetails/E2EICertificateDetails.tsx b/src/script/page/MainContent/panels/preferences/DevicesPreferences/components/E2EICertificateDetails/E2EICertificateDetails.tsx index ad1b75b05e1..1dbd0dc3695 100644 --- a/src/script/page/MainContent/panels/preferences/DevicesPreferences/components/E2EICertificateDetails/E2EICertificateDetails.tsx +++ b/src/script/page/MainContent/panels/preferences/DevicesPreferences/components/E2EICertificateDetails/E2EICertificateDetails.tsx @@ -50,7 +50,7 @@ export const E2EICertificateDetails = ({identity, isCurrentDevice}: E2EICertific const getCertificate = async () => { try { - await E2EIHandler.getInstance().enroll(); + await E2EIHandler.getInstance().attemptEnrollment(); } catch (error) { logger.error('Cannot get E2EI instance: ', error); } diff --git a/yarn.lock b/yarn.lock index 7a81ee361ad..b82e24012b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4888,20 +4888,20 @@ __metadata: languageName: node linkType: hard -"@wireapp/core-crypto@npm:1.0.0-rc.41": - version: 1.0.0-rc.41 - resolution: "@wireapp/core-crypto@npm:1.0.0-rc.41" - checksum: e9b41fac8ecaac3f4934aca8bd58deeae4f45aa597caa9c880bee6df2acf9a5973a06f93b459fb1b3005df30a258df6b5dc9dfda42c45ea4a22c6697e0d9596c +"@wireapp/core-crypto@npm:1.0.0-rc.43": + version: 1.0.0-rc.43 + resolution: "@wireapp/core-crypto@npm:1.0.0-rc.43" + checksum: 2481ae8a6322c999e5b5260c34dcb6162d79afa568c0cc444975d90828831acd35b759140e63d994d2cad6a82e74b482408b306460a32c028006c4689407ca3a languageName: node linkType: hard -"@wireapp/core@npm:44.0.13": - version: 44.0.13 - resolution: "@wireapp/core@npm:44.0.13" +"@wireapp/core@npm:45.0.0": + version: 45.0.0 + resolution: "@wireapp/core@npm:45.0.0" dependencies: "@wireapp/api-client": ^26.10.10 "@wireapp/commons": ^5.2.5 - "@wireapp/core-crypto": 1.0.0-rc.41 + "@wireapp/core-crypto": 1.0.0-rc.43 "@wireapp/cryptobox": 12.8.0 "@wireapp/promise-queue": ^2.2.10 "@wireapp/protocol-messaging": 1.44.0 @@ -4916,7 +4916,7 @@ __metadata: long: ^5.2.0 uuidjs: 4.2.13 zod: 3.22.4 - checksum: b20f6214dd0251e4cc41f24dd743feed0e69c1137350feb3a6b2267dd5e3615062d237c7e664f5f43d7028b851aa4ebbcb0a662568379e12bc846e5cb3c26c85 + checksum: cd5898cfc51c576de066e951a639c4bdaa9ecc3f7db54e4d7aec203ea7b267d2a27576fa42542f9e65325578c7317065fd8ecc07c567f6992ee03689801eb9f8 languageName: node linkType: hard @@ -6868,13 +6868,6 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:^4.2.0": - version: 4.2.0 - resolution: "crypto-js@npm:4.2.0" - checksum: f051666dbc077c8324777f44fbd3aaea2986f198fe85092535130d17026c7c2ccf2d23ee5b29b36f7a4a07312db2fae23c9094b644cc35f7858b1b4fcaf27774 - languageName: node - linkType: hard - "crypto-random-string@npm:^2.0.0": version: 2.0.0 resolution: "crypto-random-string@npm:2.0.0" @@ -11443,10 +11436,10 @@ __metadata: languageName: node linkType: hard -"jwt-decode@npm:^3.1.2": - version: 3.1.2 - resolution: "jwt-decode@npm:3.1.2" - checksum: 20a4b072d44ce3479f42d0d2c8d3dabeb353081ba4982e40b83a779f2459a70be26441be6c160bfc8c3c6eadf9f6380a036fbb06ac5406b5674e35d8c4205eeb +"jwt-decode@npm:^4.0.0": + version: 4.0.0 + resolution: "jwt-decode@npm:4.0.0" + checksum: 390e2edcb31a92e86c8cbdd1edeea4c0d62acd371f8a8f0a8878e499390c0ecf4c658b365c4e941e4ef37d0170e4ca650aaa49f99a45c0b9695a235b210154b0 languageName: node linkType: hard @@ -12870,13 +12863,12 @@ __metadata: languageName: node linkType: hard -"oidc-client-ts@npm:^2.4.0": - version: 2.4.0 - resolution: "oidc-client-ts@npm:2.4.0" +"oidc-client-ts@npm:3.0.1": + version: 3.0.1 + resolution: "oidc-client-ts@npm:3.0.1" dependencies: - crypto-js: ^4.2.0 - jwt-decode: ^3.1.2 - checksum: 8467db689298221f706d3358961efb0ddc789f6bd7d4765e71ae5fe62067999d2ce6e8e7584b9d991b8caa6f7fb383f75841e1cfa9e05808c34632de374f5e68 + jwt-decode: ^4.0.0 + checksum: 08a21a40304131642ffc6a4e9ecc3a51bf476967dfbf4b60dbdb84f79b42489fda2ba447d9276ecb575a4ca75d94f652deac38065553688bff614c9d763f6c03 languageName: node linkType: hard @@ -17624,7 +17616,7 @@ __metadata: "@wireapp/avs": 9.6.11 "@wireapp/commons": 5.2.4 "@wireapp/copy-config": 2.1.14 - "@wireapp/core": 44.0.13 + "@wireapp/core": 45.0.0 "@wireapp/eslint-config": 3.0.5 "@wireapp/prettier-config": 0.6.3 "@wireapp/react-ui-kit": 9.15.4 @@ -17682,7 +17674,7 @@ __metadata: markdown-it: 14.0.0 murmurhash: 2.0.1 node-fetch: 2.7.0 - oidc-client-ts: ^2.4.0 + oidc-client-ts: 3.0.1 os-browserify: 0.3.0 path-browserify: 1.0.1 platform: 1.3.6