@boxyhq/saml-jackson 0.4.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/controller/admin.d.ts +2 -2
- package/dist/controller/admin.js +2 -2
- package/dist/controller/api.d.ts +48 -35
- package/dist/controller/api.js +92 -47
- package/dist/controller/health-check.d.ts +11 -0
- package/dist/controller/health-check.js +53 -0
- package/dist/controller/oauth.js +12 -3
- package/dist/controller/signout.d.ts +18 -0
- package/dist/controller/signout.js +231 -0
- package/dist/controller/utils.d.ts +2 -1
- package/dist/controller/utils.js +13 -3
- package/dist/db/db.d.ts +1 -1
- package/dist/db/db.js +7 -3
- package/dist/db/defaultDb.d.ts +2 -0
- package/dist/db/defaultDb.js +12 -0
- package/dist/db/mem.d.ts +1 -1
- package/dist/db/mem.js +43 -11
- package/dist/db/mongo.d.ts +1 -1
- package/dist/db/mongo.js +12 -13
- package/dist/db/redis.d.ts +1 -1
- package/dist/db/redis.js +63 -16
- package/dist/db/sql/sql.d.ts +2 -2
- package/dist/db/sql/sql.js +19 -11
- package/dist/db/store.js +7 -3
- package/dist/db/utils.d.ts +3 -0
- package/dist/db/utils.js +7 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +21 -8
- package/dist/read-config.js +5 -1
- package/dist/saml/saml.d.ts +3 -0
- package/dist/saml/saml.js +38 -5
- package/dist/saml/x509.js +5 -1
- package/dist/typings.d.ts +49 -11
- package/package.json +24 -24
@@ -0,0 +1,231 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
19
|
+
if (mod && mod.__esModule) return mod;
|
20
|
+
var result = {};
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
22
|
+
__setModuleDefault(result, mod);
|
23
|
+
return result;
|
24
|
+
};
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
32
|
+
});
|
33
|
+
};
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
36
|
+
};
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
38
|
+
exports.LogoutController = void 0;
|
39
|
+
const xmldom_1 = require("@xmldom/xmldom");
|
40
|
+
const crypto_1 = __importDefault(require("crypto"));
|
41
|
+
const thumbprint_1 = __importDefault(require("thumbprint"));
|
42
|
+
const util_1 = require("util");
|
43
|
+
const xml_crypto_1 = require("xml-crypto");
|
44
|
+
const xml2js_1 = __importDefault(require("xml2js"));
|
45
|
+
const xmlbuilder_1 = __importDefault(require("xmlbuilder"));
|
46
|
+
const zlib_1 = require("zlib");
|
47
|
+
const dbutils = __importStar(require("../db/utils"));
|
48
|
+
const saml_1 = __importDefault(require("../saml/saml"));
|
49
|
+
const error_1 = require("./error");
|
50
|
+
const redirect = __importStar(require("./oauth/redirect"));
|
51
|
+
const utils_1 = require("./utils");
|
52
|
+
const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
|
53
|
+
const relayStatePrefix = 'boxyhq_jackson_';
|
54
|
+
class LogoutController {
|
55
|
+
constructor({ configStore, sessionStore, opts }) {
|
56
|
+
this.opts = opts;
|
57
|
+
this.configStore = configStore;
|
58
|
+
this.sessionStore = sessionStore;
|
59
|
+
}
|
60
|
+
// Create SLO Request
|
61
|
+
createRequest({ nameId, tenant, product, redirectUrl }) {
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
63
|
+
let samlConfig = null;
|
64
|
+
if (tenant && product) {
|
65
|
+
const samlConfigs = yield this.configStore.getByIndex({
|
66
|
+
name: utils_1.IndexNames.TenantProduct,
|
67
|
+
value: dbutils.keyFromParts(tenant, product),
|
68
|
+
});
|
69
|
+
if (!samlConfigs || samlConfigs.length === 0) {
|
70
|
+
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
71
|
+
}
|
72
|
+
samlConfig = samlConfigs[0];
|
73
|
+
}
|
74
|
+
if (!samlConfig) {
|
75
|
+
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
76
|
+
}
|
77
|
+
const { idpMetadata: { slo, provider }, certs: { privateKey, publicKey }, } = samlConfig;
|
78
|
+
if ('redirectUrl' in slo === false && 'postUrl' in slo === false) {
|
79
|
+
throw new error_1.JacksonError(`${provider} doesn't support SLO or disabled by IdP.`, 400);
|
80
|
+
}
|
81
|
+
const { id, xml } = buildRequestXML(nameId, this.opts.samlAudience, slo.redirectUrl);
|
82
|
+
const sessionId = crypto_1.default.randomBytes(16).toString('hex');
|
83
|
+
let logoutUrl = null;
|
84
|
+
let logoutForm = null;
|
85
|
+
const relayState = relayStatePrefix + sessionId;
|
86
|
+
const signedXML = yield signXML(xml, privateKey, publicKey);
|
87
|
+
yield this.sessionStore.put(sessionId, {
|
88
|
+
id,
|
89
|
+
redirectUrl,
|
90
|
+
});
|
91
|
+
// HTTP-Redirect binding
|
92
|
+
if ('redirectUrl' in slo) {
|
93
|
+
logoutUrl = redirect.success(slo.redirectUrl, {
|
94
|
+
SAMLRequest: Buffer.from(yield deflateRawAsync(signedXML)).toString('base64'),
|
95
|
+
RelayState: relayState,
|
96
|
+
});
|
97
|
+
}
|
98
|
+
// HTTP-POST binding
|
99
|
+
if ('postUrl' in slo) {
|
100
|
+
logoutForm = (0, utils_1.createRequestForm)(relayState, encodeURI(Buffer.from(signedXML).toString('base64')), slo.postUrl);
|
101
|
+
}
|
102
|
+
return { logoutUrl, logoutForm };
|
103
|
+
});
|
104
|
+
}
|
105
|
+
// Handle SLO Response
|
106
|
+
handleResponse({ SAMLResponse, RelayState }) {
|
107
|
+
var _a;
|
108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
109
|
+
const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
|
110
|
+
const sessionId = RelayState.replace(relayStatePrefix, '');
|
111
|
+
const session = yield this.sessionStore.get(sessionId);
|
112
|
+
if (!session) {
|
113
|
+
throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
|
114
|
+
}
|
115
|
+
const parsedResponse = yield parseSAMLResponse(rawResponse);
|
116
|
+
if (parsedResponse.status !== 'urn:oasis:names:tc:SAML:2.0:status:Success') {
|
117
|
+
throw new error_1.JacksonError(`SLO failed with status ${parsedResponse.status}.`, 400);
|
118
|
+
}
|
119
|
+
if (parsedResponse.inResponseTo !== session.id) {
|
120
|
+
throw new error_1.JacksonError(`SLO failed with mismatched request ID.`, 400);
|
121
|
+
}
|
122
|
+
const samlConfigs = yield this.configStore.getByIndex({
|
123
|
+
name: utils_1.IndexNames.EntityID,
|
124
|
+
value: parsedResponse.issuer,
|
125
|
+
});
|
126
|
+
if (!samlConfigs || samlConfigs.length === 0) {
|
127
|
+
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
128
|
+
}
|
129
|
+
const { idpMetadata, defaultRedirectUrl } = samlConfigs[0];
|
130
|
+
if (!(yield hasValidSignature(rawResponse, idpMetadata.thumbprint))) {
|
131
|
+
throw new error_1.JacksonError('Invalid signature.', 403);
|
132
|
+
}
|
133
|
+
try {
|
134
|
+
yield this.sessionStore.delete(sessionId);
|
135
|
+
}
|
136
|
+
catch (_err) {
|
137
|
+
// Ignore
|
138
|
+
}
|
139
|
+
return {
|
140
|
+
redirectUrl: (_a = session.redirectUrl) !== null && _a !== void 0 ? _a : defaultRedirectUrl,
|
141
|
+
};
|
142
|
+
});
|
143
|
+
}
|
144
|
+
}
|
145
|
+
exports.LogoutController = LogoutController;
|
146
|
+
// Create the XML for the SLO Request
|
147
|
+
const buildRequestXML = (nameId, providerName, sloUrl) => {
|
148
|
+
const id = '_' + crypto_1.default.randomBytes(10).toString('hex');
|
149
|
+
const xml = {
|
150
|
+
'samlp:LogoutRequest': {
|
151
|
+
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
152
|
+
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
153
|
+
'@ID': id,
|
154
|
+
'@Version': '2.0',
|
155
|
+
'@IssueInstant': new Date().toISOString(),
|
156
|
+
'@Destination': sloUrl,
|
157
|
+
'saml:Issuer': {
|
158
|
+
'#text': providerName,
|
159
|
+
},
|
160
|
+
'saml:NameID': {
|
161
|
+
'@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
|
162
|
+
'#text': nameId,
|
163
|
+
},
|
164
|
+
},
|
165
|
+
};
|
166
|
+
return {
|
167
|
+
id,
|
168
|
+
xml: xmlbuilder_1.default.create(xml).end({}),
|
169
|
+
};
|
170
|
+
};
|
171
|
+
// Parse SAMLResponse
|
172
|
+
const parseSAMLResponse = (rawResponse) => __awaiter(void 0, void 0, void 0, function* () {
|
173
|
+
return new Promise((resolve, reject) => {
|
174
|
+
xml2js_1.default.parseString(rawResponse, { tagNameProcessors: [xml2js_1.default.processors.stripPrefix] }, (err, { LogoutResponse }) => {
|
175
|
+
if (err) {
|
176
|
+
reject(err);
|
177
|
+
return;
|
178
|
+
}
|
179
|
+
resolve({
|
180
|
+
issuer: LogoutResponse.Issuer[0]._,
|
181
|
+
id: LogoutResponse.$.ID,
|
182
|
+
status: LogoutResponse.Status[0].StatusCode[0].$.Value,
|
183
|
+
destination: LogoutResponse.$.Destination,
|
184
|
+
inResponseTo: LogoutResponse.$.InResponseTo,
|
185
|
+
});
|
186
|
+
});
|
187
|
+
});
|
188
|
+
});
|
189
|
+
// Sign the XML
|
190
|
+
const signXML = (xml, signingKey, publicKey) => __awaiter(void 0, void 0, void 0, function* () {
|
191
|
+
const sig = new xml_crypto_1.SignedXml();
|
192
|
+
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
193
|
+
sig.keyInfoProvider = new saml_1.default.PubKeyInfo(publicKey);
|
194
|
+
sig.signingKey = signingKey;
|
195
|
+
sig.addReference("/*[local-name(.)='LogoutRequest']", ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'], 'http://www.w3.org/2001/04/xmlenc#sha256');
|
196
|
+
sig.computeSignature(xml);
|
197
|
+
return sig.getSignedXml();
|
198
|
+
});
|
199
|
+
// Validate signature
|
200
|
+
const hasValidSignature = (xml, certThumbprint) => __awaiter(void 0, void 0, void 0, function* () {
|
201
|
+
return new Promise((resolve, reject) => {
|
202
|
+
const doc = new xmldom_1.DOMParser().parseFromString(xml);
|
203
|
+
const signed = new xml_crypto_1.SignedXml();
|
204
|
+
let calculatedThumbprint;
|
205
|
+
const signature = (0, xml_crypto_1.xpath)(doc, "/*/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0] ||
|
206
|
+
(0, xml_crypto_1.xpath)(doc, "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0] ||
|
207
|
+
(0, xml_crypto_1.xpath)(doc, "/*/*/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];
|
208
|
+
signed.keyInfoProvider = {
|
209
|
+
getKey: function getKey(keyInfo) {
|
210
|
+
if (certThumbprint) {
|
211
|
+
const embeddedSignature = keyInfo[0].getElementsByTagNameNS('http://www.w3.org/2000/09/xmldsig#', 'X509Certificate');
|
212
|
+
if (embeddedSignature.length > 0) {
|
213
|
+
const base64cer = embeddedSignature[0].firstChild.toString();
|
214
|
+
calculatedThumbprint = thumbprint_1.default.calculate(base64cer);
|
215
|
+
return saml_1.default.certToPEM(base64cer);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
},
|
219
|
+
getKeyInfo: function getKeyInfo() {
|
220
|
+
return '<X509Data></X509Data>';
|
221
|
+
},
|
222
|
+
};
|
223
|
+
signed.loadSignature(signature.toString());
|
224
|
+
try {
|
225
|
+
return resolve(signed.checkSignature(xml) && calculatedThumbprint.toUpperCase() === certThumbprint.toUpperCase());
|
226
|
+
}
|
227
|
+
catch (err) {
|
228
|
+
return reject(err);
|
229
|
+
}
|
230
|
+
});
|
231
|
+
});
|
@@ -2,4 +2,5 @@ export declare enum IndexNames {
|
|
2
2
|
EntityID = "entityID",
|
3
3
|
TenantProduct = "tenantProduct"
|
4
4
|
}
|
5
|
-
export declare const
|
5
|
+
export declare const createRequestForm: (relayState: string, samlReqEnc: string, postUrl: string) => string;
|
6
|
+
export declare const validateAbsoluteUrl: (url: any, message: any) => void;
|
package/dist/controller/utils.js
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
3
|
+
exports.validateAbsoluteUrl = exports.createRequestForm = exports.IndexNames = void 0;
|
4
|
+
const error_1 = require("./error");
|
4
5
|
var IndexNames;
|
5
6
|
(function (IndexNames) {
|
6
7
|
IndexNames["EntityID"] = "entityID";
|
7
8
|
IndexNames["TenantProduct"] = "tenantProduct";
|
8
9
|
})(IndexNames = exports.IndexNames || (exports.IndexNames = {}));
|
9
|
-
const
|
10
|
+
const createRequestForm = (relayState, samlReqEnc, postUrl) => {
|
10
11
|
const formElements = [
|
11
12
|
'<!DOCTYPE html>',
|
12
13
|
'<html>',
|
@@ -29,4 +30,13 @@ const createAuthorizeForm = (relayState, samlReqEnc, postUrl) => {
|
|
29
30
|
];
|
30
31
|
return formElements.join('');
|
31
32
|
};
|
32
|
-
exports.
|
33
|
+
exports.createRequestForm = createRequestForm;
|
34
|
+
const validateAbsoluteUrl = (url, message) => {
|
35
|
+
try {
|
36
|
+
new URL(url);
|
37
|
+
}
|
38
|
+
catch (err) {
|
39
|
+
throw new error_1.JacksonError(message ? message : 'Invalid url', 400);
|
40
|
+
}
|
41
|
+
};
|
42
|
+
exports.validateAbsoluteUrl = validateAbsoluteUrl;
|
package/dist/db/db.d.ts
CHANGED
@@ -4,7 +4,7 @@ declare class DB implements DatabaseDriver {
|
|
4
4
|
private encryptionKey;
|
5
5
|
constructor(db: DatabaseDriver, encryptionKey: EncryptionKey);
|
6
6
|
get(namespace: string, key: string): Promise<unknown>;
|
7
|
-
getAll(namespace: any): Promise<unknown[]>;
|
7
|
+
getAll(namespace: any, pageOffset: any, pageLimit: any): Promise<unknown[]>;
|
8
8
|
getByIndex(namespace: string, idx: Index): Promise<unknown[]>;
|
9
9
|
put(namespace: string, key: string, val: unknown, ttl?: number, ...indexes: Index[]): Promise<unknown>;
|
10
10
|
delete(namespace: string, key: string): Promise<unknown>;
|
package/dist/db/db.js
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
"use strict";
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
3
|
if (k2 === undefined) k2 = k;
|
4
|
-
Object.
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
5
9
|
}) : (function(o, m, k, k2) {
|
6
10
|
if (k2 === undefined) k2 = k;
|
7
11
|
o[k2] = m[k];
|
@@ -57,9 +61,9 @@ class DB {
|
|
57
61
|
return decrypt(res, this.encryptionKey);
|
58
62
|
});
|
59
63
|
}
|
60
|
-
getAll(namespace) {
|
64
|
+
getAll(namespace, pageOffset, pageLimit) {
|
61
65
|
return __awaiter(this, void 0, void 0, function* () {
|
62
|
-
const res = (yield this.db.getAll(namespace));
|
66
|
+
const res = (yield this.db.getAll(namespace, pageOffset, pageLimit));
|
63
67
|
const encryptionKey = this.encryptionKey;
|
64
68
|
return res.map((r) => {
|
65
69
|
return decrypt(r, encryptionKey);
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
function defaultDb(opts) {
|
4
|
+
opts.db = opts.db || {};
|
5
|
+
opts.db.engine = opts.db.engine || 'sql';
|
6
|
+
opts.db.url = opts.db.url || 'postgresql://postgres:postgres@localhost:5432/postgres';
|
7
|
+
opts.db.type = opts.db.type || 'postgres'; // Only needed if DB_ENGINE is sql.
|
8
|
+
opts.db.ttl = (opts.db.ttl || 300) * 1; // TTL for the code, session and token stores (in seconds)
|
9
|
+
opts.db.cleanupLimit = (opts.db.cleanupLimit || 1000) * 1; // Limit cleanup of TTL entries to this many items at a time
|
10
|
+
return opts;
|
11
|
+
}
|
12
|
+
exports.default = defaultDb;
|
package/dist/db/mem.d.ts
CHANGED
@@ -10,7 +10,7 @@ declare class Mem implements DatabaseDriver {
|
|
10
10
|
constructor(options: DatabaseOption);
|
11
11
|
init(): Promise<Mem>;
|
12
12
|
get(namespace: string, key: string): Promise<any>;
|
13
|
-
getAll(namespace: string): Promise<unknown[]>;
|
13
|
+
getAll(namespace: string, pageOffset: number, pageLimit: number): Promise<unknown[]>;
|
14
14
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
15
15
|
put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<any>;
|
16
16
|
delete(namespace: string, key: string): Promise<any>;
|
package/dist/db/mem.js
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
// This is an in-memory implementation to be used with testing and prototyping only
|
3
3
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
4
4
|
if (k2 === undefined) k2 = k;
|
5
|
-
Object.
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
8
|
+
}
|
9
|
+
Object.defineProperty(o, k2, desc);
|
6
10
|
}) : (function(o, m, k, k2) {
|
7
11
|
if (k2 === undefined) k2 = k;
|
8
12
|
o[k2] = m[k];
|
@@ -66,19 +70,28 @@ class Mem {
|
|
66
70
|
return null;
|
67
71
|
});
|
68
72
|
}
|
69
|
-
getAll(namespace) {
|
73
|
+
getAll(namespace, pageOffset, pageLimit) {
|
70
74
|
return __awaiter(this, void 0, void 0, function* () {
|
75
|
+
const offsetAndLimitValueCheck = !dbutils.isNumeric(pageOffset) && !dbutils.isNumeric(pageLimit);
|
76
|
+
let take = Number(offsetAndLimitValueCheck ? this.options.pageLimit : pageLimit);
|
77
|
+
const skip = Number(offsetAndLimitValueCheck ? 0 : pageOffset);
|
78
|
+
let count = 0;
|
79
|
+
take += skip;
|
71
80
|
const returnValue = [];
|
72
81
|
if (namespace) {
|
73
|
-
|
74
|
-
|
75
|
-
|
82
|
+
const val = Array.from(this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)]);
|
83
|
+
const iterator = val.reverse().values();
|
84
|
+
for (const value of iterator) {
|
85
|
+
if (count >= take) {
|
86
|
+
break;
|
87
|
+
}
|
88
|
+
if (count >= skip) {
|
89
|
+
returnValue.push(this.store[dbutils.keyFromParts(namespace, value)]);
|
76
90
|
}
|
91
|
+
count++;
|
77
92
|
}
|
78
93
|
}
|
79
|
-
|
80
|
-
return returnValue;
|
81
|
-
return [];
|
94
|
+
return returnValue || [];
|
82
95
|
});
|
83
96
|
}
|
84
97
|
getByIndex(namespace, idx) {
|
@@ -95,9 +108,6 @@ class Mem {
|
|
95
108
|
return __awaiter(this, void 0, void 0, function* () {
|
96
109
|
const k = dbutils.key(namespace, key);
|
97
110
|
this.store[k] = val;
|
98
|
-
if (!Date.parse(this.store['createdAt']))
|
99
|
-
this.store['createdAt'] = new Date().toISOString();
|
100
|
-
this.store['modifiedAt'] = new Date().toISOString();
|
101
111
|
// console.log(this.store)
|
102
112
|
if (ttl) {
|
103
113
|
this.ttlStore[k] = {
|
@@ -123,6 +133,26 @@ class Mem {
|
|
123
133
|
}
|
124
134
|
cleanup.add(idxKey);
|
125
135
|
}
|
136
|
+
let createdAtSet = this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)];
|
137
|
+
if (!createdAtSet) {
|
138
|
+
createdAtSet = new Set();
|
139
|
+
this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)] = createdAtSet;
|
140
|
+
this.store['createdAt'] = new Date().toISOString();
|
141
|
+
createdAtSet.add(key);
|
142
|
+
}
|
143
|
+
else {
|
144
|
+
if (!this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)].has(key)) {
|
145
|
+
createdAtSet.add(key);
|
146
|
+
this.store['createdAt'] = new Date().toISOString();
|
147
|
+
}
|
148
|
+
}
|
149
|
+
let modifiedAtSet = this.indexes[dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace)];
|
150
|
+
if (!modifiedAtSet) {
|
151
|
+
modifiedAtSet = new Set();
|
152
|
+
this.indexes[dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace)] = modifiedAtSet;
|
153
|
+
}
|
154
|
+
modifiedAtSet.add(key);
|
155
|
+
this.store['modifiedAt'] = new Date().toISOString();
|
126
156
|
});
|
127
157
|
}
|
128
158
|
delete(namespace, key) {
|
@@ -135,6 +165,8 @@ class Mem {
|
|
135
165
|
for (const dbKey of dbKeys || []) {
|
136
166
|
this.indexes[dbKey] && this.indexes[dbKey].delete(key);
|
137
167
|
}
|
168
|
+
this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)].delete(key);
|
169
|
+
this.indexes[dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace)].delete(key);
|
138
170
|
delete this.cleanup[idxKey];
|
139
171
|
delete this.ttlStore[k];
|
140
172
|
});
|
package/dist/db/mongo.d.ts
CHANGED
@@ -7,7 +7,7 @@ declare class Mongo implements DatabaseDriver {
|
|
7
7
|
constructor(options: DatabaseOption);
|
8
8
|
init(): Promise<Mongo>;
|
9
9
|
get(namespace: string, key: string): Promise<any>;
|
10
|
-
getAll(namespace: string): Promise<unknown[]>;
|
10
|
+
getAll(namespace: string, offset: number, limit: number): Promise<unknown[]>;
|
11
11
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
12
12
|
put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
|
13
13
|
delete(namespace: string, key: string): Promise<any>;
|
package/dist/db/mongo.js
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
"use strict";
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
3
|
if (k2 === undefined) k2 = k;
|
4
|
-
Object.
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
5
9
|
}) : (function(o, m, k, k2) {
|
6
10
|
if (k2 === undefined) k2 = k;
|
7
11
|
o[k2] = m[k];
|
@@ -36,16 +40,9 @@ class Mongo {
|
|
36
40
|
}
|
37
41
|
init() {
|
38
42
|
return __awaiter(this, void 0, void 0, function* () {
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
}
|
43
|
-
this.client = new mongodb_1.MongoClient(this.options.url);
|
44
|
-
yield this.client.connect();
|
45
|
-
}
|
46
|
-
catch (err) {
|
47
|
-
console.error(`error connecting to ${this.options.type} db: ${err}`);
|
48
|
-
}
|
43
|
+
const dbUrl = this.options.url;
|
44
|
+
this.client = new mongodb_1.MongoClient(dbUrl);
|
45
|
+
yield this.client.connect();
|
49
46
|
this.db = this.client.db();
|
50
47
|
this.collection = this.db.collection('jacksonStore');
|
51
48
|
yield this.collection.createIndex({ indexes: 1 });
|
@@ -64,10 +61,12 @@ class Mongo {
|
|
64
61
|
return null;
|
65
62
|
});
|
66
63
|
}
|
67
|
-
getAll(namespace) {
|
64
|
+
getAll(namespace, offset, limit) {
|
68
65
|
return __awaiter(this, void 0, void 0, function* () {
|
69
66
|
const _namespaceMatch = new RegExp(`^${namespace}:.*`);
|
70
|
-
const docs = yield this.collection
|
67
|
+
const docs = yield this.collection
|
68
|
+
.find({ _id: _namespaceMatch }, { sort: { createdAt: -1 }, skip: offset, limit: limit })
|
69
|
+
.toArray();
|
71
70
|
if (docs)
|
72
71
|
return docs.map(({ value }) => value);
|
73
72
|
return [];
|
package/dist/db/redis.d.ts
CHANGED
@@ -5,7 +5,7 @@ declare class Redis implements DatabaseDriver {
|
|
5
5
|
constructor(options: DatabaseOption);
|
6
6
|
init(): Promise<Redis>;
|
7
7
|
get(namespace: string, key: string): Promise<any>;
|
8
|
-
getAll(namespace: string): Promise<unknown[]>;
|
8
|
+
getAll(namespace: string, pageOffset: number, pageLimit: number): Promise<unknown[]>;
|
9
9
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
10
10
|
put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
|
11
11
|
delete(namespace: string, key: string): Promise<any>;
|
package/dist/db/redis.js
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
"use strict";
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
3
|
if (k2 === undefined) k2 = k;
|
4
|
-
Object.
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
5
9
|
}) : (function(o, m, k, k2) {
|
6
10
|
if (k2 === undefined) k2 = k;
|
7
11
|
o[k2] = m[k];
|
@@ -27,6 +31,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
27
31
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
28
32
|
});
|
29
33
|
};
|
34
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
35
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
36
|
+
var m = o[Symbol.asyncIterator], i;
|
37
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
38
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
39
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
40
|
+
};
|
30
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
31
42
|
const redis = __importStar(require("redis"));
|
32
43
|
const dbutils = __importStar(require("./utils"));
|
@@ -57,29 +68,51 @@ class Redis {
|
|
57
68
|
return null;
|
58
69
|
});
|
59
70
|
}
|
60
|
-
getAll(namespace) {
|
71
|
+
getAll(namespace, pageOffset, pageLimit) {
|
72
|
+
var e_1, _a;
|
61
73
|
return __awaiter(this, void 0, void 0, function* () {
|
62
|
-
const
|
74
|
+
const offsetAndLimitValueCheck = !dbutils.isNumeric(pageOffset) && !dbutils.isNumeric(pageLimit);
|
75
|
+
let take = Number(offsetAndLimitValueCheck ? this.options.pageLimit : pageLimit);
|
76
|
+
const skip = Number(offsetAndLimitValueCheck ? 0 : pageOffset);
|
63
77
|
const returnValue = [];
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
78
|
+
const keyArray = [];
|
79
|
+
let count = 0;
|
80
|
+
take += skip;
|
81
|
+
try {
|
82
|
+
for (var _b = __asyncValues(this.client.zScanIterator(dbutils.keyFromParts(dbutils.createdAtPrefix, namespace), Math.min(take, 1000))), _c; _c = yield _b.next(), !_c.done;) {
|
83
|
+
const { score, value } = _c.value;
|
84
|
+
if (count >= take) {
|
85
|
+
break;
|
69
86
|
}
|
87
|
+
if (count >= skip) {
|
88
|
+
keyArray.push(dbutils.keyFromParts(namespace, value));
|
89
|
+
}
|
90
|
+
count++;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
94
|
+
finally {
|
95
|
+
try {
|
96
|
+
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
70
97
|
}
|
71
|
-
|
72
|
-
|
98
|
+
finally { if (e_1) throw e_1.error; }
|
99
|
+
}
|
100
|
+
if (keyArray.length > 0) {
|
101
|
+
const value = yield this.client.MGET(keyArray);
|
102
|
+
for (let i = 0; i < value.length; i++) {
|
103
|
+
const valueObject = JSON.parse(value[i].toString());
|
104
|
+
if (valueObject !== null && valueObject !== '') {
|
105
|
+
returnValue.push(valueObject);
|
106
|
+
}
|
73
107
|
}
|
74
108
|
}
|
75
|
-
|
76
|
-
return returnValue;
|
77
|
-
return [];
|
109
|
+
return returnValue || [];
|
78
110
|
});
|
79
111
|
}
|
80
112
|
getByIndex(namespace, idx) {
|
81
113
|
return __awaiter(this, void 0, void 0, function* () {
|
82
|
-
const
|
114
|
+
const idxKey = dbutils.keyForIndex(namespace, idx);
|
115
|
+
const dbKeys = yield this.client.sMembers(dbutils.keyFromParts(dbutils.indexPrefix, idxKey));
|
83
116
|
const ret = [];
|
84
117
|
for (const dbKey of dbKeys || []) {
|
85
118
|
ret.push(yield this.get(namespace, dbKey));
|
@@ -98,9 +131,21 @@ class Redis {
|
|
98
131
|
// no ttl support for secondary indexes
|
99
132
|
for (const idx of indexes || []) {
|
100
133
|
const idxKey = dbutils.keyForIndex(namespace, idx);
|
101
|
-
tx = tx.sAdd(idxKey, key);
|
134
|
+
tx = tx.sAdd(dbutils.keyFromParts(dbutils.indexPrefix, idxKey), key);
|
102
135
|
tx = tx.sAdd(dbutils.keyFromParts(dbutils.indexPrefix, k), idxKey);
|
103
136
|
}
|
137
|
+
const timestamp = Number(Date.now());
|
138
|
+
//Converting Timestamp in negative so that when we get the value, it will be found in reverse order (descending order).
|
139
|
+
const negativeTimestamp = -Math.abs(timestamp);
|
140
|
+
const value = yield this.client.get(k);
|
141
|
+
if (!value) {
|
142
|
+
tx = tx.zAdd(dbutils.keyFromParts(dbutils.createdAtPrefix, namespace), [
|
143
|
+
{ score: negativeTimestamp, value: key },
|
144
|
+
]);
|
145
|
+
}
|
146
|
+
tx = tx.zAdd(dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace), [
|
147
|
+
{ score: negativeTimestamp, value: key },
|
148
|
+
]);
|
104
149
|
yield tx.exec();
|
105
150
|
});
|
106
151
|
}
|
@@ -113,8 +158,10 @@ class Redis {
|
|
113
158
|
// delete secondary indexes and then the mapping of the seconary indexes
|
114
159
|
const dbKeys = yield this.client.sMembers(idxKey);
|
115
160
|
for (const dbKey of dbKeys || []) {
|
116
|
-
tx.sRem(dbKey, key);
|
161
|
+
tx.sRem(dbutils.keyFromParts(dbutils.indexPrefix, dbKey), key);
|
117
162
|
}
|
163
|
+
tx.ZREM(dbutils.keyFromParts(dbutils.createdAtPrefix, namespace), key);
|
164
|
+
tx.ZREM(dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace), key);
|
118
165
|
tx.del(idxKey);
|
119
166
|
return yield tx.exec();
|
120
167
|
});
|
package/dist/db/sql/sql.d.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { DatabaseDriver, DatabaseOption, Index, Encrypted } from '../../typings';
|
2
2
|
declare class Sql implements DatabaseDriver {
|
3
3
|
private options;
|
4
|
-
private
|
4
|
+
private dataSource;
|
5
5
|
private storeRepository;
|
6
6
|
private indexRepository;
|
7
7
|
private ttlRepository;
|
@@ -10,7 +10,7 @@ declare class Sql implements DatabaseDriver {
|
|
10
10
|
constructor(options: DatabaseOption);
|
11
11
|
init(): Promise<Sql>;
|
12
12
|
get(namespace: string, key: string): Promise<any>;
|
13
|
-
getAll(namespace: string): Promise<unknown[]>;
|
13
|
+
getAll(namespace: string, pageOffset: number, pageLimit: number): Promise<unknown[]>;
|
14
14
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
15
15
|
put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
|
16
16
|
delete(namespace: string, key: string): Promise<any>;
|