mirror of
https://github.com/bitwarden/desktop.git
synced 2024-11-14 10:16:02 +01:00
setting up more models and services
This commit is contained in:
parent
c39aab4ee7
commit
c3053ea3a7
@ -1,15 +1,15 @@
|
|||||||
var SiteRequest = function () {
|
var SiteRequest = function (site) {
|
||||||
this.folderId = null;
|
this.folderId = site.folderId;
|
||||||
this.name = null;
|
this.name = site.name ? site.name.encryptedString : null;
|
||||||
this.uri = null;
|
this.uri = site.uri ? site.uri.encryptedString : null;
|
||||||
this.username = null;
|
this.username = site.username ? site.username.encryptedString : null;
|
||||||
this.password = null;
|
this.password = site.password ? site.password.encryptedString : null;
|
||||||
this.notes = null;
|
this.notes = site.notes ? site.notes.encryptedString : null;
|
||||||
this.favorite = false;
|
this.favorite = site.favorite;
|
||||||
};
|
};
|
||||||
|
|
||||||
var FolderRequest = function () {
|
var FolderRequest = function (folder) {
|
||||||
this.name = null;
|
this.name = folder.name ? folder.name.encryptedString : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
var TokenRequest = function () {
|
var TokenRequest = function () {
|
||||||
|
41
src/models/dataModels.js
Normal file
41
src/models/dataModels.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
var FolderData = function (response, userId) {
|
||||||
|
var data = null;
|
||||||
|
if (response instanceof FolderResponse) {
|
||||||
|
data = response;
|
||||||
|
}
|
||||||
|
else if (response instanceof CipherResponse) {
|
||||||
|
data = response.Data;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw 'unsupported instance';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.id = response.Id;
|
||||||
|
this.userId = userId;
|
||||||
|
this.name = data.Name;
|
||||||
|
this.revisionDate = response.RevisionDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
var SiteData = function (response, userId) {
|
||||||
|
var data = null;
|
||||||
|
if (response instanceof SiteResponse) {
|
||||||
|
data = response;
|
||||||
|
}
|
||||||
|
else if (response instanceof CipherResponse) {
|
||||||
|
data = response.Data;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw 'unsupported instance';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.id = response.Id;
|
||||||
|
this.folderId = response.FolderId;
|
||||||
|
this.userId = userId;
|
||||||
|
this.name = data.Name;
|
||||||
|
this.uri = data.Uri;
|
||||||
|
this.username = data.Username;
|
||||||
|
this.password = data.Password;
|
||||||
|
this.notes = data.Notes;
|
||||||
|
this.favorite = response.Favorite;
|
||||||
|
this.revisionDate = response.RevisionDate;
|
||||||
|
};
|
37
src/models/domainModels.js
Normal file
37
src/models/domainModels.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
var CipherString = function (encryptedString) {
|
||||||
|
this.encryptedString = encryptedString;
|
||||||
|
|
||||||
|
if (encryptedString) {
|
||||||
|
this.initializationVector = this.encryptedString.split('|')[0];
|
||||||
|
this.cipherText = this.encryptedString.split('|')[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
!function () {
|
||||||
|
var _decryptedValue = null;
|
||||||
|
|
||||||
|
CipherString.prototype.decrypt = function (callback) {
|
||||||
|
if (!_decryptedValue) {
|
||||||
|
var cryptoService = chrome.extension.getBackgroundPage().cryptoService;
|
||||||
|
_decryptedValue = cryptoService.Decrypt(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _decryptedValue;
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
var Site = function (obj) {
|
||||||
|
this.id = obj.id;
|
||||||
|
this.folderId = obj.folderId;
|
||||||
|
this.name = new CipherString(obj.name);
|
||||||
|
this.uri = new CipherString(obj.uri);
|
||||||
|
this.username = new CipherString(obj.username);
|
||||||
|
this.password = new CipherString(obj.password);
|
||||||
|
this.notes = new CipherString(obj.notes);
|
||||||
|
this.favorite = new obj.favorite;
|
||||||
|
};
|
||||||
|
|
||||||
|
var Folder = function (obj) {
|
||||||
|
this.id = obj.id;
|
||||||
|
this.name = new CipherString(obj.name);
|
||||||
|
};
|
@ -5,13 +5,13 @@
|
|||||||
|
|
||||||
!function () {
|
!function () {
|
||||||
// Account APIs
|
// Account APIs
|
||||||
|
|
||||||
ApiService.prototype.getProfile = function (success, error) {
|
ApiService.prototype.getProfile = function (success, error) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.tokenService.getToken(function (token) {
|
this.tokenService.getToken(function (token) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: self.baseUrl + '/accounts/profile',
|
url: self.baseUrl + '/accounts/profile?access_token=' + token,
|
||||||
data: 'access_token=' + token,
|
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
success(new ProfileResponse(response))
|
success(new ProfileResponse(response))
|
||||||
@ -25,16 +25,122 @@
|
|||||||
|
|
||||||
// Site APIs
|
// Site APIs
|
||||||
|
|
||||||
|
ApiService.prototype.getSite = function (id, success, error) {
|
||||||
|
var self = this;
|
||||||
|
this.tokenService.getToken(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: self.baseUrl + '/sites/' + id + '?access_token=' + token,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
success(new SiteResponse(response))
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiService.prototype.postSite = function (siteRequest, success, error) {
|
||||||
|
var self = this;
|
||||||
|
this.tokenService.getToken(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: self.baseUrl + '/sites?access_token=' + token,
|
||||||
|
data: siteRequest,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
success(new SiteResponse(response))
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiService.prototype.putSite = function (id, siteRequest, success, error) {
|
||||||
|
var self = this;
|
||||||
|
this.tokenService.getToken(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: self.baseUrl + '/sites/' + id + '?access_token=' + token,
|
||||||
|
data: siteRequest,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
success(new SiteResponse(response))
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Folder APIs
|
// Folder APIs
|
||||||
|
|
||||||
|
ApiService.prototype.getFolder = function (id, success, error) {
|
||||||
|
var self = this;
|
||||||
|
this.tokenService.getToken(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: self.baseUrl + '/folders/' + id + '?access_token=' + token,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
success(new FolderResponse(response))
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiService.prototype.postFolder = function (folderRequest, success, error) {
|
||||||
|
var self = this;
|
||||||
|
this.tokenService.getToken(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: self.baseUrl + '/folders?access_token=' + token,
|
||||||
|
data: folderRequest,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
success(new FolderResponse(response))
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiService.prototype.putFolder = function (id, folderRequest, success, error) {
|
||||||
|
var self = this;
|
||||||
|
this.tokenService.getToken(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: self.baseUrl + '/folders/' + id + '?access_token=' + token,
|
||||||
|
data: folderRequest,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
success(new FolderResponse(response))
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Cipher APIs
|
// Cipher APIs
|
||||||
|
|
||||||
ApiService.prototype.getCipher = function (id, success, error) {
|
ApiService.prototype.getCipher = function (id, success, error) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.tokenService.getToken(function (token) {
|
this.tokenService.getToken(function (token) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: self.baseUrl + '/ciphers/' + id,
|
url: self.baseUrl + '/ciphers/' + id + '?access_token=' + token,
|
||||||
data: 'access_token=' + token,
|
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
success(new CipherResponse(response))
|
success(new CipherResponse(response))
|
||||||
@ -51,8 +157,7 @@
|
|||||||
this.tokenService.getToken(function (token) {
|
this.tokenService.getToken(function (token) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: self.baseUrl + '/ciphers',
|
url: self.baseUrl + '/ciphers?access_token=' + token,
|
||||||
data: 'access_token=' + token,
|
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
var data = [];
|
var data = [];
|
||||||
@ -69,6 +174,23 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ApiService.prototype.deleteCipher = function (id, success, error) {
|
||||||
|
var self = this;
|
||||||
|
this.tokenService.getToken(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: self.baseUrl + '/ciphers/' + id + '/delete?access_token=' + token,
|
||||||
|
dataType: 'json',
|
||||||
|
success: success,
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
function handleError(errorCallback, jqXHR, textStatus, errorThrown) {
|
function handleError(errorCallback, jqXHR, textStatus, errorThrown) {
|
||||||
errorCallback(new ErrorResponse(jqXHR));
|
errorCallback(new ErrorResponse(jqXHR));
|
||||||
}
|
}
|
||||||
|
@ -108,21 +108,16 @@
|
|||||||
var ct = ctJson.match(/"ct":"([^"]*)"/)[1];
|
var ct = ctJson.match(/"ct":"([^"]*)"/)[1];
|
||||||
var iv = sjcl.codec.base64.fromBits(response.iv);
|
var iv = sjcl.codec.base64.fromBits(response.iv);
|
||||||
|
|
||||||
return iv + "|" + ct;
|
return new CipherString(iv + "|" + ct);
|
||||||
};
|
};
|
||||||
|
|
||||||
CryptoService.prototype.decrypt = function (encValue) {
|
CryptoService.prototype.decrypt = function (cipherString) {
|
||||||
if (!this.getAes()) {
|
if (!this.getAes()) {
|
||||||
throw 'AES encryption unavailable.';
|
throw 'AES encryption unavailable.';
|
||||||
}
|
}
|
||||||
|
|
||||||
var encPieces = encValue.split('|');
|
var ivBits = sjcl.codec.base64.toBits(cipherString.initializationVector);
|
||||||
if (encPieces.length !== 2) {
|
var ctBits = sjcl.codec.base64.toBits(cipherString.cipherText);
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
var ivBits = sjcl.codec.base64.toBits(encPieces[0]);
|
|
||||||
var ctBits = sjcl.codec.base64.toBits(encPieces[1]);
|
|
||||||
|
|
||||||
var decBits = sjcl.mode.cbc.decrypt(this.getAes(), ctBits, ivBits, null);
|
var decBits = sjcl.mode.cbc.decrypt(this.getAes(), ctBits, ivBits, null);
|
||||||
return sjcl.codec.utf8String.fromBits(decBits);
|
return sjcl.codec.utf8String.fromBits(decBits);
|
||||||
|
@ -1,104 +1,131 @@
|
|||||||
function TokenService() {
|
function SiteService(cryptoService, userService, apiService) {
|
||||||
|
this.cryptoService = cryptoService;
|
||||||
|
this.userService = userService;
|
||||||
|
this.apiService = apiService;
|
||||||
};
|
};
|
||||||
|
|
||||||
!function () {
|
!function () {
|
||||||
var _token;
|
var ciphersKey = 'ciphers_' + this.userService.userId;
|
||||||
|
|
||||||
TokenService.prototype.setToken = function (token, callback) {
|
SiteService.prototype.get = function (id, callback) {
|
||||||
if (!callback || typeof callback !== 'function') {
|
if (!callback || typeof callback !== 'function') {
|
||||||
throw 'callback function required';
|
throw 'callback function required';
|
||||||
}
|
}
|
||||||
|
|
||||||
_token = token;
|
chrome.storage.local.get(ciphersKey, function (obj) {
|
||||||
chrome.storage.local.set({
|
if (!obj) {
|
||||||
'authBearer': token
|
callback(null);
|
||||||
}, function () {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
TokenService.prototype.getToken = function (callback) {
|
|
||||||
if (!callback || typeof callback !== 'function') {
|
|
||||||
throw 'callback function required';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_token) {
|
|
||||||
return callback(_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.storage.local.get('authBearer', function (obj) {
|
|
||||||
if (obj && obj.authBearer) {
|
|
||||||
_token = obj.authBearer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(_token);
|
var sites = obj[ciphersKey];
|
||||||
|
if (id in sites) {
|
||||||
|
callback(new Site(sites[id]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
TokenService.prototype.clearToken = function (callback) {
|
SiteService.prototype.getAll = function (callback) {
|
||||||
if (!callback || typeof callback !== 'function') {
|
if (!callback || typeof callback !== 'function') {
|
||||||
throw 'callback function required';
|
throw 'callback function required';
|
||||||
}
|
}
|
||||||
|
|
||||||
_token = null;
|
chrome.storage.local.get(ciphersKey, function (obj) {
|
||||||
chrome.storage.local.remove('authBearer', function () {
|
if (!obj) {
|
||||||
callback();
|
callback([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sites = obj[ciphersKey];
|
||||||
|
var response = [];
|
||||||
|
for (var id in sites) {
|
||||||
|
response.push(new Site(sites[id]));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(response);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// jwthelper methods
|
SiteService.prototype.save = function (site, callback) {
|
||||||
// ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js
|
if (!callback || typeof callback !== 'function') {
|
||||||
|
throw 'callback function required';
|
||||||
TokenService.prototype.decodeToken = function (token) {
|
|
||||||
var parts = token.split('.');
|
|
||||||
|
|
||||||
if (parts.length !== 3) {
|
|
||||||
throw new Error('JWT must have 3 parts');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded = urlBase64Decode(parts[1]);
|
var newRecord = site.id === null,
|
||||||
if (!decoded) {
|
self = this;
|
||||||
throw new Error('Cannot decode the token');
|
|
||||||
|
var request = new SiteRequest(site);
|
||||||
|
if (newRecord) {
|
||||||
|
self.apiService.postSite(request, apiSuccess, handleError);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.apiService.putSite(site.id, request, apiSuccess, handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.parse(decoded);
|
function apiSuccess(response) {
|
||||||
|
userService.getUserId(function (userId) {
|
||||||
|
var data = new SiteData(response, userId);
|
||||||
|
|
||||||
|
chrome.storage.local.get(ciphersKey, function (obj) {
|
||||||
|
if (!obj) {
|
||||||
|
obj = {};
|
||||||
|
obj[ciphersKey] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var sites = obj[ciphersKey];
|
||||||
|
if (!newRecord && site.id in sites) {
|
||||||
|
sites[site.id] = data;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sites.push(data);
|
||||||
|
site.id = data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[ciphersKey] = sites;
|
||||||
|
chrome.storage.local.set(obj, function () {
|
||||||
|
callback(site);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TokenService.prototype.getTokenExpirationDate = function (token) {
|
SiteService.prototype.delete = function (id, callback) {
|
||||||
var decoded = this.decodeToken(token);
|
if (!callback || typeof callback !== 'function') {
|
||||||
|
throw 'callback function required';
|
||||||
if (typeof decoded.exp === "undefined") {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var d = new Date(0); // The 0 here is the key, which sets the date to the epoch
|
self.apiService.deleteCipher(id, apiSuccess, handleError);
|
||||||
d.setUTCSeconds(decoded.exp);
|
|
||||||
|
|
||||||
return d;
|
function apiSuccess(response) {
|
||||||
};
|
userService.getUserId(function (userId) {
|
||||||
|
chrome.storage.local.get(ciphersKey, function (obj) {
|
||||||
|
if (!obj) {
|
||||||
|
obj = {};
|
||||||
|
obj[ciphersKey] = [];
|
||||||
|
}
|
||||||
|
|
||||||
TokenService.prototype.isTokenExpired = function (token, offsetSeconds) {
|
var sites = obj[ciphersKey];
|
||||||
var d = this.getTokenExpirationDate(token);
|
if (id in sites) {
|
||||||
offsetSeconds = offsetSeconds || 0;
|
var index = sites.indexOf(sites[id]);
|
||||||
if (d === null) {
|
sites.splice(index, 1);
|
||||||
return false;
|
|
||||||
|
obj[ciphersKey] = sites;
|
||||||
|
chrome.storage.local.set(obj, function () {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token expired?
|
|
||||||
return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function urlBase64Decode(str) {
|
|
||||||
var output = str.replace(/-/g, '+').replace(/_/g, '/');
|
function handleError() {
|
||||||
switch (output.length % 4) {
|
// TODO: check for unauth or forbidden and logout
|
||||||
case 0: { break; }
|
}
|
||||||
case 2: { output += '=='; break; }
|
|
||||||
case 3: { output += '='; break; }
|
|
||||||
default: {
|
|
||||||
throw 'Illegal base64url string!';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return window.decodeURIComponent(escape(window.atob(output))); //polyfill https://github.com/davidchambers/Base64.js
|
|
||||||
};
|
|
||||||
}();
|
}();
|
||||||
|
@ -6,6 +6,12 @@
|
|||||||
!function () {
|
!function () {
|
||||||
var _userProfile = null;
|
var _userProfile = null;
|
||||||
|
|
||||||
|
UserService.prototype.getUserId = function (callback) {
|
||||||
|
this.getUserProfile(function (profile) {
|
||||||
|
callback(profile.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
UserService.prototype.getUserProfile = function (callback) {
|
UserService.prototype.getUserProfile = function (callback) {
|
||||||
if (!callback || typeof callback !== 'function') {
|
if (!callback || typeof callback !== 'function') {
|
||||||
throw 'callback function required';
|
throw 'callback function required';
|
||||||
|
Loading…
Reference in New Issue
Block a user