mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-23 11:56:00 +01:00
Some groundwork for Send (#192)
* send work * New method to update the last used index (#184) Instead of updating it every time you call getNext(), it will be updated in a separate call, to avoid updating the index when the cipher did not auto-fill correctly (e.g wrong frame) Fixes #1392 * added OnlyOrg to PolicyType enum (#183) * [Require SSO] Add policy type enumeration (#186) * Added SsoAuthentication policy type * Updated policy type name // added comments for clarification of what each type controls * [SSO] New user provision flow (#173) * Initial commit of new user sso flow * Adjusted stateSplit conditional per review * Add logging to lowdb storage service (#188) * Fix lint errors/warnings (#187) * remove password api * access id * makeSendKey Co-authored-by: Josep Marí <xusoo@users.noreply.github.com> Co-authored-by: Addison Beck <abeck@bitwarden.com> Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
This commit is contained in:
parent
5e50aa1a19
commit
0e9e73ce95
@ -42,6 +42,8 @@ import { PreloginRequest } from '../models/request/preloginRequest';
|
||||
import { RegisterRequest } from '../models/request/registerRequest';
|
||||
import { SeatRequest } from '../models/request/seatRequest';
|
||||
import { SelectionReadOnlyRequest } from '../models/request/selectionReadOnlyRequest';
|
||||
import { SendAccessRequest } from '../models/request/sendAccessRequest';
|
||||
import { SendRequest } from '../models/request/sendRequest';
|
||||
import { SetPasswordRequest } from '../models/request/setPasswordRequest';
|
||||
import { StorageRequest } from '../models/request/storageRequest';
|
||||
import { TaxInfoUpdateRequest } from '../models/request/taxInfoUpdateRequest';
|
||||
@ -92,6 +94,8 @@ import { PolicyResponse } from '../models/response/policyResponse';
|
||||
import { PreloginResponse } from '../models/response/preloginResponse';
|
||||
import { ProfileResponse } from '../models/response/profileResponse';
|
||||
import { SelectionReadOnlyResponse } from '../models/response/selectionReadOnlyResponse';
|
||||
import { SendAccessResponse } from '../models/response/sendAccessResponse';
|
||||
import { SendResponse } from '../models/response/sendResponse';
|
||||
import { SubscriptionResponse } from '../models/response/subscriptionResponse';
|
||||
import { SyncResponse } from '../models/response/syncResponse';
|
||||
import { TaxInfoResponse } from '../models/response/taxInfoResponse';
|
||||
@ -155,6 +159,15 @@ export abstract class ApiService {
|
||||
putFolder: (id: string, request: FolderRequest) => Promise<FolderResponse>;
|
||||
deleteFolder: (id: string) => Promise<any>;
|
||||
|
||||
getSend: (id: string) => Promise<SendResponse>;
|
||||
postSendAccess: (id: string, request: SendAccessRequest) => Promise<SendAccessResponse>;
|
||||
getSends: () => Promise<ListResponse<SendResponse>>;
|
||||
postSend: (request: SendRequest) => Promise<SendResponse>;
|
||||
postSendFile: (data: FormData) => Promise<SendResponse>;
|
||||
putSend: (id: string, request: SendRequest) => Promise<SendResponse>;
|
||||
putSendRemovePassword: (id: string) => Promise<SendResponse>;
|
||||
deleteSend: (id: string) => Promise<any>;
|
||||
|
||||
getCipher: (id: string) => Promise<CipherResponse>;
|
||||
getCipherAdmin: (id: string) => Promise<CipherResponse>;
|
||||
getCiphersOrganization: (organizationId: string) => Promise<ListResponse<CipherResponse>>;
|
||||
|
@ -35,6 +35,7 @@ export abstract class CryptoService {
|
||||
makeShareKey: () => Promise<[CipherString, SymmetricCryptoKey]>;
|
||||
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, CipherString]>;
|
||||
makePinKey: (pin: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise<SymmetricCryptoKey>;
|
||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
||||
hashPassword: (password: string, key: SymmetricCryptoKey) => Promise<string>;
|
||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
|
||||
remakeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
|
||||
|
4
src/enums/sendType.ts
Normal file
4
src/enums/sendType.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum SendType {
|
||||
Text = 0,
|
||||
File = 1,
|
||||
}
|
@ -43,6 +43,10 @@ export class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromUrlB64ToArray(str: string): Uint8Array {
|
||||
return Utils.fromB64ToArray(Utils.fromUrlB64ToB64(str));
|
||||
}
|
||||
|
||||
static fromHexToArray(str: string): Uint8Array {
|
||||
if (Utils.isNode || Utils.isNativeScript) {
|
||||
return new Uint8Array(Buffer.from(str, 'hex'));
|
||||
@ -90,11 +94,13 @@ export class Utils {
|
||||
}
|
||||
|
||||
static fromBufferToUrlB64(buffer: ArrayBuffer): string {
|
||||
const output = this.fromBufferToB64(buffer)
|
||||
.replace(/\+/g, '-')
|
||||
return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer))
|
||||
}
|
||||
|
||||
static fromB64toUrlB64(b64Str: string) {
|
||||
return b64Str.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=/g, '');
|
||||
return output;
|
||||
}
|
||||
|
||||
static fromBufferToUtf8(buffer: ArrayBuffer): string {
|
||||
@ -121,8 +127,8 @@ export class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
static fromUrlB64ToUtf8(b64Str: string): string {
|
||||
let output = b64Str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
static fromUrlB64ToB64(urlB64Str: string): string {
|
||||
let output = urlB64Str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
switch (output.length % 4) {
|
||||
case 0:
|
||||
break;
|
||||
@ -136,7 +142,11 @@ export class Utils {
|
||||
throw new Error('Illegal base64url string!');
|
||||
}
|
||||
|
||||
return Utils.fromB64ToUtf8(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
static fromUrlB64ToUtf8(urlB64Str: string): string {
|
||||
return Utils.fromB64ToUtf8(Utils.fromUrlB64ToB64(urlB64Str));
|
||||
}
|
||||
|
||||
static fromB64ToUtf8(b64Str: string): string {
|
||||
|
23
src/models/api/sendFileApi.ts
Normal file
23
src/models/api/sendFileApi.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { BaseResponse } from '../response/baseResponse';
|
||||
|
||||
export class SendFileApi extends BaseResponse {
|
||||
id: string;
|
||||
url: string;
|
||||
fileName: string;
|
||||
key: string;
|
||||
size: string;
|
||||
sizeName: string;
|
||||
|
||||
constructor(data: any = null) {
|
||||
super(data);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
this.id = this.getResponseProperty('Id');
|
||||
this.url = this.getResponseProperty('Url');
|
||||
this.fileName = this.getResponseProperty('FileName');
|
||||
this.key = this.getResponseProperty('Key');
|
||||
this.size = this.getResponseProperty('Size');
|
||||
this.sizeName = this.getResponseProperty('SizeName');
|
||||
}
|
||||
}
|
15
src/models/api/sendTextApi.ts
Normal file
15
src/models/api/sendTextApi.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { BaseResponse } from '../response/baseResponse';
|
||||
|
||||
export class SendTextApi extends BaseResponse {
|
||||
text: string;
|
||||
hidden: boolean;
|
||||
|
||||
constructor(data: any = null) {
|
||||
super(data);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
this.text = this.getResponseProperty('Text');
|
||||
this.hidden = this.getResponseProperty('Hidden') || false;
|
||||
}
|
||||
}
|
57
src/models/data/sendData.ts
Normal file
57
src/models/data/sendData.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { SendType } from '../../enums/sendType';
|
||||
|
||||
import { SendFileData } from './sendFileData';
|
||||
import { SendTextData } from './sendTextData';
|
||||
|
||||
import { SendResponse } from '../response/sendResponse';
|
||||
|
||||
export class SendData {
|
||||
id: string;
|
||||
accessId: string;
|
||||
userId: string;
|
||||
type: SendType;
|
||||
name: string;
|
||||
notes: string;
|
||||
file: SendFileData;
|
||||
text: SendTextData;
|
||||
key: string;
|
||||
maxAccessCount?: number;
|
||||
accessCount: number;
|
||||
revisionDate: string;
|
||||
expirationDate: string;
|
||||
deletionDate: string;
|
||||
password: string;
|
||||
disabled: boolean;
|
||||
|
||||
constructor(response?: SendResponse, userId?: string) {
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = response.id;
|
||||
this.accessId = response.accessId;
|
||||
this.userId = userId;
|
||||
this.type = response.type;
|
||||
this.name = response.name;
|
||||
this.notes = response.notes;
|
||||
this.key = response.key;
|
||||
this.maxAccessCount = response.maxAccessCount;
|
||||
this.accessCount = response.accessCount;
|
||||
this.revisionDate = response.revisionDate;
|
||||
this.expirationDate = response.expirationDate;
|
||||
this.deletionDate = response.deletionDate;
|
||||
this.password = response.password;
|
||||
this.disabled = response.disable;
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.Text:
|
||||
this.text = new SendTextData(response.text);
|
||||
break;
|
||||
case SendType.File:
|
||||
this.file = new SendFileData(response.file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
23
src/models/data/sendFileData.ts
Normal file
23
src/models/data/sendFileData.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { SendFileApi } from '../api/sendFileApi';
|
||||
|
||||
export class SendFileData {
|
||||
id: string;
|
||||
url: string;
|
||||
fileName: string;
|
||||
key: string;
|
||||
size: string;
|
||||
sizeName: string;
|
||||
|
||||
constructor(data?: SendFileApi) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = data.id;
|
||||
this.url = data.url;
|
||||
this.fileName = data.fileName;
|
||||
this.key = data.key;
|
||||
this.size = data.size;
|
||||
this.sizeName = data.sizeName;
|
||||
}
|
||||
}
|
15
src/models/data/sendTextData.ts
Normal file
15
src/models/data/sendTextData.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { SendTextApi } from '../api/sendTextApi';
|
||||
|
||||
export class SendTextData {
|
||||
text: string;
|
||||
hidden: boolean;
|
||||
|
||||
constructor(data?: SendTextApi) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.text = data.text;
|
||||
this.hidden = data.hidden;
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import { CryptoService } from '../../abstractions/crypto.service';
|
||||
|
||||
import { Utils } from '../../misc/utils';
|
||||
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class CipherString {
|
||||
encryptedString?: string;
|
||||
encryptionType?: EncryptionType;
|
||||
@ -89,7 +91,7 @@ export class CipherString {
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(orgId: string): Promise<string> {
|
||||
async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise<string> {
|
||||
if (this.decryptedValue != null) {
|
||||
return this.decryptedValue;
|
||||
}
|
||||
@ -103,8 +105,10 @@ export class CipherString {
|
||||
}
|
||||
|
||||
try {
|
||||
const orgKey = await cryptoService.getOrgKey(orgId);
|
||||
this.decryptedValue = await cryptoService.decryptToUtf8(this, orgKey);
|
||||
if (key == null) {
|
||||
key = await cryptoService.getOrgKey(orgId);
|
||||
}
|
||||
this.decryptedValue = await cryptoService.decryptToUtf8(this, key);
|
||||
} catch (e) {
|
||||
this.decryptedValue = '[error: cannot decrypt]';
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ import { CipherString } from './cipherString';
|
||||
|
||||
import { View } from '../view/view';
|
||||
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export default class Domain {
|
||||
protected buildDomainModel<D extends Domain>(domain: D, dataObj: any, map: any,
|
||||
alreadyEncrypted: boolean, notEncList: any[] = []) {
|
||||
@ -33,7 +35,8 @@ export default class Domain {
|
||||
}
|
||||
}
|
||||
|
||||
protected async decryptObj<T extends View>(viewModel: T, map: any, orgId: string): Promise<T> {
|
||||
protected async decryptObj<T extends View>(viewModel: T, map: any, orgId: string,
|
||||
key: SymmetricCryptoKey = null): Promise<T> {
|
||||
const promises = [];
|
||||
const self: any = this;
|
||||
|
||||
@ -47,7 +50,7 @@ export default class Domain {
|
||||
const p = Promise.resolve().then(() => {
|
||||
const mapProp = map[theProp] || theProp;
|
||||
if (self[mapProp]) {
|
||||
return self[mapProp].decrypt(orgId);
|
||||
return self[mapProp].decrypt(orgId, key);
|
||||
}
|
||||
return null;
|
||||
}).then((val: any) => {
|
||||
|
140
src/models/domain/send.ts
Normal file
140
src/models/domain/send.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { CryptoService } from '../../abstractions/crypto.service';
|
||||
|
||||
import { SendType } from '../../enums/sendType';
|
||||
|
||||
import { Utils } from '../../misc/utils';
|
||||
|
||||
import { SendData } from '../data/sendData';
|
||||
|
||||
import { SendView } from '../view/sendView';
|
||||
|
||||
import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
import { SendFile } from './sendFile';
|
||||
import { SendText } from './sendText';
|
||||
|
||||
export class Send extends Domain {
|
||||
id: string;
|
||||
accessId: string;
|
||||
userId: string;
|
||||
type: SendType;
|
||||
name: CipherString;
|
||||
notes: CipherString;
|
||||
file: SendFile;
|
||||
text: SendText;
|
||||
key: CipherString;
|
||||
maxAccessCount?: number;
|
||||
accessCount: number;
|
||||
revisionDate: Date;
|
||||
expirationDate: Date;
|
||||
deletionDate: Date;
|
||||
password: string;
|
||||
disabled: boolean;
|
||||
|
||||
constructor(obj?: SendData, alreadyEncrypted: boolean = false) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildDomainModel(this, obj, {
|
||||
id: null,
|
||||
accessId: null,
|
||||
userId: null,
|
||||
name: null,
|
||||
notes: null,
|
||||
key: null,
|
||||
}, alreadyEncrypted, ['id', 'accessId', 'userId']);
|
||||
|
||||
this.type = obj.type;
|
||||
this.maxAccessCount = obj.maxAccessCount;
|
||||
this.accessCount = obj.accessCount;
|
||||
this.password = obj.password;
|
||||
this.disabled = obj.disabled;
|
||||
this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null;
|
||||
this.deletionDate = obj.deletionDate != null ? new Date(obj.deletionDate) : null;
|
||||
this.expirationDate = obj.expirationDate != null ? new Date(obj.expirationDate) : null;
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.Text:
|
||||
this.text = new SendText(obj.text, alreadyEncrypted);
|
||||
break;
|
||||
case SendType.File:
|
||||
this.file = new SendFile(obj.file, alreadyEncrypted);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(): Promise<SendView> {
|
||||
const model = new SendView(this);
|
||||
|
||||
let cryptoService: CryptoService;
|
||||
const containerService = (Utils.global as any).bitwardenContainerService;
|
||||
if (containerService) {
|
||||
cryptoService = containerService.getCryptoService();
|
||||
} else {
|
||||
throw new Error('global bitwardenContainerService not initialized.');
|
||||
}
|
||||
|
||||
try {
|
||||
model.key = await cryptoService.decryptToBytes(this.key, null);
|
||||
model.cryptoKey = await cryptoService.makeSendKey(model.key);
|
||||
} catch (e) {
|
||||
// TODO: error?
|
||||
}
|
||||
|
||||
await this.decryptObj(model, {
|
||||
name: null,
|
||||
notes: null,
|
||||
}, null, model.cryptoKey);
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.File:
|
||||
model.file = await this.file.decrypt(model.cryptoKey);
|
||||
break;
|
||||
case SendType.Text:
|
||||
model.text = await this.text.decrypt(model.cryptoKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
toSendData(userId: string): SendData {
|
||||
const s = new SendData();
|
||||
s.id = this.id;
|
||||
s.accessId = this.accessId;
|
||||
s.userId = userId;
|
||||
s.maxAccessCount = this.maxAccessCount;
|
||||
s.accessCount = this.accessCount;
|
||||
s.disabled = this.disabled;
|
||||
s.password = this.password;
|
||||
s.revisionDate = this.revisionDate != null ? this.revisionDate.toISOString() : null;
|
||||
s.deletionDate = this.deletionDate != null ? this.deletionDate.toISOString() : null;
|
||||
s.expirationDate = this.expirationDate != null ? this.expirationDate.toISOString() : null;
|
||||
s.type = this.type;
|
||||
|
||||
this.buildDataModel(this, s, {
|
||||
name: null,
|
||||
notes: null,
|
||||
key: null,
|
||||
});
|
||||
|
||||
switch (s.type) {
|
||||
case SendType.File:
|
||||
s.text = this.text.toSendTextData();
|
||||
break;
|
||||
case SendType.Text:
|
||||
s.file = this.file.toSendFileData();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
65
src/models/domain/sendAccess.ts
Normal file
65
src/models/domain/sendAccess.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { SendType } from '../../enums/sendType';
|
||||
|
||||
import { SendAccessResponse } from '../response/sendAccessResponse';
|
||||
|
||||
import { SendAccessView } from '../view/sendAccessView';
|
||||
|
||||
import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
import { SendFile } from './sendFile';
|
||||
import { SendText } from './sendText';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
export class SendAccess extends Domain {
|
||||
id: string;
|
||||
type: SendType;
|
||||
name: CipherString;
|
||||
file: SendFile;
|
||||
text: SendText;
|
||||
|
||||
constructor(obj?: SendAccessResponse, alreadyEncrypted: boolean = false) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildDomainModel(this, obj, {
|
||||
id: null,
|
||||
name: null,
|
||||
}, alreadyEncrypted, ['id']);
|
||||
|
||||
this.type = obj.type;
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.Text:
|
||||
this.text = new SendText(obj.text, alreadyEncrypted);
|
||||
break;
|
||||
case SendType.File:
|
||||
this.file = new SendFile(obj.file, alreadyEncrypted);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(key: SymmetricCryptoKey): Promise<SendAccessView> {
|
||||
const model = new SendAccessView(this);
|
||||
|
||||
await this.decryptObj(model, {
|
||||
name: null,
|
||||
}, null, key);
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.File:
|
||||
model.file = await this.file.decrypt(key);
|
||||
break;
|
||||
case SendType.Text:
|
||||
model.text = await this.text.decrypt(key);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
49
src/models/domain/sendFile.ts
Normal file
49
src/models/domain/sendFile.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
import { SendFileData } from '../data/sendFileData';
|
||||
|
||||
import { SendFileView } from '../view/sendFileView';
|
||||
|
||||
export class SendFile extends Domain {
|
||||
id: string;
|
||||
url: string;
|
||||
size: string;
|
||||
sizeName: string;
|
||||
fileName: CipherString;
|
||||
|
||||
constructor(obj?: SendFileData, alreadyEncrypted: boolean = false) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.size = obj.size;
|
||||
this.buildDomainModel(this, obj, {
|
||||
id: null,
|
||||
url: null,
|
||||
sizeName: null,
|
||||
fileName: null,
|
||||
}, alreadyEncrypted, ['id', 'url', 'sizeName']);
|
||||
}
|
||||
|
||||
async decrypt(key: SymmetricCryptoKey): Promise<SendFileView> {
|
||||
const view = await this.decryptObj(new SendFileView(this), {
|
||||
fileName: null,
|
||||
}, null, key);
|
||||
return view;
|
||||
}
|
||||
|
||||
toSendFileData(): SendFileData {
|
||||
const f = new SendFileData();
|
||||
f.size = this.size;
|
||||
this.buildDataModel(this, f, {
|
||||
id: null,
|
||||
url: null,
|
||||
sizeName: null,
|
||||
fileName: null,
|
||||
}, ['id', 'url', 'sizeName']);
|
||||
return f;
|
||||
}
|
||||
}
|
39
src/models/domain/sendText.ts
Normal file
39
src/models/domain/sendText.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { CipherString } from './cipherString';
|
||||
import Domain from './domainBase';
|
||||
import { SymmetricCryptoKey } from './symmetricCryptoKey';
|
||||
|
||||
import { SendTextData } from '../data/sendTextData';
|
||||
|
||||
import { SendTextView } from '../view/sendTextView';
|
||||
|
||||
export class SendText extends Domain {
|
||||
text: CipherString;
|
||||
hidden: boolean;
|
||||
|
||||
constructor(obj?: SendTextData, alreadyEncrypted: boolean = false) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hidden = obj.hidden;
|
||||
this.buildDomainModel(this, obj, {
|
||||
text: null,
|
||||
}, alreadyEncrypted, []);
|
||||
}
|
||||
|
||||
decrypt(key: SymmetricCryptoKey): Promise<SendTextView> {
|
||||
return this.decryptObj(new SendTextView(this), {
|
||||
text: null,
|
||||
}, null, key);
|
||||
}
|
||||
|
||||
toSendTextData(): SendTextData {
|
||||
const t = new SendTextData();
|
||||
this.buildDataModel(this, t, {
|
||||
text: null,
|
||||
hidden: null,
|
||||
}, ['hidden']);
|
||||
return t;
|
||||
}
|
||||
}
|
3
src/models/request/sendAccessRequest.ts
Normal file
3
src/models/request/sendAccessRequest.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class SendAccessRequest {
|
||||
password: string;
|
||||
}
|
46
src/models/request/sendRequest.ts
Normal file
46
src/models/request/sendRequest.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { SendType } from '../../enums/sendType';
|
||||
|
||||
import { SendFileApi } from '../api/sendFileApi'
|
||||
import { SendTextApi } from '../api/sendTextApi';
|
||||
|
||||
import { Send } from '../domain/send';
|
||||
|
||||
export class SendRequest {
|
||||
type: SendType;
|
||||
name: string;
|
||||
notes: string;
|
||||
key: string;
|
||||
maxAccessCount?: number;
|
||||
expirationDate: string;
|
||||
deletionDate: string;
|
||||
text: SendTextApi;
|
||||
file: SendFileApi;
|
||||
password: string;
|
||||
disabled: boolean;
|
||||
|
||||
constructor(send: Send) {
|
||||
this.type = send.type;
|
||||
this.name = send.name ? send.name.encryptedString : null;
|
||||
this.notes = send.notes ? send.notes.encryptedString : null;
|
||||
this.maxAccessCount = send.maxAccessCount;
|
||||
this.expirationDate = send.expirationDate != null ? send.expirationDate.toISOString() : null;
|
||||
this.deletionDate = send.deletionDate != null ? send.deletionDate.toISOString() : null;
|
||||
this.key = send.key != null ? send.key.encryptedString : null;
|
||||
this.password = send.password;
|
||||
this.disabled = send.disabled;
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.Text:
|
||||
this.text = new SendTextApi();
|
||||
this.text.text = send.text.text != null ? send.text.text.encryptedString : null;
|
||||
this.text.hidden = send.text.hidden;
|
||||
break;
|
||||
case SendType.File:
|
||||
this.file = new SendFileApi();
|
||||
this.file.fileName = send.file.fileName != null ? send.file.fileName.encryptedString : null;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
export abstract class BaseResponse {
|
||||
protected response: any;
|
||||
private response: any;
|
||||
|
||||
constructor(response: any) {
|
||||
this.response = response;
|
||||
|
31
src/models/response/sendAccessResponse.ts
Normal file
31
src/models/response/sendAccessResponse.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { BaseResponse } from './baseResponse';
|
||||
|
||||
import { SendType } from '../../enums/sendType';
|
||||
|
||||
import { SendFileApi } from '../api/sendFileApi';
|
||||
import { SendTextApi } from '../api/sendTextApi';
|
||||
|
||||
export class SendAccessResponse extends BaseResponse {
|
||||
id: string;
|
||||
type: SendType;
|
||||
name: string;
|
||||
file: SendFileApi;
|
||||
text: SendTextApi;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty('Id');
|
||||
this.type = this.getResponseProperty('Type');
|
||||
this.name = this.getResponseProperty('Name');
|
||||
|
||||
const text = this.getResponseProperty('Text');
|
||||
if (text != null) {
|
||||
this.text = new SendTextApi(text);
|
||||
}
|
||||
|
||||
const file = this.getResponseProperty('File');
|
||||
if (file != null) {
|
||||
this.file = new SendFileApi(file);
|
||||
}
|
||||
}
|
||||
}
|
51
src/models/response/sendResponse.ts
Normal file
51
src/models/response/sendResponse.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { BaseResponse } from './baseResponse';
|
||||
|
||||
import { SendType } from '../../enums/sendType';
|
||||
|
||||
import { SendFileApi } from '../api/sendFileApi';
|
||||
import { SendTextApi } from '../api/sendTextApi';
|
||||
|
||||
export class SendResponse extends BaseResponse {
|
||||
id: string;
|
||||
accessId: string;
|
||||
type: SendType;
|
||||
name: string;
|
||||
notes: string;
|
||||
file: SendFileApi;
|
||||
text: SendTextApi;
|
||||
key: string;
|
||||
maxAccessCount?: number;
|
||||
accessCount: number;
|
||||
revisionDate: string;
|
||||
expirationDate: string;
|
||||
deletionDate: string;
|
||||
password: string;
|
||||
disable: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty('Id');
|
||||
this.accessId = this.getResponseProperty('AccessId');
|
||||
this.type = this.getResponseProperty('Type');
|
||||
this.name = this.getResponseProperty('Name');
|
||||
this.notes = this.getResponseProperty('Notes');
|
||||
this.key = this.getResponseProperty('Key');
|
||||
this.maxAccessCount = this.getResponseProperty('MaxAccessCount');
|
||||
this.accessCount = this.getResponseProperty('AccessCount');
|
||||
this.revisionDate = this.getResponseProperty('RevisionDate');
|
||||
this.expirationDate = this.getResponseProperty('ExpirationDate');
|
||||
this.deletionDate = this.getResponseProperty('DeletionDate');
|
||||
this.password = this.getResponseProperty('Password');
|
||||
this.disable = this.getResponseProperty('Disabled') || false;
|
||||
|
||||
const text = this.getResponseProperty('Text');
|
||||
if (text != null) {
|
||||
this.text = new SendTextApi(text);
|
||||
}
|
||||
|
||||
const file = this.getResponseProperty('File');
|
||||
if (file != null) {
|
||||
this.file = new SendFileApi(file);
|
||||
}
|
||||
}
|
||||
}
|
24
src/models/view/sendAccessView.ts
Normal file
24
src/models/view/sendAccessView.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { SendType } from '../../enums/sendType';
|
||||
|
||||
import { SendAccess } from '../domain/sendAccess';
|
||||
|
||||
import { SendFileView } from './sendFileView';
|
||||
import { SendTextView } from './sendTextView';
|
||||
import { View } from './view';
|
||||
|
||||
export class SendAccessView implements View {
|
||||
id: string = null;
|
||||
name: string = null;
|
||||
type: SendType = null;
|
||||
text = new SendTextView();
|
||||
file = new SendFileView();
|
||||
|
||||
constructor(s?: SendAccess) {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = s.id;
|
||||
this.type = s.type;
|
||||
}
|
||||
}
|
31
src/models/view/sendFileView.ts
Normal file
31
src/models/view/sendFileView.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { View } from './view';
|
||||
|
||||
import { SendFile } from '../domain/sendFile';
|
||||
|
||||
export class SendFileView implements View {
|
||||
id: string = null;
|
||||
url: string = null;
|
||||
size: string = null;
|
||||
sizeName: string = null;
|
||||
fileName: string = null;
|
||||
|
||||
constructor(f?: SendFile) {
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = f.id;
|
||||
this.url = f.url;
|
||||
this.size = f.size;
|
||||
this.sizeName = f.sizeName;
|
||||
}
|
||||
|
||||
get fileSize(): number {
|
||||
try {
|
||||
if (this.size != null) {
|
||||
return parseInt(this.size, null);
|
||||
}
|
||||
} catch { }
|
||||
return 0;
|
||||
}
|
||||
}
|
20
src/models/view/sendTextView.ts
Normal file
20
src/models/view/sendTextView.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { View } from './view';
|
||||
|
||||
import { SendText } from '../domain/sendText';
|
||||
|
||||
export class SendTextView implements View {
|
||||
text: string = null;
|
||||
hidden: boolean;
|
||||
|
||||
constructor(t?: SendText) {
|
||||
if (!t) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hidden = t.hidden;
|
||||
}
|
||||
|
||||
get maskedText(): string {
|
||||
return this.text != null ? '••••••••' : null;
|
||||
}
|
||||
}
|
49
src/models/view/sendView.ts
Normal file
49
src/models/view/sendView.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { SendType } from '../../enums/sendType';
|
||||
import { Utils } from '../../misc/utils';
|
||||
|
||||
import { Send } from '../domain/send';
|
||||
import { SymmetricCryptoKey } from '../domain/symmetricCryptoKey';
|
||||
|
||||
import { SendFileView } from './sendFileView';
|
||||
import { SendTextView } from './sendTextView';
|
||||
import { View } from './view';
|
||||
|
||||
export class SendView implements View {
|
||||
id: string = null;
|
||||
accessId: string = null;
|
||||
name: string = null;
|
||||
notes: string = null;
|
||||
key: ArrayBuffer;
|
||||
cryptoKey: SymmetricCryptoKey;
|
||||
type: SendType = null;
|
||||
text = new SendTextView();
|
||||
file = new SendFileView();
|
||||
maxAccessCount?: number = null;
|
||||
accessCount: number = 0;
|
||||
revisionDate: Date = null;
|
||||
deletionDate: Date = null;
|
||||
expirationDate: Date = null;
|
||||
password: string = null;
|
||||
disabled: boolean = false;
|
||||
|
||||
constructor(s?: Send) {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = s.id;
|
||||
this.accessId = s.accessId;
|
||||
this.type = s.type;
|
||||
this.maxAccessCount = s.maxAccessCount;
|
||||
this.accessCount = s.accessCount;
|
||||
this.revisionDate = s.revisionDate;
|
||||
this.deletionDate = s.deletionDate;
|
||||
this.expirationDate = s.expirationDate;
|
||||
this.disabled = s.disabled;
|
||||
this.password = s.password;
|
||||
}
|
||||
|
||||
get urlB64Key(): string {
|
||||
return Utils.fromBufferToUrlB64(this.key);
|
||||
}
|
||||
}
|
@ -46,6 +46,8 @@ import { PreloginRequest } from '../models/request/preloginRequest';
|
||||
import { RegisterRequest } from '../models/request/registerRequest';
|
||||
import { SeatRequest } from '../models/request/seatRequest';
|
||||
import { SelectionReadOnlyRequest } from '../models/request/selectionReadOnlyRequest';
|
||||
import { SendAccessRequest } from '../models/request/sendAccessRequest';
|
||||
import { SendRequest } from '../models/request/sendRequest';
|
||||
import { SetPasswordRequest } from '../models/request/setPasswordRequest';
|
||||
import { StorageRequest } from '../models/request/storageRequest';
|
||||
import { TaxInfoUpdateRequest } from '../models/request/taxInfoUpdateRequest';
|
||||
@ -97,6 +99,8 @@ import { PolicyResponse } from '../models/response/policyResponse';
|
||||
import { PreloginResponse } from '../models/response/preloginResponse';
|
||||
import { ProfileResponse } from '../models/response/profileResponse';
|
||||
import { SelectionReadOnlyResponse } from '../models/response/selectionReadOnlyResponse';
|
||||
import { SendAccessResponse } from '../models/response/sendAccessResponse';
|
||||
import { SendResponse } from '../models/response/sendResponse';
|
||||
import { SubscriptionResponse } from '../models/response/subscriptionResponse';
|
||||
import { SyncResponse } from '../models/response/syncResponse';
|
||||
import { TaxInfoResponse } from '../models/response/taxInfoResponse';
|
||||
@ -377,6 +381,47 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return this.send('DELETE', '/folders/' + id, null, true, false);
|
||||
}
|
||||
|
||||
// Send APIs
|
||||
|
||||
async getSend(id: string): Promise<SendResponse> {
|
||||
const r = await this.send('GET', '/sends/' + id, null, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
async postSendAccess(id: string, request: SendAccessRequest): Promise<SendAccessResponse> {
|
||||
const r = await this.send('POST', '/sends/access/' + id, request, false, true);
|
||||
return new SendAccessResponse(r);
|
||||
}
|
||||
|
||||
async getSends(): Promise<ListResponse<SendResponse>> {
|
||||
const r = await this.send('GET', '/sends', null, true, true);
|
||||
return new ListResponse(r, SendResponse);
|
||||
}
|
||||
|
||||
async postSend(request: SendRequest): Promise<SendResponse> {
|
||||
const r = await this.send('POST', '/sends', request, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
async postSendFile(data: FormData): Promise<SendResponse> {
|
||||
const r = await this.send('POST', '/sends/file', data, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
async putSend(id: string, request: SendRequest): Promise<SendResponse> {
|
||||
const r = await this.send('PUT', '/sends/' + id, request, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
async putSendRemovePassword(id: string): Promise<SendResponse> {
|
||||
const r = await this.send('PUT', '/sends/' + id + '/remove-password', null, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
deleteSend(id: string): Promise<any> {
|
||||
return this.send('DELETE', '/sends/' + id, null, true, false);
|
||||
}
|
||||
|
||||
// Cipher APIs
|
||||
|
||||
async getCipher(id: string): Promise<CipherResponse> {
|
||||
@ -1040,7 +1085,6 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
}
|
||||
|
||||
async preValidateSso(identifier: string): Promise<boolean> {
|
||||
|
||||
if (identifier == null || identifier === '') {
|
||||
throw new Error('Organization Identifier was not provided.');
|
||||
}
|
||||
@ -1063,7 +1107,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
const error = await this.handleError(response, false);
|
||||
const error = await this.handleError(response, false, true);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
@ -1111,13 +1155,13 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
const responseJson = await response.json();
|
||||
return responseJson;
|
||||
} else if (response.status !== 200) {
|
||||
const error = await this.handleError(response, false);
|
||||
const error = await this.handleError(response, false, authed);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleError(response: Response, tokenError: boolean): Promise<ErrorResponse> {
|
||||
if ((tokenError && response.status === 400) || response.status === 401 || response.status === 403) {
|
||||
private async handleError(response: Response, tokenError: boolean, authed: boolean): Promise<ErrorResponse> {
|
||||
if (authed && ((tokenError && response.status === 400) || response.status === 401 || response.status === 403)) {
|
||||
await this.logoutCallback(true);
|
||||
return null;
|
||||
}
|
||||
@ -1163,7 +1207,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken);
|
||||
return tokenResponse;
|
||||
} else {
|
||||
const error = await this.handleError(response, true);
|
||||
const error = await this.handleError(response, true, true);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
@ -353,6 +353,11 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return await this.stretchKey(pinKey);
|
||||
}
|
||||
|
||||
async makeSendKey(keyMaterial: ArrayBuffer): Promise<SymmetricCryptoKey> {
|
||||
const sendKey = await this.cryptoFunctionService.hkdf(keyMaterial, 'bitwarden-send', 'send', 64, 'sha256');
|
||||
return new SymmetricCryptoKey(sendKey);
|
||||
}
|
||||
|
||||
async hashPassword(password: string, key: SymmetricCryptoKey): Promise<string> {
|
||||
if (key == null) {
|
||||
key = await this.getKey();
|
||||
|
Loading…
Reference in New Issue
Block a user