@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.
- package/README.md +1 -1
- package/dist/controller/api.d.ts +1 -0
- package/dist/controller/api.js +37 -5
- package/dist/controller/health-check.d.ts +11 -0
- package/dist/controller/health-check.js +53 -0
- package/dist/controller/logout.d.ts +18 -0
- package/dist/controller/logout.js +199 -0
- package/dist/controller/oauth/redirect.d.ts +1 -1
- package/dist/controller/oauth/redirect.js +6 -1
- package/dist/controller/oauth.d.ts +5 -3
- package/dist/controller/oauth.js +156 -27
- package/dist/controller/utils.d.ts +2 -1
- package/dist/controller/utils.js +11 -24
- package/dist/db/mem.js +31 -12
- package/dist/db/mongo.js +3 -10
- package/dist/db/redis.js +17 -6
- package/dist/db/sql/sql.d.ts +1 -1
- package/dist/db/sql/sql.js +10 -9
- package/dist/db/utils.d.ts +2 -0
- package/dist/db/utils.js +3 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +14 -2
- package/dist/typings.d.ts +50 -21
- package/package.json +22 -25
- package/dist/saml/saml.d.ts +0 -12
- package/dist/saml/saml.js +0 -211
package/dist/controller/oauth.js
CHANGED
@@ -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
|
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
|
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
|
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 =
|
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
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
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 =
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
205
|
-
|
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
|
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
|
5
|
+
export declare const relayStatePrefix = "boxyhq_jackson_";
|
6
|
+
export declare const validateAbsoluteUrl: (url: any, message: any) => void;
|
package/dist/controller/utils.js
CHANGED
@@ -1,32 +1,19 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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.
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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.
|
83
|
-
|
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(
|
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
|
});
|
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;
|
package/dist/db/sql/sql.js
CHANGED
@@ -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.
|
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.
|
68
|
-
this.indexRepository = this.
|
69
|
-
this.ttlRepository = this.
|
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.
|
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.
|
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.
|
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.
|
173
|
+
const rec = yield this.indexRepository.findOneBy({
|
173
174
|
key,
|
174
175
|
storeKey: store.key,
|
175
176
|
});
|
package/dist/db/utils.d.ts
CHANGED
@@ -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 {
|
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
|
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;
|