shared login and logout commands

This commit is contained in:
Kyle Spearrin 2019-03-18 10:33:29 -04:00
parent b5b4222b32
commit d4c2b20a25
5 changed files with 464 additions and 13 deletions

287
package-lock.json generated
View File

@ -115,6 +115,16 @@
"@types/node": "*"
}
},
"@types/inquirer": {
"version": "0.0.43",
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-0.0.43.tgz",
"integrity": "sha512-xgyfKZVMFqE8aIKy1xfFVsX2MxyXUNgjgmbF6dRbR3sL+ZM5K4ka/9L4mmTwX8eTeVYtduyXu0gUVwVJa1HbNw==",
"dev": true,
"requires": {
"@types/rx": "*",
"@types/through": "*"
}
},
"@types/jasmine": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.8.tgz",
@ -175,6 +185,141 @@
"@types/node": "*"
}
},
"@types/rx": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz",
"integrity": "sha1-WY/JSla67ZdfGUV04PVy/Y5iekg=",
"dev": true,
"requires": {
"@types/rx-core": "*",
"@types/rx-core-binding": "*",
"@types/rx-lite": "*",
"@types/rx-lite-aggregates": "*",
"@types/rx-lite-async": "*",
"@types/rx-lite-backpressure": "*",
"@types/rx-lite-coincidence": "*",
"@types/rx-lite-experimental": "*",
"@types/rx-lite-joinpatterns": "*",
"@types/rx-lite-testing": "*",
"@types/rx-lite-time": "*",
"@types/rx-lite-virtualtime": "*"
}
},
"@types/rx-core": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/rx-core/-/rx-core-4.0.3.tgz",
"integrity": "sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA=",
"dev": true
},
"@types/rx-core-binding": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz",
"integrity": "sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ==",
"dev": true,
"requires": {
"@types/rx-core": "*"
}
},
"@types/rx-lite": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/rx-lite/-/rx-lite-4.0.6.tgz",
"integrity": "sha512-oYiDrFIcor9zDm0VDUca1UbROiMYBxMLMaM6qzz4ADAfOmA9r1dYEcAFH+2fsPI5BCCjPvV9pWC3X3flbrvs7w==",
"dev": true,
"requires": {
"@types/rx-core": "*",
"@types/rx-core-binding": "*"
}
},
"@types/rx-lite-aggregates": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz",
"integrity": "sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg==",
"dev": true,
"requires": {
"@types/rx-lite": "*"
}
},
"@types/rx-lite-async": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz",
"integrity": "sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw==",
"dev": true,
"requires": {
"@types/rx-lite": "*"
}
},
"@types/rx-lite-backpressure": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz",
"integrity": "sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA==",
"dev": true,
"requires": {
"@types/rx-lite": "*"
}
},
"@types/rx-lite-coincidence": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz",
"integrity": "sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ==",
"dev": true,
"requires": {
"@types/rx-lite": "*"
}
},
"@types/rx-lite-experimental": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz",
"integrity": "sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0=",
"dev": true,
"requires": {
"@types/rx-lite": "*"
}
},
"@types/rx-lite-joinpatterns": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz",
"integrity": "sha1-9w/jcFGKhDLykVjMkv+1a05K/D4=",
"dev": true,
"requires": {
"@types/rx-lite": "*"
}
},
"@types/rx-lite-testing": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz",
"integrity": "sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek=",
"dev": true,
"requires": {
"@types/rx-lite-virtualtime": "*"
}
},
"@types/rx-lite-time": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz",
"integrity": "sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw==",
"dev": true,
"requires": {
"@types/rx-lite": "*"
}
},
"@types/rx-lite-virtualtime": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz",
"integrity": "sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg==",
"dev": true,
"requires": {
"@types/rx-lite": "*"
}
},
"@types/through": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.29.tgz",
"integrity": "sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/tldjs": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@types/tldjs/-/tldjs-2.3.0.tgz",
@ -326,6 +471,11 @@
"ansi-wrap": "0.1.0"
}
},
"ansi-escapes": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ=="
},
"ansi-red": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz",
@ -1301,6 +1451,11 @@
}
}
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"chokidar": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
@ -1390,6 +1545,19 @@
"integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
"dev": true
},
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"requires": {
"restore-cursor": "^2.0.0"
}
},
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk="
},
"cliui": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
@ -2551,6 +2719,26 @@
}
}
},
"external-editor": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
"integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==",
"requires": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
"tmp": "^0.0.33"
},
"dependencies": {
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
}
}
},
"extglob": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
@ -2674,6 +2862,14 @@
"pend": "~1.2.0"
}
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"fileset": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
@ -3811,6 +4007,55 @@
}
}
},
"inquirer": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz",
"integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==",
"requires": {
"ansi-escapes": "^3.0.0",
"chalk": "^2.0.0",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.0",
"figures": "^2.0.0",
"lodash": "^4.17.10",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rxjs": "^6.1.0",
"string-width": "^2.1.0",
"strip-ansi": "^4.0.0",
"through": "^2.3.6"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@ -4964,6 +5209,11 @@
"mime-db": "~1.35.0"
}
},
"mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
},
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
@ -5059,6 +5309,11 @@
}
}
},
"mute-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
},
"nan": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
@ -5744,6 +5999,14 @@
"wrappy": "1"
}
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"requires": {
"mimic-fn": "^1.0.0"
}
},
"optimist": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
@ -5796,8 +6059,7 @@
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"p-finally": {
"version": "1.0.0",
@ -6541,6 +6803,15 @@
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
"dev": true
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"requires": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
}
},
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
@ -6582,6 +6853,14 @@
"inherits": "^2.0.1"
}
},
"run-async": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
"requires": {
"is-promise": "^2.1.0"
}
},
"rx": {
"version": "2.3.24",
"resolved": "https://registry.npmjs.org/rx/-/rx-2.3.24.tgz",
@ -7328,8 +7607,7 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
"version": "0.2.3",
@ -7403,7 +7681,6 @@
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.2"
}

View File

@ -26,6 +26,7 @@
"devDependencies": {
"@types/commander": "^2.12.2",
"@types/form-data": "^2.2.1",
"@types/inquirer": "^0.0.43",
"@types/jasmine": "^2.8.8",
"@types/lowdb": "^1.0.5",
"@types/lunr": "^2.1.6",
@ -81,6 +82,7 @@
"electron-store": "1.3.0",
"electron-updater": "4.0.6",
"form-data": "2.3.2",
"inquirer": "6.2.0",
"jsdom": "13.2.0",
"keytar": "4.2.1",
"lowdb": "1.0.0",

View File

@ -0,0 +1,150 @@
import * as program from 'commander';
import * as inquirer from 'inquirer';
import { TwoFactorProviderType } from '../../enums/twoFactorProviderType';
import { AuthResult } from '../../models/domain/authResult';
import { TwoFactorEmailRequest } from '../../models/request/twoFactorEmailRequest';
import { ApiService } from '../../abstractions/api.service';
import { AuthService } from '../../abstractions/auth.service';
import { I18nService } from '../../abstractions/i18n.service';
import { Response } from '../models/response';
import { MessageResponse } from '../models/response/messageResponse';
export class LoginCommand {
protected validatedParams: () => Promise<any>;
protected success: () => Promise<MessageResponse>;
constructor(protected authService: AuthService, protected apiService: ApiService,
protected i18nService: I18nService) { }
async run(email: string, password: string, cmd: program.Command) {
if (email == null || email === '') {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: 'input',
name: 'email',
message: 'Email address:',
});
email = answer.email;
}
if (email == null || email.trim() === '') {
return Response.badRequest('Email address is required.');
}
if (email.indexOf('@') === -1) {
return Response.badRequest('Email address is invalid.');
}
if (password == null || password === '') {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: 'password',
name: 'password',
message: 'Master password:',
});
password = answer.password;
}
if (password == null || password === '') {
return Response.badRequest('Master password is required.');
}
let twoFactorToken: string = cmd.code;
let twoFactorMethod: TwoFactorProviderType = null;
try {
if (cmd.method != null) {
twoFactorMethod = parseInt(cmd.method, null);
}
} catch (e) {
return Response.error('Invalid two-step login method.');
}
try {
if (this.validatedParams != null) {
await this.validatedParams();
}
let response: AuthResult = null;
if (twoFactorToken != null && twoFactorMethod != null) {
response = await this.authService.logInComplete(email, password, twoFactorMethod,
twoFactorToken, false);
} else {
response = await this.authService.logIn(email, password);
if (response.twoFactor) {
let selectedProvider: any = null;
const twoFactorProviders = this.authService.getSupportedTwoFactorProviders(null);
if (twoFactorProviders.length === 0) {
return Response.badRequest('No providers available for this client.');
}
if (twoFactorMethod != null) {
try {
selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0];
} catch (e) {
return Response.error('Invalid two-step login method.');
}
}
if (selectedProvider == null) {
if (twoFactorProviders.length === 1) {
selectedProvider = twoFactorProviders[0];
} else {
const options = twoFactorProviders.map((p) => p.name);
options.push(new inquirer.Separator());
options.push('Cancel');
const answer: inquirer.Answers =
await inquirer.createPromptModule({ output: process.stderr })({
type: 'list',
name: 'method',
message: 'Two-step login method:',
choices: options,
});
const i = options.indexOf(answer.method);
if (i === (options.length - 1)) {
return Response.error('Login failed.');
}
selectedProvider = twoFactorProviders[i];
}
}
if (twoFactorToken == null && response.twoFactorProviders.size > 1 &&
selectedProvider.type === TwoFactorProviderType.Email) {
const emailReq = new TwoFactorEmailRequest(this.authService.email,
this.authService.masterPasswordHash);
await this.apiService.postTwoFactorEmail(emailReq);
}
if (twoFactorToken == null) {
const answer: inquirer.Answers =
await inquirer.createPromptModule({ output: process.stderr })({
type: 'input',
name: 'token',
message: 'Two-step login code:',
});
twoFactorToken = answer.token;
if (twoFactorToken == null || twoFactorToken === '') {
return Response.badRequest('Code is required.');
}
}
response = await this.authService.logInTwoFactor(selectedProvider.type,
twoFactorToken, false);
}
}
if (response.twoFactor) {
return Response.error('Login failed.');
}
if (this.success != null) {
const res = await this.success();
return Response.success(res);
} else {
const res = new MessageResponse('You are logged in!', null);
return Response.success(res);
}
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -0,0 +1,19 @@
import * as program from 'commander';
import { AuthService } from '../../abstractions/auth.service';
import { I18nService } from '../../abstractions/i18n.service';
import { Response } from '../models/response';
import { MessageResponse } from '../models/response/messageResponse';
export class LogoutCommand {
constructor(private authService: AuthService, private i18nService: I18nService,
private logoutCallback: () => Promise<void>) { }
async run(cmd: program.Command) {
await this.logoutCallback();
this.authService.logOut(() => { /* Do nothing */ });
const res = new MessageResponse('You have logged out.', null);
return Response.success(res);
}
}

View File

@ -1,6 +1,7 @@
import * as program from 'commander';
import * as fetch from 'node-fetch';
import { I18nService } from '../../abstractions/i18n.service';
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
import { Response } from '../models/response';
@ -9,8 +10,8 @@ import { MessageResponse } from '../models/response/messageResponse';
export class UpdateCommand {
inPkg: boolean = false;
constructor(private platformUtilsService: PlatformUtilsService, private repoName: string,
private executableName: string) {
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
private repoName: string, private executableName: string, private showExtendedMessage: boolean) {
this.inPkg = !!(process as any).pkg;
}
@ -68,12 +69,14 @@ export class UpdateCommand {
res.message += 'You can download this update at ' + downloadUrl;
if (this.inPkg) {
res.message += '\n\nIf you installed this CLI through a package manager ' +
'you should probably update using its update command instead.';
} else {
res.message += '\n\nIf you installed this CLI through NPM ' +
'you should update using `npm install -g @bitwarden/' + this.repoName + '`';
if (this.showExtendedMessage) {
if (this.inPkg) {
res.message += '\n\nIf you installed this CLI through a package manager ' +
'you should probably update using its update command instead.';
} else {
res.message += '\n\nIf you installed this CLI through NPM ' +
'you should update using `npm install -g @bitwarden/' + this.repoName + '`';
}
}
return Response.success(res);
} else {