@boxyhq/saml-jackson 0.5.1 → 1.0.2

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.
@@ -41,14 +41,26 @@ const util_1 = require("util");
41
41
  const zlib_1 = require("zlib");
42
42
  const dbutils = __importStar(require("../db/utils"));
43
43
  const metrics = __importStar(require("../opentelemetry/metrics"));
44
- const saml_1 = __importDefault(require("../saml/saml"));
44
+ const saml20_1 = __importDefault(require("@boxyhq/saml20"));
45
+ const claims_1 = __importDefault(require("../saml/claims"));
45
46
  const error_1 = require("./error");
46
47
  const allowed = __importStar(require("./oauth/allowed"));
47
48
  const codeVerifier = __importStar(require("./oauth/code-verifier"));
48
49
  const redirect = __importStar(require("./oauth/redirect"));
49
50
  const utils_1 = require("./utils");
50
51
  const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
51
- const relayStatePrefix = 'boxyhq_jackson_';
52
+ const validateResponse = (rawResponse, validateOpts) => __awaiter(void 0, void 0, void 0, function* () {
53
+ const profile = yield saml20_1.default.validateAsync(rawResponse, validateOpts);
54
+ if (profile && profile.claims) {
55
+ // we map claims to our attributes id, email, firstName, lastName where possible. We also map original claims to raw
56
+ profile.claims = claims_1.default.map(profile.claims);
57
+ // some providers don't return the id in the assertion, we set it to a sha256 hash of the email
58
+ if (!profile.claims.id && profile.claims.email) {
59
+ profile.claims.id = crypto_1.default.createHash('sha256').update(profile.claims.email).digest('hex');
60
+ }
61
+ }
62
+ return profile;
63
+ });
52
64
  function getEncodedClientId(client_id) {
53
65
  try {
54
66
  const sp = new URLSearchParams(client_id);
@@ -74,11 +86,53 @@ class OAuthController {
74
86
  this.tokenStore = tokenStore;
75
87
  this.opts = opts;
76
88
  }
89
+ resolveMultipleConfigMatches(samlConfigs, idp_hint, originalParams, isIdpFlow = false) {
90
+ if (samlConfigs.length > 1) {
91
+ if (idp_hint) {
92
+ return { resolvedSamlConfig: samlConfigs.find(({ clientID }) => clientID === idp_hint) };
93
+ }
94
+ else if (this.opts.idpDiscoveryPath) {
95
+ if (!isIdpFlow) {
96
+ // redirect to IdP selection page
97
+ const idpList = samlConfigs.map(({ idpMetadata: { provider }, clientID }) => JSON.stringify({
98
+ provider,
99
+ clientID,
100
+ }));
101
+ return {
102
+ redirect_url: redirect.success(this.opts.externalUrl + this.opts.idpDiscoveryPath, Object.assign(Object.assign({}, originalParams), { idp: idpList })),
103
+ };
104
+ }
105
+ else {
106
+ const appList = samlConfigs.map(({ product, name, description, clientID }) => ({
107
+ product,
108
+ name,
109
+ description,
110
+ clientID,
111
+ }));
112
+ return {
113
+ app_select_form: saml20_1.default.createPostForm(this.opts.idpDiscoveryPath, [
114
+ {
115
+ name: 'SAMLResponse',
116
+ value: originalParams.SAMLResponse,
117
+ },
118
+ {
119
+ name: 'app',
120
+ value: encodeURIComponent(JSON.stringify(appList)),
121
+ },
122
+ ]),
123
+ };
124
+ }
125
+ }
126
+ }
127
+ return {};
128
+ }
77
129
  authorize(body) {
78
130
  return __awaiter(this, void 0, void 0, function* () {
79
131
  const { response_type = 'code', client_id, redirect_uri, state, tenant, product, code_challenge, code_challenge_method = '',
80
132
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
81
- provider = 'saml', } = body;
133
+ provider = 'saml', idp_hint, } = body;
134
+ let requestedTenant = tenant;
135
+ let requestedProduct = product;
82
136
  metrics.increment('oauthAuthorize');
83
137
  if (!redirect_uri) {
84
138
  throw new error_1.JacksonError('Please specify a redirect URL.', 400);
@@ -95,22 +149,58 @@ class OAuthController {
95
149
  if (!samlConfigs || samlConfigs.length === 0) {
96
150
  throw new error_1.JacksonError('SAML configuration not found.', 403);
97
151
  }
98
- // TODO: Support multiple matches
99
152
  samlConfig = samlConfigs[0];
153
+ // Support multiple matches
154
+ const { resolvedSamlConfig, redirect_url } = this.resolveMultipleConfigMatches(samlConfigs, idp_hint, {
155
+ response_type,
156
+ client_id,
157
+ redirect_uri,
158
+ state,
159
+ tenant,
160
+ product,
161
+ code_challenge,
162
+ code_challenge_method,
163
+ provider,
164
+ });
165
+ if (redirect_url) {
166
+ return { redirect_url };
167
+ }
168
+ if (resolvedSamlConfig) {
169
+ samlConfig = resolvedSamlConfig;
170
+ }
100
171
  }
101
172
  else if (client_id && client_id !== '' && client_id !== 'undefined' && client_id !== 'null') {
102
173
  // if tenant and product are encoded in the client_id then we parse it and check for the relevant config(s)
103
174
  const sp = getEncodedClientId(client_id);
104
- if (sp === null || sp === void 0 ? void 0 : sp.tenant) {
175
+ if (sp && sp.tenant && sp.product) {
176
+ requestedTenant = sp.tenant;
177
+ requestedProduct = sp.product;
105
178
  const samlConfigs = yield this.configStore.getByIndex({
106
179
  name: utils_1.IndexNames.TenantProduct,
107
- value: dbutils.keyFromParts(sp.tenant, sp.product || ''),
180
+ value: dbutils.keyFromParts(sp.tenant, sp.product),
108
181
  });
109
182
  if (!samlConfigs || samlConfigs.length === 0) {
110
183
  throw new error_1.JacksonError('SAML configuration not found.', 403);
111
184
  }
112
- // TODO: Support multiple matches
113
185
  samlConfig = samlConfigs[0];
186
+ // Support multiple matches
187
+ const { resolvedSamlConfig, redirect_url } = this.resolveMultipleConfigMatches(samlConfigs, idp_hint, {
188
+ response_type,
189
+ client_id,
190
+ redirect_uri,
191
+ state,
192
+ tenant,
193
+ product,
194
+ code_challenge,
195
+ code_challenge_method,
196
+ provider,
197
+ });
198
+ if (redirect_url) {
199
+ return { redirect_url };
200
+ }
201
+ if (resolvedSamlConfig) {
202
+ samlConfig = resolvedSamlConfig;
203
+ }
114
204
  }
115
205
  else {
116
206
  samlConfig = yield this.configStore.get(client_id);
@@ -137,7 +227,7 @@ class OAuthController {
137
227
  ssoUrl = sso.postUrl;
138
228
  post = true;
139
229
  }
140
- const samlReq = saml_1.default.request({
230
+ const samlReq = saml20_1.default.request({
141
231
  ssoUrl,
142
232
  entityID: this.opts.samlAudience,
143
233
  callbackUrl: this.opts.externalUrl + this.opts.samlPath,
@@ -145,12 +235,16 @@ class OAuthController {
145
235
  publicKey: samlConfig.certs.publicKey,
146
236
  });
147
237
  const sessionId = crypto_1.default.randomBytes(16).toString('hex');
148
- const requestedParams = {
149
- tenant,
150
- product,
151
- client_id,
152
- state,
153
- };
238
+ const requested = { client_id, state };
239
+ if (requestedTenant) {
240
+ requested.tenant = requestedTenant;
241
+ }
242
+ if (requestedProduct) {
243
+ requested.product = requestedProduct;
244
+ }
245
+ if (idp_hint) {
246
+ requested.idp_hint = idp_hint;
247
+ }
154
248
  yield this.sessionStore.put(sessionId, {
155
249
  id: samlReq.id,
156
250
  redirect_uri,
@@ -158,9 +252,9 @@ class OAuthController {
158
252
  state,
159
253
  code_challenge,
160
254
  code_challenge_method,
161
- requested: requestedParams,
255
+ requested,
162
256
  });
163
- const relayState = relayStatePrefix + sessionId;
257
+ const relayState = utils_1.relayStatePrefix + sessionId;
164
258
  let redirectUrl;
165
259
  let authorizeForm;
166
260
  if (!post) {
@@ -172,7 +266,16 @@ class OAuthController {
172
266
  }
173
267
  else {
174
268
  // HTTP POST binding
175
- authorizeForm = (0, utils_1.createAuthorizeForm)(relayState, encodeURI(Buffer.from(samlReq.request).toString('base64')), ssoUrl);
269
+ authorizeForm = saml20_1.default.createPostForm(ssoUrl, [
270
+ {
271
+ name: 'RelayState',
272
+ value: relayState,
273
+ },
274
+ {
275
+ name: 'SAMLRequest',
276
+ value: Buffer.from(samlReq.request).toString('base64'),
277
+ },
278
+ ]);
176
279
  }
177
280
  return {
178
281
  redirect_url: redirectUrl,
@@ -182,18 +285,16 @@ class OAuthController {
182
285
  }
183
286
  samlResponse(body) {
184
287
  return __awaiter(this, void 0, void 0, function* () {
185
- const { SAMLResponse } = body;
288
+ const { SAMLResponse, idp_hint } = body;
186
289
  let RelayState = body.RelayState || ''; // RelayState will contain the sessionId from earlier quasi-oauth flow
187
- if (!this.opts.idpEnabled && !RelayState.startsWith(relayStatePrefix)) {
290
+ const isIdPFlow = !RelayState.startsWith(utils_1.relayStatePrefix);
291
+ if (!this.opts.idpEnabled && isIdPFlow) {
188
292
  // IDP is disabled so block the request
189
293
  throw new error_1.JacksonError('IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.', 403);
190
294
  }
191
- if (!RelayState.startsWith(relayStatePrefix)) {
192
- RelayState = '';
193
- }
194
- RelayState = RelayState.replace(relayStatePrefix, '');
295
+ RelayState = RelayState.replace(utils_1.relayStatePrefix, '');
195
296
  const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
196
- const parsedResp = yield saml_1.default.parseAsync(rawResponse);
297
+ const parsedResp = yield saml20_1.default.parseAsync(rawResponse);
197
298
  const samlConfigs = yield this.configStore.getByIndex({
198
299
  name: utils_1.IndexNames.EntityID,
199
300
  value: parsedResp === null || parsedResp === void 0 ? void 0 : parsedResp.issuer,
@@ -201,8 +302,17 @@ class OAuthController {
201
302
  if (!samlConfigs || samlConfigs.length === 0) {
202
303
  throw new error_1.JacksonError('SAML configuration not found.', 403);
203
304
  }
204
- // TODO: Support multiple matches
205
- const samlConfig = samlConfigs[0];
305
+ let samlConfig = samlConfigs[0];
306
+ if (isIdPFlow) {
307
+ RelayState = '';
308
+ const { resolvedSamlConfig, app_select_form } = this.resolveMultipleConfigMatches(samlConfigs, idp_hint, { SAMLResponse }, true);
309
+ if (app_select_form) {
310
+ return { app_select_form };
311
+ }
312
+ if (resolvedSamlConfig) {
313
+ samlConfig = resolvedSamlConfig;
314
+ }
315
+ }
206
316
  let session;
207
317
  if (RelayState !== '') {
208
318
  session = yield this.sessionStore.get(RelayState);
@@ -210,6 +320,20 @@ class OAuthController {
210
320
  throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
211
321
  }
212
322
  }
323
+ if (!isIdPFlow) {
324
+ // Resolve if there are multiple matches for SP login. TODO: Support multiple matches for IdP login
325
+ samlConfig =
326
+ samlConfigs.length === 1
327
+ ? samlConfigs[0]
328
+ : samlConfigs.filter((c) => {
329
+ var _a, _b, _c;
330
+ return (c.clientID === ((_a = session === null || session === void 0 ? void 0 : session.requested) === null || _a === void 0 ? void 0 : _a.client_id) ||
331
+ (c.tenant === ((_b = session === null || session === void 0 ? void 0 : session.requested) === null || _b === void 0 ? void 0 : _b.tenant) && c.product === ((_c = session === null || session === void 0 ? void 0 : session.requested) === null || _c === void 0 ? void 0 : _c.product)));
332
+ })[0];
333
+ }
334
+ if (!samlConfig) {
335
+ throw new error_1.JacksonError('SAML configuration not found.', 403);
336
+ }
213
337
  const validateOpts = {
214
338
  thumbprint: samlConfig.idpMetadata.thumbprint,
215
339
  audience: this.opts.samlAudience,
@@ -217,7 +341,7 @@ class OAuthController {
217
341
  if (session && session.id) {
218
342
  validateOpts.inResponseTo = session.id;
219
343
  }
220
- const profile = yield saml_1.default.validateAsync(rawResponse, validateOpts);
344
+ const profile = yield validateResponse(rawResponse, validateOpts);
221
345
  // store details against a code
222
346
  const code = crypto_1.default.randomBytes(20).toString('hex');
223
347
  const codeVal = {
@@ -346,6 +470,11 @@ class OAuthController {
346
470
  }
347
471
  }
348
472
  }
473
+ else {
474
+ if (client_secret !== this.opts.clientSecretVerifier && client_secret !== codeVal.clientSecret) {
475
+ throw new error_1.JacksonError('Invalid client_secret', 401);
476
+ }
477
+ }
349
478
  }
350
479
  else if (codeVal && codeVal.session) {
351
480
  throw new error_1.JacksonError('Please specify client_secret or code_verifier', 401);
@@ -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 relayStatePrefix = "boxyhq_jackson_";
6
+ export declare const validateAbsoluteUrl: (url: any, message: any) => void;
@@ -1,32 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAuthorizeForm = exports.IndexNames = void 0;
3
+ exports.validateAbsoluteUrl = exports.relayStatePrefix = 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 formElements = [
11
- '<!DOCTYPE html>',
12
- '<html>',
13
- '<head>',
14
- '<meta charset="utf-8">',
15
- '<meta http-equiv="x-ua-compatible" content="ie=edge">',
16
- '</head>',
17
- '<body onload="document.forms[0].submit()">',
18
- '<noscript>',
19
- '<p>Note: Since your browser does not support JavaScript, you must press the Continue button once to proceed.</p>',
20
- '</noscript>',
21
- '<form method="post" action="' + encodeURI(postUrl) + '">',
22
- '<input type="hidden" name="RelayState" value="' + relayState + '"/>',
23
- '<input type="hidden" name="SAMLRequest" value="' + samlReqEnc + '"/>',
24
- '<input type="submit" value="Continue" />',
25
- '</form>',
26
- '<script>document.forms[0].style.display="none";</script>',
27
- '</body>',
28
- '</html>',
29
- ];
30
- return formElements.join('');
10
+ exports.relayStatePrefix = 'boxyhq_jackson_';
11
+ const validateAbsoluteUrl = (url, message) => {
12
+ try {
13
+ new URL(url);
14
+ }
15
+ catch (err) {
16
+ throw new error_1.JacksonError(message ? message : 'Invalid url', 400);
17
+ }
31
18
  };
32
- exports.createAuthorizeForm = createAuthorizeForm;
19
+ exports.validateAbsoluteUrl = validateAbsoluteUrl;
package/dist/db/mem.js CHANGED
@@ -79,16 +79,16 @@ class Mem {
79
79
  take += skip;
80
80
  const returnValue = [];
81
81
  if (namespace) {
82
- for (const key in this.store) {
83
- if (key.startsWith(namespace)) {
84
- if (count >= take) {
85
- break;
86
- }
87
- if (count >= skip) {
88
- returnValue.push(this.store[key]);
89
- }
90
- count++;
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)]);
91
90
  }
91
+ count++;
92
92
  }
93
93
  }
94
94
  return returnValue || [];
@@ -108,9 +108,6 @@ class Mem {
108
108
  return __awaiter(this, void 0, void 0, function* () {
109
109
  const k = dbutils.key(namespace, key);
110
110
  this.store[k] = val;
111
- if (!Date.parse(this.store['createdAt']))
112
- this.store['createdAt'] = new Date().toISOString();
113
- this.store['modifiedAt'] = new Date().toISOString();
114
111
  // console.log(this.store)
115
112
  if (ttl) {
116
113
  this.ttlStore[k] = {
@@ -136,6 +133,26 @@ class Mem {
136
133
  }
137
134
  cleanup.add(idxKey);
138
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();
139
156
  });
140
157
  }
141
158
  delete(namespace, key) {
@@ -148,6 +165,8 @@ class Mem {
148
165
  for (const dbKey of dbKeys || []) {
149
166
  this.indexes[dbKey] && this.indexes[dbKey].delete(key);
150
167
  }
168
+ this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)].delete(key);
169
+ this.indexes[dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace)].delete(key);
151
170
  delete this.cleanup[idxKey];
152
171
  delete this.ttlStore[k];
153
172
  });
package/dist/db/mongo.js CHANGED
@@ -40,16 +40,9 @@ class Mongo {
40
40
  }
41
41
  init() {
42
42
  return __awaiter(this, void 0, void 0, function* () {
43
- try {
44
- if (!this.options.url) {
45
- throw Error('Please specify a db url');
46
- }
47
- this.client = new mongodb_1.MongoClient(this.options.url);
48
- yield this.client.connect();
49
- }
50
- catch (err) {
51
- console.error(`error connecting to ${this.options.type} db: ${err}`);
52
- }
43
+ const dbUrl = this.options.url;
44
+ this.client = new mongodb_1.MongoClient(dbUrl);
45
+ yield this.client.connect();
53
46
  this.db = this.client.db();
54
47
  this.collection = this.db.collection('jacksonStore');
55
48
  yield this.collection.createIndex({ indexes: 1 });
package/dist/db/redis.js CHANGED
@@ -79,16 +79,13 @@ class Redis {
79
79
  let count = 0;
80
80
  take += skip;
81
81
  try {
82
- for (var _b = __asyncValues(this.client.scanIterator({
83
- MATCH: dbutils.keyFromParts(namespace, '*'),
84
- COUNT: Math.min(take, 1000),
85
- })), _c; _c = yield _b.next(), !_c.done;) {
86
- const key = _c.value;
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;
87
84
  if (count >= take) {
88
85
  break;
89
86
  }
90
87
  if (count >= skip) {
91
- keyArray.push(key);
88
+ keyArray.push(dbutils.keyFromParts(namespace, value));
92
89
  }
93
90
  count++;
94
91
  }
@@ -137,6 +134,18 @@ class Redis {
137
134
  tx = tx.sAdd(dbutils.keyFromParts(dbutils.indexPrefix, idxKey), key);
138
135
  tx = tx.sAdd(dbutils.keyFromParts(dbutils.indexPrefix, k), idxKey);
139
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
+ ]);
140
149
  yield tx.exec();
141
150
  });
142
151
  }
@@ -151,6 +160,8 @@ class Redis {
151
160
  for (const dbKey of dbKeys || []) {
152
161
  tx.sRem(dbutils.keyFromParts(dbutils.indexPrefix, dbKey), key);
153
162
  }
163
+ tx.ZREM(dbutils.keyFromParts(dbutils.createdAtPrefix, namespace), key);
164
+ tx.ZREM(dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace), key);
154
165
  tx.del(idxKey);
155
166
  return yield tx.exec();
156
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;
@@ -47,8 +47,8 @@ class Sql {
47
47
  return __awaiter(this, void 0, void 0, function* () {
48
48
  while (true) {
49
49
  try {
50
- this.connection = yield (0, typeorm_1.createConnection)({
51
- name: this.options.type + Math.floor(Math.random() * 100000),
50
+ this.dataSource = new typeorm_1.DataSource({
51
+ // name: this.options.type! + Math.floor(Math.random() * 100000),
52
52
  type: this.options.type,
53
53
  url: this.options.url,
54
54
  synchronize: true,
@@ -56,6 +56,7 @@ class Sql {
56
56
  logging: ['error'],
57
57
  entities: [JacksonStore_1.JacksonStore, JacksonIndex_1.JacksonIndex, JacksonTTL_1.JacksonTTL],
58
58
  });
59
+ yield this.dataSource.initialize();
59
60
  break;
60
61
  }
61
62
  catch (err) {
@@ -64,9 +65,9 @@ class Sql {
64
65
  continue;
65
66
  }
66
67
  }
67
- this.storeRepository = this.connection.getRepository(JacksonStore_1.JacksonStore);
68
- this.indexRepository = this.connection.getRepository(JacksonIndex_1.JacksonIndex);
69
- this.ttlRepository = this.connection.getRepository(JacksonTTL_1.JacksonTTL);
68
+ this.storeRepository = this.dataSource.getRepository(JacksonStore_1.JacksonStore);
69
+ this.indexRepository = this.dataSource.getRepository(JacksonIndex_1.JacksonIndex);
70
+ this.ttlRepository = this.dataSource.getRepository(JacksonTTL_1.JacksonTTL);
70
71
  if (this.options.ttl && this.options.cleanupLimit) {
71
72
  this.ttlCleanup = () => __awaiter(this, void 0, void 0, function* () {
72
73
  const now = Date.now();
@@ -99,7 +100,7 @@ class Sql {
99
100
  }
100
101
  get(namespace, key) {
101
102
  return __awaiter(this, void 0, void 0, function* () {
102
- const res = yield this.storeRepository.findOne({
103
+ const res = yield this.storeRepository.findOneBy({
103
104
  key: dbutils.key(namespace, key),
104
105
  });
105
106
  if (res && res.value) {
@@ -133,7 +134,7 @@ class Sql {
133
134
  }
134
135
  getByIndex(namespace, idx) {
135
136
  return __awaiter(this, void 0, void 0, function* () {
136
- const res = yield this.indexRepository.find({
137
+ const res = yield this.indexRepository.findBy({
137
138
  key: dbutils.keyForIndex(namespace, idx),
138
139
  });
139
140
  const ret = [];
@@ -151,7 +152,7 @@ class Sql {
151
152
  }
152
153
  put(namespace, key, val, ttl = 0, ...indexes) {
153
154
  return __awaiter(this, void 0, void 0, function* () {
154
- yield this.connection.transaction((transactionalEntityManager) => __awaiter(this, void 0, void 0, function* () {
155
+ yield this.dataSource.transaction((transactionalEntityManager) => __awaiter(this, void 0, void 0, function* () {
155
156
  const dbKey = dbutils.key(namespace, key);
156
157
  const store = new JacksonStore_1.JacksonStore();
157
158
  store.key = dbKey;
@@ -169,7 +170,7 @@ class Sql {
169
170
  // no ttl support for secondary indexes
170
171
  for (const idx of indexes || []) {
171
172
  const key = dbutils.keyForIndex(namespace, idx);
172
- const rec = yield this.indexRepository.findOne({
173
+ const rec = yield this.indexRepository.findOneBy({
173
174
  key,
174
175
  storeKey: store.key,
175
176
  });
@@ -6,3 +6,5 @@ export declare const keyFromParts: (...parts: string[]) => string;
6
6
  export declare const sleep: (ms: number) => Promise<void>;
7
7
  export declare function isNumeric(num: any): boolean;
8
8
  export declare const indexPrefix = "_index";
9
+ export declare const createdAtPrefix = "_createdAt";
10
+ export declare const modifiedAtPrefix = "_modifiedAt";
package/dist/db/utils.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.indexPrefix = exports.isNumeric = exports.sleep = exports.keyFromParts = exports.keyDigest = exports.keyForIndex = exports.key = void 0;
6
+ exports.modifiedAtPrefix = exports.createdAtPrefix = exports.indexPrefix = exports.isNumeric = exports.sleep = exports.keyFromParts = exports.keyDigest = exports.keyForIndex = exports.key = void 0;
7
7
  const ripemd160_1 = __importDefault(require("ripemd160"));
8
8
  const key = (namespace, k) => {
9
9
  return namespace + ':' + k;
@@ -31,3 +31,5 @@ function isNumeric(num) {
31
31
  }
32
32
  exports.isNumeric = isNumeric;
33
33
  exports.indexPrefix = '_index';
34
+ exports.createdAtPrefix = '_createdAt';
35
+ exports.modifiedAtPrefix = '_modifiedAt';
package/dist/index.d.ts CHANGED
@@ -1,11 +1,15 @@
1
+ import { AdminController } from './controller/admin';
1
2
  import { APIController } from './controller/api';
2
3
  import { OAuthController } from './controller/oauth';
3
- import { AdminController } from './controller/admin';
4
+ import { HealthCheckController } from './controller/health-check';
5
+ import { LogoutController } from './controller/logout';
4
6
  import { JacksonOption } from './typings';
5
7
  export declare const controllers: (opts: JacksonOption) => Promise<{
6
8
  apiController: APIController;
7
9
  oauthController: OAuthController;
8
10
  adminController: AdminController;
11
+ logoutController: LogoutController;
12
+ healthCheckController: HealthCheckController;
9
13
  }>;
10
14
  export default controllers;
11
15
  export * from './typings';
package/dist/index.js CHANGED
@@ -27,12 +27,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.controllers = void 0;
30
+ const admin_1 = require("./controller/admin");
30
31
  const api_1 = require("./controller/api");
31
32
  const oauth_1 = require("./controller/oauth");
32
- const admin_1 = require("./controller/admin");
33
+ const health_check_1 = require("./controller/health-check");
34
+ const logout_1 = require("./controller/logout");
33
35
  const db_1 = __importDefault(require("./db/db"));
34
- const read_config_1 = __importDefault(require("./read-config"));
35
36
  const defaultDb_1 = __importDefault(require("./db/defaultDb"));
37
+ const read_config_1 = __importDefault(require("./read-config"));
36
38
  const defaultOpts = (opts) => {
37
39
  const newOpts = Object.assign({}, opts);
38
40
  if (!newOpts.externalUrl) {
@@ -56,8 +58,11 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
56
58
  const sessionStore = db.store('oauth:session', opts.db.ttl);
57
59
  const codeStore = db.store('oauth:code', opts.db.ttl);
58
60
  const tokenStore = db.store('oauth:token', opts.db.ttl);
61
+ const healthCheckStore = db.store('_health:check');
59
62
  const apiController = new api_1.APIController({ configStore });
60
63
  const adminController = new admin_1.AdminController({ configStore });
64
+ const healthCheckController = new health_check_1.HealthCheckController({ healthCheckStore });
65
+ yield healthCheckController.init();
61
66
  const oauthController = new oauth_1.OAuthController({
62
67
  configStore,
63
68
  sessionStore,
@@ -65,6 +70,11 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
65
70
  tokenStore,
66
71
  opts,
67
72
  });
73
+ const logoutController = new logout_1.LogoutController({
74
+ configStore,
75
+ sessionStore,
76
+ opts,
77
+ });
68
78
  // write pre-loaded config if present
69
79
  if (opts.preLoadedConfig && opts.preLoadedConfig.length > 0) {
70
80
  const configs = yield (0, read_config_1.default)(opts.preLoadedConfig);
@@ -79,6 +89,8 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
79
89
  apiController,
80
90
  oauthController,
81
91
  adminController,
92
+ logoutController,
93
+ healthCheckController,
82
94
  };
83
95
  });
84
96
  exports.controllers = controllers;