mirror of
https://github.com/bitwarden/jslib
synced 2025-12-14 23:33:36 +00:00
Add Linked Field as custom field type (#431)
* Basic proof of concept of Linked custom fields * Linked Fields for all cipher types, use dropdown * Move linkedFieldOptions to view models * Move add-edit custom fields to own component * Fix change handling if cipherType changes * Use Field.LinkedId to store linked field info * Refactor accessors in cipherView for type safety * Use map for linkedFieldOptions * Refactor: use decorators to record linkable info * Add ItemView * Use enums for linked field ids * Add union type for linkedId enums, add jsdoc comment * Use parameter properties for linkedFieldOption Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * Fix type casting Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
Directive,
|
Directive,
|
||||||
Input,
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
SimpleChanges,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -18,13 +20,17 @@ import { CipherType } from 'jslib-common/enums/cipherType';
|
|||||||
import { EventType } from 'jslib-common/enums/eventType';
|
import { EventType } from 'jslib-common/enums/eventType';
|
||||||
import { FieldType } from 'jslib-common/enums/fieldType';
|
import { FieldType } from 'jslib-common/enums/fieldType';
|
||||||
|
|
||||||
|
import { Utils } from 'jslib-common/misc/utils';
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class AddEditCustomFieldsComponent {
|
export class AddEditCustomFieldsComponent implements OnChanges {
|
||||||
@Input() cipher: CipherView;
|
@Input() cipher: CipherView;
|
||||||
|
@Input() thisCipherType: CipherType;
|
||||||
@Input() editMode: boolean;
|
@Input() editMode: boolean;
|
||||||
|
|
||||||
addFieldType: FieldType = FieldType.Text;
|
addFieldType: FieldType = FieldType.Text;
|
||||||
addFieldTypeOptions: any[];
|
addFieldTypeOptions: any[];
|
||||||
|
addFieldLinkedTypeOption: any;
|
||||||
linkedFieldOptions: any[] = [];
|
linkedFieldOptions: any[] = [];
|
||||||
|
|
||||||
cipherType = CipherType;
|
cipherType = CipherType;
|
||||||
@@ -37,6 +43,13 @@ export class AddEditCustomFieldsComponent {
|
|||||||
{ name: i18nService.t('cfTypeHidden'), value: FieldType.Hidden },
|
{ name: i18nService.t('cfTypeHidden'), value: FieldType.Hidden },
|
||||||
{ name: i18nService.t('cfTypeBoolean'), value: FieldType.Boolean },
|
{ name: i18nService.t('cfTypeBoolean'), value: FieldType.Boolean },
|
||||||
];
|
];
|
||||||
|
this.addFieldLinkedTypeOption = { name: this.i18nService.t('cfTypeLinked'), value: FieldType.Linked };
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes.thisCipherType != null) {
|
||||||
|
this.setLinkedFieldOptions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addField() {
|
addField() {
|
||||||
@@ -48,6 +61,10 @@ export class AddEditCustomFieldsComponent {
|
|||||||
f.type = this.addFieldType;
|
f.type = this.addFieldType;
|
||||||
f.newField = true;
|
f.newField = true;
|
||||||
|
|
||||||
|
if (f.type === FieldType.Linked) {
|
||||||
|
f.linkedId = this.linkedFieldOptions[0].value;
|
||||||
|
}
|
||||||
|
|
||||||
this.cipher.fields.push(f);
|
this.cipher.fields.push(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,4 +90,17 @@ export class AddEditCustomFieldsComponent {
|
|||||||
drop(event: CdkDragDrop<string[]>) {
|
drop(event: CdkDragDrop<string[]>) {
|
||||||
moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex);
|
moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setLinkedFieldOptions() {
|
||||||
|
// Delete any Linked custom fields if the item type does not support them
|
||||||
|
if (this.cipher.linkedFieldOptions == null) {
|
||||||
|
this.cipher.fields = this.cipher.fields.filter(f => f.type !== FieldType.Linked);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: any = [];
|
||||||
|
this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) =>
|
||||||
|
options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id }));
|
||||||
|
this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, 'name'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export enum FieldType {
|
|||||||
Text = 0,
|
Text = 0,
|
||||||
Hidden = 1,
|
Hidden = 1,
|
||||||
Boolean = 2,
|
Boolean = 2,
|
||||||
|
Linked = 3,
|
||||||
}
|
}
|
||||||
|
|||||||
40
common/src/enums/linkedIdType.ts
Normal file
40
common/src/enums/linkedIdType.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
export type LinkedIdType = LoginLinkedId | CardLinkedId | IdentityLinkedId;
|
||||||
|
|
||||||
|
// LoginView
|
||||||
|
export enum LoginLinkedId {
|
||||||
|
Username = 100,
|
||||||
|
Password = 101,
|
||||||
|
}
|
||||||
|
|
||||||
|
// CardView
|
||||||
|
export enum CardLinkedId {
|
||||||
|
CardholderName = 300,
|
||||||
|
ExpMonth = 301,
|
||||||
|
ExpYear = 302,
|
||||||
|
Code = 303,
|
||||||
|
Brand = 304,
|
||||||
|
Number = 305,
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdentityView
|
||||||
|
export enum IdentityLinkedId {
|
||||||
|
Title = 400,
|
||||||
|
MiddleName = 401,
|
||||||
|
Address1 = 402,
|
||||||
|
Address2 = 403,
|
||||||
|
Address3 = 404,
|
||||||
|
City = 405,
|
||||||
|
State = 406,
|
||||||
|
PostalCode = 407,
|
||||||
|
Country = 408,
|
||||||
|
Company = 409,
|
||||||
|
Email = 410,
|
||||||
|
Phone = 411,
|
||||||
|
Ssn = 412,
|
||||||
|
Username = 413,
|
||||||
|
PassportNumber = 414,
|
||||||
|
LicenseNumber = 415,
|
||||||
|
FirstName = 416,
|
||||||
|
LastName = 417,
|
||||||
|
FullName = 418,
|
||||||
|
}
|
||||||
28
common/src/misc/linkedFieldOption.decorator.ts
Normal file
28
common/src/misc/linkedFieldOption.decorator.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { ItemView } from '../models/view/itemView';
|
||||||
|
|
||||||
|
import { LinkedIdType } from '../enums/linkedIdType';
|
||||||
|
|
||||||
|
export class LinkedMetadata {
|
||||||
|
constructor(readonly propertyKey: string, private readonly _i18nKey?: string) { }
|
||||||
|
|
||||||
|
get i18nKey() {
|
||||||
|
return this._i18nKey ?? this.propertyKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A decorator used to set metadata used by Linked custom fields. Apply it to a class property or getter to make it
|
||||||
|
* available as a Linked custom field option.
|
||||||
|
* @param id - A unique value that is saved in the Field model. It is used to look up the decorated class property.
|
||||||
|
* @param i18nKey - The i18n key used to describe the decorated class property in the UI. If it is null, then the name
|
||||||
|
* of the class property will be used as the i18n key.
|
||||||
|
*/
|
||||||
|
export function linkedFieldOption(id: LinkedIdType, i18nKey?: string) {
|
||||||
|
return (prototype: ItemView, propertyKey: string) => {
|
||||||
|
if (prototype.linkedFieldOptions == null) {
|
||||||
|
prototype.linkedFieldOptions = new Map<LinkedIdType, LinkedMetadata>();
|
||||||
|
}
|
||||||
|
|
||||||
|
prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, i18nKey));
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { BaseResponse } from '../response/baseResponse';
|
import { BaseResponse } from '../response/baseResponse';
|
||||||
|
|
||||||
import { FieldType } from '../../enums/fieldType';
|
import { FieldType } from '../../enums/fieldType';
|
||||||
|
import { LinkedIdType } from '../../enums/linkedIdType';
|
||||||
|
|
||||||
export class FieldApi extends BaseResponse {
|
export class FieldApi extends BaseResponse {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
type: FieldType;
|
type: FieldType;
|
||||||
|
linkedId: LinkedIdType;
|
||||||
|
|
||||||
constructor(data: any = null) {
|
constructor(data: any = null) {
|
||||||
super(data);
|
super(data);
|
||||||
@@ -15,5 +17,6 @@ export class FieldApi extends BaseResponse {
|
|||||||
this.type = this.getResponseProperty('Type');
|
this.type = this.getResponseProperty('Type');
|
||||||
this.name = this.getResponseProperty('Name');
|
this.name = this.getResponseProperty('Name');
|
||||||
this.value = this.getResponseProperty('Value');
|
this.value = this.getResponseProperty('Value');
|
||||||
|
this.linkedId = this.getResponseProperty('linkedId');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FieldType } from '../../enums/fieldType';
|
import { FieldType } from '../../enums/fieldType';
|
||||||
|
import { LinkedIdType } from '../../enums/linkedIdType';
|
||||||
|
|
||||||
import { FieldApi } from '../api/fieldApi';
|
import { FieldApi } from '../api/fieldApi';
|
||||||
|
|
||||||
@@ -6,6 +7,7 @@ export class FieldData {
|
|||||||
type: FieldType;
|
type: FieldType;
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
linkedId: LinkedIdType;
|
||||||
|
|
||||||
constructor(response?: FieldApi) {
|
constructor(response?: FieldApi) {
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
@@ -14,5 +16,6 @@ export class FieldData {
|
|||||||
this.type = response.type;
|
this.type = response.type;
|
||||||
this.name = response.name;
|
this.name = response.name;
|
||||||
this.value = response.value;
|
this.value = response.value;
|
||||||
|
this.linkedId = response.linkedId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FieldType } from '../../enums/fieldType';
|
import { FieldType } from '../../enums/fieldType';
|
||||||
|
import { LinkedIdType } from '../../enums/linkedIdType';
|
||||||
|
|
||||||
import { FieldData } from '../data/fieldData';
|
import { FieldData } from '../data/fieldData';
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ export class Field extends Domain {
|
|||||||
name: EncString;
|
name: EncString;
|
||||||
value: EncString;
|
value: EncString;
|
||||||
type: FieldType;
|
type: FieldType;
|
||||||
|
linkedId: LinkedIdType;
|
||||||
|
|
||||||
constructor(obj?: FieldData, alreadyEncrypted: boolean = false) {
|
constructor(obj?: FieldData, alreadyEncrypted: boolean = false) {
|
||||||
super();
|
super();
|
||||||
@@ -20,6 +22,7 @@ export class Field extends Domain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.type = obj.type;
|
this.type = obj.type;
|
||||||
|
this.linkedId = obj.linkedId;
|
||||||
this.buildDomainModel(this, obj, {
|
this.buildDomainModel(this, obj, {
|
||||||
name: null,
|
name: null,
|
||||||
value: null,
|
value: null,
|
||||||
@@ -39,7 +42,8 @@ export class Field extends Domain {
|
|||||||
name: null,
|
name: null,
|
||||||
value: null,
|
value: null,
|
||||||
type: null,
|
type: null,
|
||||||
}, ['type']);
|
linkedId: null,
|
||||||
|
}, ['type', 'linkedId']);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export class CipherRequest {
|
|||||||
field.type = f.type;
|
field.type = f.type;
|
||||||
field.name = f.name ? f.name.encryptedString : null;
|
field.name = f.name ? f.name.encryptedString : null;
|
||||||
field.value = f.value ? f.value.encryptedString : null;
|
field.value = f.value ? f.value.encryptedString : null;
|
||||||
|
field.linkedId = f.linkedId;
|
||||||
return field;
|
return field;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import { View } from './view';
|
import { ItemView } from './itemView';
|
||||||
|
|
||||||
import { Card } from '../domain/card';
|
import { Card } from '../domain/card';
|
||||||
|
|
||||||
export class CardView implements View {
|
import { CardLinkedId as LinkedId } from '../../enums/linkedIdType';
|
||||||
|
|
||||||
|
import { linkedFieldOption } from '../../misc/linkedFieldOption.decorator';
|
||||||
|
|
||||||
|
export class CardView extends ItemView {
|
||||||
|
@linkedFieldOption(LinkedId.CardholderName)
|
||||||
cardholderName: string = null;
|
cardholderName: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.ExpMonth, 'expirationMonth')
|
||||||
expMonth: string = null;
|
expMonth: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.ExpYear, 'expirationYear')
|
||||||
expYear: string = null;
|
expYear: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Code, 'securityCode')
|
||||||
code: string = null;
|
code: string = null;
|
||||||
|
|
||||||
// tslint:disable
|
// tslint:disable
|
||||||
@@ -15,7 +23,7 @@ export class CardView implements View {
|
|||||||
// tslint:enable
|
// tslint:enable
|
||||||
|
|
||||||
constructor(c?: Card) {
|
constructor(c?: Card) {
|
||||||
// ctor
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
get maskedCode(): string {
|
get maskedCode(): string {
|
||||||
@@ -26,6 +34,7 @@ export class CardView implements View {
|
|||||||
return this.number != null ? '•'.repeat(this.number.length) : null;
|
return this.number != null ? '•'.repeat(this.number.length) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@linkedFieldOption(LinkedId.Brand)
|
||||||
get brand(): string {
|
get brand(): string {
|
||||||
return this._brand;
|
return this._brand;
|
||||||
}
|
}
|
||||||
@@ -34,6 +43,7 @@ export class CardView implements View {
|
|||||||
this._subTitle = null;
|
this._subTitle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@linkedFieldOption(LinkedId.Number)
|
||||||
get number(): string {
|
get number(): string {
|
||||||
return this._number;
|
return this._number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { CipherRepromptType } from '../../enums/cipherRepromptType';
|
import { CipherRepromptType } from '../../enums/cipherRepromptType';
|
||||||
import { CipherType } from '../../enums/cipherType';
|
import { CipherType } from '../../enums/cipherType';
|
||||||
|
import { LinkedIdType } from '../../enums/linkedIdType';
|
||||||
|
|
||||||
import { Cipher } from '../domain/cipher';
|
import { Cipher } from '../domain/cipher';
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ import { AttachmentView } from './attachmentView';
|
|||||||
import { CardView } from './cardView';
|
import { CardView } from './cardView';
|
||||||
import { FieldView } from './fieldView';
|
import { FieldView } from './fieldView';
|
||||||
import { IdentityView } from './identityView';
|
import { IdentityView } from './identityView';
|
||||||
|
import { ItemView } from './itemView';
|
||||||
import { LoginView } from './loginView';
|
import { LoginView } from './loginView';
|
||||||
import { PasswordHistoryView } from './passwordHistoryView';
|
import { PasswordHistoryView } from './passwordHistoryView';
|
||||||
import { SecureNoteView } from './secureNoteView';
|
import { SecureNoteView } from './secureNoteView';
|
||||||
@@ -57,16 +59,16 @@ export class CipherView implements View {
|
|||||||
this.reprompt = c.reprompt ?? CipherRepromptType.None;
|
this.reprompt = c.reprompt ?? CipherRepromptType.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
get subTitle(): string {
|
private get item() {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
return this.login.subTitle;
|
return this.login;
|
||||||
case CipherType.SecureNote:
|
case CipherType.SecureNote:
|
||||||
return this.secureNote.subTitle;
|
return this.secureNote;
|
||||||
case CipherType.Card:
|
case CipherType.Card:
|
||||||
return this.card.subTitle;
|
return this.card;
|
||||||
case CipherType.Identity:
|
case CipherType.Identity:
|
||||||
return this.identity.subTitle;
|
return this.identity;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -74,6 +76,10 @@ export class CipherView implements View {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get subTitle(): string {
|
||||||
|
return this.item.subTitle;
|
||||||
|
}
|
||||||
|
|
||||||
get hasPasswordHistory(): boolean {
|
get hasPasswordHistory(): boolean {
|
||||||
return this.passwordHistory && this.passwordHistory.length > 0;
|
return this.passwordHistory && this.passwordHistory.length > 0;
|
||||||
}
|
}
|
||||||
@@ -109,4 +115,22 @@ export class CipherView implements View {
|
|||||||
get isDeleted(): boolean {
|
get isDeleted(): boolean {
|
||||||
return this.deletedDate != null;
|
return this.deletedDate != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get linkedFieldOptions() {
|
||||||
|
return this.item.linkedFieldOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
linkedFieldValue(id: LinkedIdType) {
|
||||||
|
const linkedFieldOption = this.linkedFieldOptions?.get(id);
|
||||||
|
if (linkedFieldOption == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = this.item;
|
||||||
|
return this.item[linkedFieldOption.propertyKey as keyof typeof item];
|
||||||
|
}
|
||||||
|
|
||||||
|
linkedFieldI18nKey(id: LinkedIdType): string {
|
||||||
|
return this.linkedFieldOptions.get(id)?.i18nKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FieldType } from '../../enums/fieldType';
|
import { FieldType } from '../../enums/fieldType';
|
||||||
|
import { LinkedIdType } from '../../enums/linkedIdType';
|
||||||
|
|
||||||
import { View } from './view';
|
import { View } from './view';
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ export class FieldView implements View {
|
|||||||
type: FieldType = null;
|
type: FieldType = null;
|
||||||
newField: boolean = false; // Marks if the field is new and hasn't been saved
|
newField: boolean = false; // Marks if the field is new and hasn't been saved
|
||||||
showValue: boolean = false;
|
showValue: boolean = false;
|
||||||
|
linkedId: LinkedIdType = null;
|
||||||
|
|
||||||
constructor(f?: Field) {
|
constructor(f?: Field) {
|
||||||
if (!f) {
|
if (!f) {
|
||||||
@@ -17,6 +19,7 @@ export class FieldView implements View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.type = f.type;
|
this.type = f.type;
|
||||||
|
this.linkedId = f.linkedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
get maskedValue(): string {
|
get maskedValue(): string {
|
||||||
|
|||||||
@@ -1,25 +1,45 @@
|
|||||||
import { View } from './view';
|
import { ItemView } from './itemView';
|
||||||
|
|
||||||
import { Identity } from '../domain/identity';
|
import { Identity } from '../domain/identity';
|
||||||
|
|
||||||
import { Utils } from '../../misc/utils';
|
import { Utils } from '../../misc/utils';
|
||||||
|
|
||||||
export class IdentityView implements View {
|
import { IdentityLinkedId as LinkedId } from '../../enums/linkedIdType';
|
||||||
|
|
||||||
|
import { linkedFieldOption } from '../../misc/linkedFieldOption.decorator';
|
||||||
|
|
||||||
|
export class IdentityView extends ItemView {
|
||||||
|
@linkedFieldOption(LinkedId.Title)
|
||||||
title: string = null;
|
title: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.MiddleName)
|
||||||
middleName: string = null;
|
middleName: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Address1)
|
||||||
address1: string = null;
|
address1: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Address2)
|
||||||
address2: string = null;
|
address2: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Address3)
|
||||||
address3: string = null;
|
address3: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.City, 'cityTown')
|
||||||
city: string = null;
|
city: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.State, 'stateProvince')
|
||||||
state: string = null;
|
state: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.PostalCode, 'zipPostalCode')
|
||||||
postalCode: string = null;
|
postalCode: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Country)
|
||||||
country: string = null;
|
country: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Company)
|
||||||
company: string = null;
|
company: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Email)
|
||||||
email: string = null;
|
email: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Phone)
|
||||||
phone: string = null;
|
phone: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Ssn)
|
||||||
ssn: string = null;
|
ssn: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Username)
|
||||||
username: string = null;
|
username: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.PassportNumber)
|
||||||
passportNumber: string = null;
|
passportNumber: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.LicenseNumber)
|
||||||
licenseNumber: string = null;
|
licenseNumber: string = null;
|
||||||
|
|
||||||
// tslint:disable
|
// tslint:disable
|
||||||
@@ -29,9 +49,10 @@ export class IdentityView implements View {
|
|||||||
// tslint:enable
|
// tslint:enable
|
||||||
|
|
||||||
constructor(i?: Identity) {
|
constructor(i?: Identity) {
|
||||||
// ctor
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@linkedFieldOption(LinkedId.FirstName)
|
||||||
get firstName(): string {
|
get firstName(): string {
|
||||||
return this._firstName;
|
return this._firstName;
|
||||||
}
|
}
|
||||||
@@ -40,6 +61,7 @@ export class IdentityView implements View {
|
|||||||
this._subTitle = null;
|
this._subTitle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@linkedFieldOption(LinkedId.LastName)
|
||||||
get lastName(): string {
|
get lastName(): string {
|
||||||
return this._lastName;
|
return this._lastName;
|
||||||
}
|
}
|
||||||
@@ -65,6 +87,7 @@ export class IdentityView implements View {
|
|||||||
return this._subTitle;
|
return this._subTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@linkedFieldOption(LinkedId.FullName)
|
||||||
get fullName(): string {
|
get fullName(): string {
|
||||||
if (this.title != null || this.firstName != null || this.middleName != null || this.lastName != null) {
|
if (this.title != null || this.firstName != null || this.middleName != null || this.lastName != null) {
|
||||||
let name = '';
|
let name = '';
|
||||||
|
|||||||
8
common/src/models/view/itemView.ts
Normal file
8
common/src/models/view/itemView.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { View } from './view';
|
||||||
|
|
||||||
|
import { LinkedMetadata } from '../../misc/linkedFieldOption.decorator';
|
||||||
|
|
||||||
|
export abstract class ItemView implements View {
|
||||||
|
linkedFieldOptions: Map<number, LinkedMetadata>;
|
||||||
|
abstract get subTitle(): string;
|
||||||
|
}
|
||||||
@@ -1,18 +1,27 @@
|
|||||||
|
import { ItemView } from './itemView';
|
||||||
import { LoginUriView } from './loginUriView';
|
import { LoginUriView } from './loginUriView';
|
||||||
import { View } from './view';
|
|
||||||
|
|
||||||
import { Utils } from '../../misc/utils';
|
import { Utils } from '../../misc/utils';
|
||||||
|
|
||||||
import { Login } from '../domain/login';
|
import { Login } from '../domain/login';
|
||||||
|
|
||||||
export class LoginView implements View {
|
import { LoginLinkedId as LinkedId } from '../../enums/linkedIdType';
|
||||||
|
|
||||||
|
import { linkedFieldOption } from '../../misc/linkedFieldOption.decorator';
|
||||||
|
|
||||||
|
export class LoginView extends ItemView {
|
||||||
|
@linkedFieldOption(LinkedId.Username)
|
||||||
username: string = null;
|
username: string = null;
|
||||||
|
@linkedFieldOption(LinkedId.Password)
|
||||||
password: string = null;
|
password: string = null;
|
||||||
|
|
||||||
passwordRevisionDate?: Date = null;
|
passwordRevisionDate?: Date = null;
|
||||||
totp: string = null;
|
totp: string = null;
|
||||||
uris: LoginUriView[] = null;
|
uris: LoginUriView[] = null;
|
||||||
autofillOnPageLoad: boolean = null;
|
autofillOnPageLoad: boolean = null;
|
||||||
|
|
||||||
constructor(l?: Login) {
|
constructor(l?: Login) {
|
||||||
|
super();
|
||||||
if (!l) {
|
if (!l) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { SecureNoteType } from '../../enums/secureNoteType';
|
import { SecureNoteType } from '../../enums/secureNoteType';
|
||||||
|
|
||||||
import { View } from './view';
|
import { ItemView } from './itemView';
|
||||||
|
|
||||||
import { SecureNote } from '../domain/secureNote';
|
import { SecureNote } from '../domain/secureNote';
|
||||||
|
|
||||||
export class SecureNoteView implements View {
|
export class SecureNoteView extends ItemView {
|
||||||
type: SecureNoteType = null;
|
type: SecureNoteType = null;
|
||||||
|
|
||||||
constructor(n?: SecureNote) {
|
constructor(n?: SecureNote) {
|
||||||
|
super();
|
||||||
if (!n) {
|
if (!n) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||||||
async encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise<Field> {
|
async encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise<Field> {
|
||||||
const field = new Field();
|
const field = new Field();
|
||||||
field.type = fieldModel.type;
|
field.type = fieldModel.type;
|
||||||
|
field.linkedId = fieldModel.linkedId;
|
||||||
// normalize boolean type field values
|
// normalize boolean type field values
|
||||||
if (fieldModel.type === FieldType.Boolean && fieldModel.value !== 'true') {
|
if (fieldModel.type === FieldType.Boolean && fieldModel.value !== 'true') {
|
||||||
fieldModel.value = 'false';
|
fieldModel.value = 'false';
|
||||||
|
|||||||
Reference in New Issue
Block a user