@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.
@@ -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 createAuthorizeForm: (relayState: string, samlReqEnc: string, postUrl: string) => string;
5
+ export declare const createRequestForm: (relayState: string, samlReqEnc: string, postUrl: string) => string;
6
+ export declare const validateAbsoluteUrl: (url: any, message: any) => void;
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAuthorizeForm = exports.IndexNames = void 0;
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 createAuthorizeForm = (relayState, samlReqEnc, postUrl) => {
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.createAuthorizeForm = createAuthorizeForm;
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.defineProperty(o, k2, { enumerable: true, get: function() { return m[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);
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,2 @@
1
+ import { JacksonOption } from '../typings';
2
+ export default function defaultDb(opts: JacksonOption): JacksonOption;
@@ -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.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
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
- for (const key in this.store) {
74
- if (key.startsWith(namespace)) {
75
- returnValue.push(this.store[key]);
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
- if (returnValue)
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
  });
@@ -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.defineProperty(o, k2, { enumerable: true, get: function() { return m[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);
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
- try {
40
- if (!this.options.url) {
41
- throw Error('Please specify a db url');
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.find({ _id: _namespaceMatch }).toArray();
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 [];
@@ -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.defineProperty(o, k2, { enumerable: true, get: function() { return m[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);
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 keys = yield this.client.sendCommand(['keys', namespace + ':*']);
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
- for (let i = 0; i < keys.length; i++) {
65
- try {
66
- if (this.client.get(keys[i])) {
67
- const value = yield this.client.get(keys[i]);
68
- returnValue.push(JSON.parse(value));
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
- catch (error) {
72
- console.error(error);
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
- if (returnValue)
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 dbKeys = yield this.client.sMembers(dbutils.keyForIndex(namespace, idx));
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
  });
@@ -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 connection;
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>;