Skip to content

Commit

Permalink
runfix: renew certificate device details [WPB-9193] (#17451)
Browse files Browse the repository at this point in the history
* Revert "fix: show enrollment modal for existing clients after e2ei activation [WPB-9816] (#17422)"

This reverts commit 72c8c63.

* runfix: old client can snooze the enrollment

* runfix: show the update button during the grace period

* runfix: always calculate the next fire date

* runfix: don't open the confirmation modal if delay <= 0

* test: fix tests

* test: show get cert button

* Revert "runfix: always calculate the next fire date"

This reverts commit b15d2c7.

* runfix: reset timers when updating the cert manually

* refactor: use certificate status

* runfix: pass 'is current device' flag

* runfix: update state after enrollment

* runfix: recalculate isSnoozable value when task runs

* refactor: improve naming

* runfix: get and update buttons visible at once

* test: update expired cert test

* test: expires soon cert
  • Loading branch information
PatrykBuniX committed May 24, 2024
1 parent b1c722a commit 6cf2a69
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 83 deletions.
40 changes: 30 additions & 10 deletions src/script/E2EIdentity/E2EIdentityEnrollment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
MLSStatuses,
} from './E2EIdentityVerification';
import {getEnrollmentStore} from './Enrollment.store';
import {getEnrollmentTimer} from './EnrollmentTimer';
import {getEnrollmentTimer, hasGracePeriodStartedForSelfClient} from './EnrollmentTimer';
import {getModalOptions, ModalType} from './Modals';
import {OIDCService} from './OIDCService';
import {OIDCServiceStore} from './OIDCService/OIDCServiceStorage';
Expand Down Expand Up @@ -149,7 +149,7 @@ export class E2EIHandler extends TypedEventEmitter<Events> {
// If we have an enrollment in progress, we can just finish it (meaning we are coming back from an idp redirect)
if (this.wasJustRedirected()) {
// We should not allow to snooze the enorollment if the client is still fresh and the user is coming back from an idp redirect
await this.enroll(!isFreshClient);
await this.enroll({snoozable: !isFreshClient});
} else {
// If we have an enrollment in progress but we are not coming back from an idp redirect, we need to clear the progress and start over
await this.coreE2EIService.clearAllProgress();
Expand All @@ -170,6 +170,15 @@ export class E2EIHandler extends TypedEventEmitter<Events> {
return !!state && !!session_state && !!code;
}

public async hasGracePeriodStartedForSelfClient(): Promise<boolean> {
const identity = await getActiveWireIdentity();
return hasGracePeriodStartedForSelfClient(
identity,
this.enrollmentStore.get.e2eiActivatedAt(),
this.config.gracePeriodInMs,
);
}

/**
* Will initiate the timer that will regularly prompt the user to enroll (or to renew the certificate if it is about to expire)
* @returns the delay under which the next enrollment/renewal modal will be prompted
Expand All @@ -189,7 +198,7 @@ export class E2EIHandler extends TypedEventEmitter<Events> {
this.config.gracePeriodInMs,
);

const task = async () => {
const task = async (isSnoozable: boolean) => {
this.enrollmentStore.clear.timer();
await this.processEnrollmentUponExpiry(isSnoozable);
};
Expand All @@ -204,11 +213,14 @@ export class E2EIHandler extends TypedEventEmitter<Events> {
if (isFirstE2EIActivation || firingDate <= Date.now()) {
// We want to automatically trigger the enrollment modal if it's a devices in team that just activated e2eidentity
// Or if the timer is supposed to fire now
void task();
void task(isSnoozable);
} else {
LowPrecisionTaskScheduler.addTask({
key: timerKey,
task,
task: () => {
const {isSnoozable} = getEnrollmentTimer(identity, e2eActivatedAt, this.config.gracePeriodInMs);
return task(isSnoozable);
},
firingDate: firingDate,
intervalDelay: TIME_IN_MILLIS.SECOND * 10,
});
Expand Down Expand Up @@ -262,7 +274,11 @@ export class E2EIHandler extends TypedEventEmitter<Events> {
return oidcService.getUser();
}

public async enroll(snoozable: boolean = true) {
public async enroll({snoozable = true, resetTimers = false}: {snoozable?: boolean; resetTimers?: boolean} = {}) {
if (resetTimers) {
this.enrollmentStore.clear.timer();
}

try {
// Notify user about E2EI enrolment in progress
const isCertificateRenewal = await hasActiveCertificate();
Expand Down Expand Up @@ -358,12 +374,14 @@ export class E2EIHandler extends TypedEventEmitter<Events> {
hideClose: true,
hideSecondary: !snoozable,
primaryActionFn: async () => {
await this.enroll(snoozable);
await this.enroll({snoozable});
resolve();
},
secondaryActionFn: async () => {
const delay = await this.startTimers();
this.showSnoozeConfirmationModal(delay);
if (delay > 0) {
this.showSnoozeConfirmationModal(delay);
}
resolve();
},
extraParams: {
Expand All @@ -383,12 +401,14 @@ export class E2EIHandler extends TypedEventEmitter<Events> {
const {modalOptions, modalType: determinedModalType} = getModalOptions({
hideSecondary: !snoozable,
primaryActionFn: async () => {
await this.enroll(snoozable);
await this.enroll({snoozable});
resolve();
},
secondaryActionFn: async () => {
const delay = await this.startTimers();
this.showSnoozeConfirmationModal(delay);
if (delay > 0) {
this.showSnoozeConfirmationModal(delay);
}
resolve();
},
extraParams: {
Expand Down
9 changes: 9 additions & 0 deletions src/script/E2EIdentity/EnrollmentTimer/EnrollmentTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,12 @@ export function getEnrollmentTimer(
// When logging in to a old device that doesn't have an identity yet, we trigger an enrollment timer
return {isSnoozable: nextTick > 0, firingDate: Date.now() + nextTick};
}

export function hasGracePeriodStartedForSelfClient(
identity: WireIdentity | undefined,
e2eiActivatedAt: number,
teamGracePeriodDuration: number,
) {
const deadline = getGracePeriod(identity, e2eiActivatedAt, teamGracePeriodDuration);
return Date.now() >= deadline.start;
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const E2EIVerificationMessage = ({message, conversation}: E2EIVerificatio

const getCertificate = async () => {
try {
await E2EIHandler.getInstance().enroll();
await E2EIHandler.getInstance().enroll({resetTimers: true});
} catch (error) {
logger.error('Failed to enroll user certificate: ', error);
}
Expand Down
92 changes: 92 additions & 0 deletions src/script/hooks/useCertificateStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {useCallback, useEffect, useState} from 'react';

import {CredentialType} from '@wireapp/core/lib/messagingProtocols/mls';

import {TIME_IN_MILLIS} from 'Util/TimeUtil';

import {E2EIHandler, MLSStatuses, WireIdentity} from '../E2EIdentity';

const getCertificateStatus = (identity?: WireIdentity, isSelfWithinGracePeriod: boolean = false) => {
if (!identity || identity.credentialType === CredentialType.Basic) {
return MLSStatuses.NOT_ACTIVATED;
}

const certificate = identity.x509Identity?.certificate;
const hasCertificate = !!certificate && Boolean(certificate.length);

if (!hasCertificate) {
return MLSStatuses.NOT_ACTIVATED;
}

if (identity.status === MLSStatuses.VALID && isSelfWithinGracePeriod) {
return MLSStatuses.EXPIRES_SOON;
}

return identity.status;
};

export const useCertificateStatus = (
identity?: WireIdentity,
isCurrentDevice = false,
): [string | null, MLSStatuses] => {
const [certificateStatus, setCertificateStatus] = useState<[string | null, MLSStatuses]>([
null,
MLSStatuses.NOT_ACTIVATED,
]);

const refreshCertificateStatus = useCallback(async () => {
const identityCertificate = identity?.x509Identity?.certificate;
const certificate = !!identityCertificate && Boolean(identityCertificate.length) ? identityCertificate : null;

const hasGracePeriodStarted = isCurrentDevice
? await E2EIHandler.getInstance().hasGracePeriodStartedForSelfClient()
: false;
const status = getCertificateStatus(identity, hasGracePeriodStarted);

setCertificateStatus(prev => {
if (prev[0] === certificate && prev[1] === status) {
return prev;
}

return [certificate, status];
});
}, [identity, isCurrentDevice]);

useEffect(() => {
void refreshCertificateStatus();

// Refresh the certificate status every second if the device is the current device
if (isCurrentDevice) {
const tid = setInterval(() => {
void refreshCertificateStatus();
}, TIME_IN_MILLIS.SECOND);

return () => {
clearTimeout(tid);
};
}

return () => {};
}, [refreshCertificateStatus, isCurrentDevice]);

return certificateStatus;
};
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ export const DevicesPreferences: React.FC<DevicesPreferencesProps> = ({
const [localFingerprint, setLocalFingerprint] = useState('');

const {devices} = useKoSubscribableChildren(selfUser, ['devices']);
const {getDeviceIdentity} = useUserIdentity(selfUser.qualifiedId, conversationState.selfMLSConversation()?.groupId);
const {getDeviceIdentity} = useUserIdentity(
selfUser.qualifiedId,
conversationState.selfMLSConversation()?.groupId,
true,
);
const currentClient = clientState.currentClient;

const isSSO = selfUser.isNoPasswordSSO;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const DetailedDevice: React.FC<DeviceProps> = ({
isProteusVerified,
}) => {
const getIdentity = getDeviceIdentity ? () => getDeviceIdentity(device.id) : undefined;

return (
<>
<h3 className="preferences-devices-model preferences-devices-model-name" data-uie-name="device-model">
Expand Down
Loading

0 comments on commit 6cf2a69

Please sign in to comment.