bitwarden-estensione-browser/src/services/api.service.ts

455 lines
17 KiB
TypeScript
Raw Normal View History

2017-11-03 16:59:45 +01:00
import AppIdService from './appId.service';
import ConstantsService from './constants.service';
import TokenService from './token.service';
import UtilsService from './utils.service';
import EnvironmentUrls from '../models/domain/environmentUrls';
import { CipherRequest } from '../models/request/cipherRequest';
import { DeviceRequest } from '../models/request/deviceRequest';
import { DeviceTokenRequest } from '../models/request/deviceTokenRequest';
import { FolderRequest } from '../models/request/folderRequest';
import { PasswordHintRequest } from '../models/request/passwordHintRequest';
import { RegisterRequest } from '../models/request/registerRequest';
import { TokenRequest } from '../models/request/tokenRequest';
import { TwoFactorEmailRequest } from '../models/request/twoFactorEmailRequest';
import { AttachmentResponse } from '../models/response/attachmentResponse';
import { CipherResponse } from '../models/response/cipherResponse';
import { DeviceResponse } from '../models/response/deviceResponse';
import { DomainsResponse } from '../models/response/domainsResponse';
import { ErrorResponse } from '../models/response/errorResponse';
import { FolderResponse } from '../models/response/folderResponse';
import { GlobalDomainResponse } from '../models/response/globalDomainResponse';
import { IdentityTokenResponse } from '../models/response/identityTokenResponse';
import { KeysResponse } from '../models/response/keysResponse';
import { ListResponse } from '../models/response/listResponse';
import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse';
import { ProfileResponse } from '../models/response/profileResponse';
import { SyncResponse } from '../models/response/syncResponse';
export default class ApiService {
urlsSet: boolean = false;
baseUrl: string;
identityBaseUrl: string;
logoutCallback: Function;
2017-12-15 15:58:26 +01:00
constructor(private tokenService: TokenService, private utilsService: UtilsService,
logoutCallback: Function) {
2017-11-03 16:59:45 +01:00
this.logoutCallback = logoutCallback;
}
setUrls(urls: EnvironmentUrls) {
this.urlsSet = true;
if (urls.base != null) {
this.baseUrl = urls.base + '/api';
this.identityBaseUrl = urls.base + '/identity';
return;
}
if (urls.api != null && urls.identity != null) {
this.baseUrl = urls.api;
this.identityBaseUrl = urls.identity;
return;
}
/* tslint:disable */
// Desktop
//this.baseUrl = 'http://localhost:4000';
//this.identityBaseUrl = 'http://localhost:33656';
// Desktop HTTPS
//this.baseUrl = 'https://localhost:44377';
//this.identityBaseUrl = 'https://localhost:44392';
// Desktop external
//this.baseUrl = 'http://192.168.1.3:4000';
//this.identityBaseUrl = 'http://192.168.1.3:33656';
// Preview
//this.baseUrl = 'https://preview-api.bitwarden.com';
//this.identityBaseUrl = 'https://preview-identity.bitwarden.com';
// Production
this.baseUrl = 'https://api.bitwarden.com';
this.identityBaseUrl = 'https://identity.bitwarden.com';
/* tslint:enable */
}
// Auth APIs
async postIdentityToken(request: TokenRequest): Promise<IdentityTokenResponse | any> {
const response = await fetch(new Request(this.identityBaseUrl + '/connect/token', {
body: this.qsStringify(request.toIdentityToken()),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Accept': 'application/json',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'POST',
}));
2017-11-16 17:12:41 +01:00
let responseJson: any = null;
const typeHeader = response.headers.get('content-type');
if (typeHeader != null && typeHeader.indexOf('application/json') > -1) {
responseJson = await response.json();
}
if (responseJson != null) {
if (response.status === 200) {
return new IdentityTokenResponse(responseJson);
} else if (response.status === 400 && responseJson.TwoFactorProviders2 &&
Object.keys(responseJson.TwoFactorProviders2).length) {
await this.tokenService.clearTwoFactorToken(request.email);
return responseJson.TwoFactorProviders2;
}
2017-11-03 16:59:45 +01:00
}
2017-11-16 17:12:41 +01:00
return Promise.reject(new ErrorResponse(responseJson, response.status, true));
2017-11-03 16:59:45 +01:00
}
async refreshIdentityToken(): Promise<any> {
2017-11-07 16:43:02 +01:00
try {
await this.doRefreshToken();
} catch (e) {
2017-11-03 16:59:45 +01:00
return Promise.reject(null);
}
}
// Two Factor APIs
async postTwoFactorEmail(request: TwoFactorEmailRequest): Promise<any> {
const response = await fetch(new Request(this.baseUrl + '/two-factor/send-email-login', {
body: JSON.stringify(request),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Content-Type': 'application/json; charset=utf-8',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'POST',
}));
if (response.status !== 200) {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
// Account APIs
async getAccountRevisionDate(): Promise<number> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/accounts/revision-date', {
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-12-15 15:58:26 +01:00
'Accept': 'application/json',
'Authorization': authHeader,
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
}));
if (response.status === 200) {
return (await response.json() as number);
} else {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
async postPasswordHint(request: PasswordHintRequest): Promise<any> {
const response = await fetch(new Request(this.baseUrl + '/accounts/password-hint', {
body: JSON.stringify(request),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Content-Type': 'application/json; charset=utf-8',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'POST',
}));
if (response.status !== 200) {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
async postRegister(request: RegisterRequest): Promise<any> {
const response = await fetch(new Request(this.baseUrl + '/accounts/register', {
body: JSON.stringify(request),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Content-Type': 'application/json; charset=utf-8',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'POST',
}));
if (response.status !== 200) {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
// Folder APIs
async postFolder(request: FolderRequest): Promise<FolderResponse> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/folders', {
body: JSON.stringify(request),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Accept': 'application/json',
'Authorization': authHeader,
'Content-Type': 'application/json; charset=utf-8',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'POST',
}));
if (response.status === 200) {
const responseJson = await response.json();
return new FolderResponse(responseJson);
} else {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
async putFolder(id: string, request: FolderRequest): Promise<FolderResponse> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/folders/' + id, {
body: JSON.stringify(request),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Accept': 'application/json',
'Authorization': authHeader,
'Content-Type': 'application/json; charset=utf-8',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'PUT',
}));
if (response.status === 200) {
const responseJson = await response.json();
return new FolderResponse(responseJson);
} else {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
async deleteFolder(id: string): Promise<any> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/folders/' + id, {
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-12-15 15:58:26 +01:00
'Authorization': authHeader,
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'DELETE',
}));
if (response.status !== 200) {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
// Cipher APIs
async postCipher(request: CipherRequest): Promise<CipherResponse> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/ciphers', {
body: JSON.stringify(request),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Accept': 'application/json',
'Authorization': authHeader,
'Content-Type': 'application/json; charset=utf-8',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'POST',
}));
if (response.status === 200) {
const responseJson = await response.json();
return new CipherResponse(responseJson);
} else {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
async putCipher(id: string, request: CipherRequest): Promise<CipherResponse> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/ciphers/' + id, {
body: JSON.stringify(request),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Accept': 'application/json',
'Authorization': authHeader,
'Content-Type': 'application/json; charset=utf-8',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'PUT',
}));
if (response.status === 200) {
const responseJson = await response.json();
return new CipherResponse(responseJson);
} else {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
async deleteCipher(id: string): Promise<any> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/ciphers/' + id, {
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-12-15 15:58:26 +01:00
'Authorization': authHeader,
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'DELETE',
}));
if (response.status !== 200) {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
// Attachments APIs
async postCipherAttachment(id: string, data: FormData): Promise<CipherResponse> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/ciphers/' + id + '/attachment', {
body: data,
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-12-15 15:58:26 +01:00
'Accept': 'application/json',
'Authorization': authHeader,
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'POST',
}));
if (response.status === 200) {
const responseJson = await response.json();
return new CipherResponse(responseJson);
} else {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
async deleteCipherAttachment(id: string, attachmentId: string): Promise<any> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/ciphers/' + id + '/attachment/' + attachmentId, {
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-12-15 15:58:26 +01:00
'Authorization': authHeader,
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'DELETE',
}));
if (response.status !== 200) {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
// Sync APIs
async getSync(): Promise<SyncResponse> {
const authHeader = await this.handleTokenState();
const response = await fetch(new Request(this.baseUrl + '/sync', {
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-12-15 15:58:26 +01:00
'Accept': 'application/json',
'Authorization': authHeader,
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
}));
if (response.status === 200) {
const responseJson = await response.json();
return new SyncResponse(responseJson);
} else {
const error = await this.handleError(response, false);
return Promise.reject(error);
}
}
// Helpers
private async handleError(response: Response, tokenError: boolean): Promise<ErrorResponse> {
if ((tokenError && response.status === 400) || response.status === 401 || response.status === 403) {
2017-12-07 04:35:21 +01:00
this.logoutCallback(true);
2017-11-03 16:59:45 +01:00
return null;
}
2017-11-16 17:12:41 +01:00
let responseJson: any = null;
const typeHeader = response.headers.get('content-type');
if (typeHeader != null && typeHeader.indexOf('application/json') > -1) {
responseJson = await response.json();
}
return new ErrorResponse(responseJson, response.status, tokenError);
2017-11-03 16:59:45 +01:00
}
private async handleTokenState(): Promise<string> {
2017-11-07 16:43:02 +01:00
let accessToken: string;
if (this.tokenService.tokenNeedsRefresh()) {
const tokenResponse = await this.doRefreshToken();
accessToken = tokenResponse.accessToken;
} else {
accessToken = await this.tokenService.getToken();
2017-11-03 16:59:45 +01:00
}
2017-11-07 16:43:02 +01:00
return 'Bearer ' + accessToken;
2017-11-03 16:59:45 +01:00
}
2017-11-07 16:43:02 +01:00
private async doRefreshToken(): Promise<IdentityTokenResponse> {
2017-11-03 16:59:45 +01:00
const refreshToken = await this.tokenService.getRefreshToken();
if (refreshToken == null || refreshToken === '') {
throw new Error();
}
const response = await fetch(new Request(this.identityBaseUrl + '/connect/token', {
body: this.qsStringify({
grant_type: 'refresh_token',
client_id: 'browser',
refresh_token: refreshToken,
}),
cache: 'no-cache',
2017-11-05 02:37:35 +01:00
headers: new Headers({
2017-11-03 16:59:45 +01:00
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Accept': 'application/json',
2017-12-15 17:12:13 +01:00
'Device-Type': this.utilsService.getBrowser().toString(),
2017-11-05 02:37:35 +01:00
}),
2017-11-03 16:59:45 +01:00
method: 'POST',
}));
if (response.status === 200) {
2017-11-07 16:43:02 +01:00
const responseJson = await response.json();
const tokenResponse = new IdentityTokenResponse(responseJson);
await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken);
return tokenResponse;
2017-11-03 16:59:45 +01:00
} else {
2017-11-16 17:12:41 +01:00
const error = await this.handleError(response, true);
return Promise.reject(error);
2017-11-03 16:59:45 +01:00
}
}
private qsStringify(params: any): string {
return Object.keys(params).map((key) => {
return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
}).join('&');
}
}