diff --git a/jslib b/jslib index b01759924c..7bdca0dcb4 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit b01759924c112c3d3d33538e782af70e282bbb5b +Subproject commit 7bdca0dcb4bbe8983157d555740db5ed229d5948 diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 99d4756b6c..027efe05fb 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -33,6 +33,7 @@ import { AuthService } from 'jslib/abstractions/auth.service'; import { CipherService } from 'jslib/abstractions/cipher.service'; import { CollectionService } from 'jslib/abstractions/collection.service'; import { CryptoService } from 'jslib/abstractions/crypto.service'; +import { EventService } from 'jslib/abstractions/event.service'; import { FolderService } from 'jslib/abstractions/folder.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { LockService } from 'jslib/abstractions/lock.service'; @@ -83,7 +84,7 @@ export class AppComponent implements OnDestroy, OnInit { private cryptoService: CryptoService, private collectionService: CollectionService, private sanitizer: DomSanitizer, private searchService: SearchService, private notificationsService: NotificationsService, private routerService: RouterService, - private stateService: StateService) { } + private stateService: StateService, private eventService: EventService) { } ngOnInit() { this.ngZone.runOutsideAngular(() => { @@ -173,9 +174,11 @@ export class AppComponent implements OnDestroy, OnInit { } private async logOut(expired: boolean) { + await this.eventService.uploadEvents(); const userId = await this.userService.getUserId(); await Promise.all([ + this.eventService.clearEvents(), this.syncService.setLastSync(new Date(0)), this.tokenService.clearToken(), this.cryptoService.clearKeys(), diff --git a/src/app/services/event.service.ts b/src/app/services/event.service.ts index 6813af4293..cc88644b44 100644 --- a/src/app/services/event.service.ts +++ b/src/app/services/event.service.ts @@ -62,6 +62,9 @@ export class EventService { case EventType.User_FailedLogIn2fa: msg = this.i18nService.t('failedLogin2fa'); break; + case EventType.User_ClientExportedVault: + msg = this.i18nService.t('exportedVault'); + break; // Cipher case EventType.Cipher_Created: msg = this.i18nService.t('createdItemId', this.formatCipherId(ev, options)); @@ -81,6 +84,30 @@ export class EventService { case EventType.Cipher_Shared: msg = this.i18nService.t('sharedItemId', this.formatCipherId(ev, options)); break; + case EventType.Cipher_ClientViewed: + msg = this.i18nService.t('viewedItemId', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_ClientToggledPasswordVisible: + msg = this.i18nService.t('viewedPasswordItemId', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_ClientToggledHiddenFieldVisible: + msg = this.i18nService.t('viewedHiddenFieldItemId', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_ClientToggledCardCodeVisible: + msg = this.i18nService.t('viewedSecurityCodeItemId', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_ClientCopiedHiddenField: + msg = this.i18nService.t('copiedHiddenFieldItemId', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_ClientCopiedPassword: + msg = this.i18nService.t('copiedPasswordItemId', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_ClientCopiedCardCode: + msg = this.i18nService.t('copiedSecurityCodeItemId', this.formatCipherId(ev, options)); + break; + case EventType.Cipher_ClientAutofilled: + msg = this.i18nService.t('autofilledItemId', this.formatCipherId(ev, options)); + break; case EventType.Cipher_UpdatedCollections: msg = this.i18nService.t('editedCollectionsForItem', this.formatCipherId(ev, options)); break; diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index 33c7540cee..a92bf70cc8 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -34,6 +34,7 @@ import { ConstantsService } from 'jslib/services/constants.service'; import { ContainerService } from 'jslib/services/container.service'; import { CryptoService } from 'jslib/services/crypto.service'; import { EnvironmentService } from 'jslib/services/environment.service'; +import { EventService as EventLoggingService } from 'jslib/services/event.service'; import { ExportService } from 'jslib/services/export.service'; import { FolderService } from 'jslib/services/folder.service'; import { ImportService } from 'jslib/services/import.service'; @@ -58,6 +59,7 @@ import { CollectionService as CollectionServiceAbstraction } from 'jslib/abstrac import { CryptoService as CryptoServiceAbstraction } from 'jslib/abstractions/crypto.service'; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib/abstractions/cryptoFunction.service'; import { EnvironmentService as EnvironmentServiceAbstraction } from 'jslib/abstractions/environment.service'; +import { EventService as EventLoggingServiceAbstraction } from 'jslib/abstractions/event.service'; import { ExportService as ExportServiceAbstraction } from 'jslib/abstractions/export.service'; import { FolderService as FolderServiceAbstraction } from 'jslib/abstractions/folder.service'; import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service'; @@ -119,6 +121,7 @@ const notificationsService = new NotificationsService(userService, syncService, apiService, lockService, async () => messagingService.send('logout', { expired: true })); const environmentService = new EnvironmentService(apiService, storageService, notificationsService); const auditService = new AuditService(cryptoFunctionService, apiService); +const eventLoggingService = new EventLoggingService(storageService, apiService, userService, cipherService); const analytics = new Analytics(window, () => platformUtilsService.isDev() || platformUtilsService.isSelfHost(), platformUtilsService, storageService, appIdService); @@ -153,6 +156,7 @@ export function initFactory(): Function { lockService.init(true); const locale = await storageService.get(ConstantsService.localeKey); await i18nService.init(locale); + eventLoggingService.init(true); authService.init(); const htmlEl = window.document.documentElement; htmlEl.classList.add('locale_' + i18nService.translationLocale); @@ -206,6 +210,7 @@ export function initFactory(): Function { { provide: ImportServiceAbstraction, useValue: importService }, { provide: NotificationsServiceAbstraction, useValue: notificationsService }, { provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService }, + { provide: EventLoggingServiceAbstraction, useValue: eventLoggingService }, { provide: APP_INITIALIZER, useFactory: initFactory, diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 3b98aa2a51..5be728f6d3 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -2212,6 +2212,9 @@ "failedLogin2fa": { "message": "Login attempt failed with incorrect two-step login." }, + "exportedVault": { + "message": "Exported vault." + }, "editedOrgSettings": { "message": "Edited organization settings." }, @@ -2251,6 +2254,78 @@ } } }, + "viewedItemId": { + "message": "Viewed item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedPasswordItemId": { + "message": "Viewed password for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedHiddenFieldItemId": { + "message": "Viewed hidden field for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "viewedSecurityCodeItemId": { + "message": "Viewed security code for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedPasswordItemId": { + "message": "Copied password for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedHiddenFieldItemId": { + "message": "Copied hidden field for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "copiedSecurityCodeItemId": { + "message": "Copied security code for item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, + "autofilledItemId": { + "message": "Auto-filled item $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "Google" + } + } + }, "createdCollectionId": { "message": "Created collection $ID$.", "placeholders": {