sardines-built-in-services
Version:
A built-in service provider for sardines.js
372 lines (371 loc) • 16.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TempleteAccount = exports.basePostgresDBStruct = void 0;
var bcrypt = require("bcryptjs");
var postgresql_1 = require("../storage/postgresql");
var cryptPassword = function (password) { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2, new Promise(function (resolve, reject) {
bcrypt.genSalt(10, function (err, salt) {
if (err)
reject(err);
bcrypt.hash(password, salt, function (err, hash) {
if (err)
reject(err);
else
resolve(hash);
});
});
})];
});
}); };
var comparePassword = function (plainPass, hashword) {
return new Promise(function (resolve, reject) {
bcrypt.compare(plainPass, hashword, function (err, isPasswordMatch) {
if (err)
reject(err);
else
resolve(isPasswordMatch);
});
});
};
var tokenAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var genToken = function (length) {
var token = '';
for (var i = 0; i < length; i++) {
token += tokenAlphabet[Math.round(Math.random() * (tokenAlphabet.length - 1))];
}
return token;
};
exports.basePostgresDBStruct = {
account: {
id: 'UUID PRIMARY KEY DEFAULT uuid_generate_v4()',
create_on: 'TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP',
last_access_on: 'TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP',
name: 'VARCHAR(30) UNIQUE NOT NULL',
password: 'VARCHAR(64) NOT NULL',
password_expire_on: 'TIMESTAMP(3)',
email: 'VARCHAR(100) UNIQUE',
is_email_varified: 'Boolean NOT NULL DEFAULT false',
mobile: 'VARCHAR(20) UNIQUE',
is_mobile_varified: 'Boolean NOT NULL DEFAULT false',
can_login: 'Boolean NOT NULL DEFAULT true',
can_manage_accounts: 'Boolean NOT NULL DEFAULT false'
},
token: {
account_id: 'UUID',
token: 'VARCHAR(100) UNIQUE NOT NULL',
expire_on: 'TIMESTAMP(3)'
}
};
var TempleteAccount = (function () {
function TempleteAccount() {
this.db = null;
this.isInited = false;
this.dbStruct = exports.basePostgresDBStruct;
this.tokenExpireInSeconds = 1800;
this.tokenCache = new Map();
}
TempleteAccount.prototype.setupDB = function (serverSettings, dbStruct) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (dbStruct) {
this.dbStruct = postgresql_1.Database.mergeDbStructs(exports.basePostgresDBStruct, dbStruct);
}
this.db = new postgresql_1.Database(serverSettings, this.dbStruct);
return [2, this.db];
});
});
};
TempleteAccount.prototype.touch = function (tableName, obj) {
return __awaiter(this, void 0, void 0, function () {
var _i, obj_1, o, objId, colType, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!tableName || !this.dbStruct[tableName])
return [2];
if (!Array.isArray(obj)) return [3, 5];
_i = 0, obj_1 = obj;
_a.label = 1;
case 1:
if (!(_i < obj_1.length)) return [3, 4];
o = obj_1[_i];
return [4, this.touch(tableName, o)];
case 2:
_a.sent();
_a.label = 3;
case 3:
_i++;
return [3, 1];
case 4: return [3, 10];
case 5:
objId = { id: null };
if (typeof obj === 'string') {
objId.id = obj;
}
else if (typeof obj === 'object' && obj && obj.id) {
objId.id = obj.id;
}
if (!objId.id) return [3, 10];
_a.label = 6;
case 6:
_a.trys.push([6, 9, , 10]);
colType = this.db.getColumnType(tableName, 'last_access_on');
if (!colType) return [3, 8];
return [4, this.db.set(tableName, { last_access_on: 'CURRENT_TIMESTAMP' }, objId)];
case 7:
_a.sent();
_a.label = 8;
case 8: return [3, 10];
case 9:
e_1 = _a.sent();
if (e_1)
return [2];
return [3, 10];
case 10: return [2];
}
});
});
};
TempleteAccount.prototype.queryAccount = function (account) {
return __awaiter(this, void 0, void 0, function () {
var accountForQuery;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
accountForQuery = Object.assign({}, account);
if (accountForQuery.password)
delete accountForQuery.password;
return [4, this.db.get('account', accountForQuery)];
case 1: return [2, _a.sent()];
}
});
});
};
TempleteAccount.prototype.createOrUpdateAccount = function (account) {
return __awaiter(this, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!account.password) return [3, 2];
_a = account;
return [4, cryptPassword(account.password)];
case 1:
_a.password = _b.sent();
return [3, 3];
case 2:
if (!account.id)
throw 'password is required to create account';
_b.label = 3;
case 3:
if (!!account.id) return [3, 5];
return [4, this.db.set('account', account)];
case 4: return [2, _b.sent()];
case 5: return [2, this.db.set('account', account, { id: account.id })];
}
});
});
};
TempleteAccount.prototype.validatePassword = function (password) {
if (!password) {
throw 'Password can not be empty';
}
else if (password.length < 6) {
throw 'Password shall be longer than 6';
}
};
TempleteAccount.prototype.signUp = function (account, password) {
return __awaiter(this, void 0, void 0, function () {
var accountInst, accountObj, tokenObj;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.validatePassword(password);
return [4, this.queryAccount(account)];
case 1:
accountInst = _a.sent();
if (!accountInst) return [3, 2];
throw 'Account already exists';
case 2:
accountObj = Object.assign({}, account);
accountObj.password = password;
return [4, this.createOrUpdateAccount(accountObj)];
case 3:
accountInst = _a.sent();
return [4, this.createToken(accountInst.id)];
case 4:
tokenObj = _a.sent();
return [2, tokenObj.token];
}
});
});
};
TempleteAccount.prototype.createToken = function (accountId, expireInSeconds) {
if (expireInSeconds === void 0) { expireInSeconds = this.tokenExpireInSeconds; }
return __awaiter(this, void 0, void 0, function () {
var tokenQuery, tokenInDb, tokenObj;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
tokenQuery = {
account_id: accountId,
token: genToken(30)
};
return [4, this.db.get('token', tokenQuery)];
case 1:
tokenInDb = _a.sent();
_a.label = 2;
case 2:
if (!tokenInDb) return [3, 4];
tokenQuery.token = genToken(32);
return [4, this.db.get('token', tokenQuery)];
case 3:
tokenInDb = _a.sent();
return [3, 2];
case 4:
tokenObj = tokenQuery;
if (expireInSeconds > 0) {
tokenObj.expire_on = Date.now() + 1000 * expireInSeconds;
}
else {
tokenObj.expire_on = Number.MAX_SAFE_INTEGER;
}
return [4, this.db.set('token', tokenObj)];
case 5:
_a.sent();
this.tokenCache.set(tokenObj.token, tokenObj);
return [2, tokenObj];
}
});
});
};
TempleteAccount.prototype.validateToken = function (token, update) {
if (update === void 0) { update = false; }
return __awaiter(this, void 0, void 0, function () {
var tokenObj;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!token)
'token is empty';
tokenObj = null;
if (!this.tokenCache.has(token)) return [3, 1];
tokenObj = this.tokenCache.get(token);
return [3, 3];
case 1: return [4, this.db.get('token', { token: token })];
case 2:
tokenObj = _a.sent();
_a.label = 3;
case 3:
if (!!tokenObj) return [3, 4];
throw 'Invalid token';
case 4:
if (!(tokenObj.expire_on && tokenObj.expire_on - Date.now() < 0)) return [3, 5];
throw 'token expired';
case 5:
if (!update) return [3, 7];
tokenObj.expire_on = Date.now() + 1000 * this.tokenExpireInSeconds;
return [4, this.db.set('token', tokenObj, { account_id: tokenObj.account_id, token: tokenObj.token })];
case 6:
_a.sent();
this.tokenCache.set(token, tokenObj);
_a.label = 7;
case 7: return [2, tokenObj];
}
});
});
};
TempleteAccount.prototype.signIn = function (account, password) {
return __awaiter(this, void 0, void 0, function () {
var accountInst, tokenObj;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.validatePassword(password);
return [4, this.queryAccount(account)];
case 1:
accountInst = _a.sent();
if (!accountInst) return [3, 9];
return [4, comparePassword(password, accountInst.password)];
case 2:
if (!_a.sent()) return [3, 7];
return [4, this.db.get('token', { account_id: accountInst.id }, { expire_on: -1 })];
case 3:
tokenObj = _a.sent();
if (!(!tokenObj || tokenObj.expire_on.getTime() <= Date.now() + 30000)) return [3, 5];
return [4, this.createToken(accountInst.id)];
case 4:
tokenObj = _a.sent();
_a.label = 5;
case 5: return [4, this.touch('account', { id: accountInst.id })];
case 6:
_a.sent();
return [2, tokenObj.token];
case 7: throw 'Password is invalid';
case 8: return [3, 10];
case 9: throw 'Account does not exist';
case 10: return [2];
}
});
});
};
TempleteAccount.prototype.signOut = function (token) {
return __awaiter(this, void 0, void 0, function () {
var tokenObj;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4, this.validateToken(token)];
case 1:
tokenObj = _a.sent();
if (this.tokenCache.has(token)) {
this.tokenCache.delete(token);
}
return [4, this.db.set('token', null, { account_id: tokenObj.account_id })];
case 2:
_a.sent();
return [2];
}
});
});
};
return TempleteAccount;
}());
exports.TempleteAccount = TempleteAccount;