@boxyhq/saml-jackson 0.3.7 → 0.3.8-beta.763
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/dist/controller/admin.d.ts +8 -0
- package/dist/controller/admin.js +27 -0
- package/dist/controller/api.d.ts +120 -6
- package/dist/controller/api.js +194 -10
- package/dist/controller/oauth.d.ts +1 -0
- package/dist/controller/oauth.js +43 -9
- package/dist/controller/utils.d.ts +1 -0
- package/dist/controller/utils.js +25 -1
- package/dist/db/db.d.ts +1 -0
- package/dist/db/db.js +9 -0
- package/dist/db/mem.d.ts +1 -0
- package/dist/db/mem.js +19 -0
- package/dist/db/mongo.d.ts +1 -0
- package/dist/db/mongo.js +23 -2
- package/dist/db/redis.d.ts +1 -0
- package/dist/db/redis.js +20 -0
- package/dist/db/sql/entity/JacksonStore.d.ts +2 -0
- package/dist/db/sql/entity/JacksonStore.js +13 -0
- package/dist/db/sql/sql.d.ts +1 -0
- package/dist/db/sql/sql.js +18 -1
- package/dist/db/store.js +5 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -0
- package/dist/opentelemetry/metrics.d.ts +2 -0
- package/dist/opentelemetry/metrics.js +42 -0
- package/dist/saml/saml.js +1 -1
- package/dist/typings.d.ts +12 -2
- package/migration/mariadb/1644332636666-createdAt.ts +16 -0
- package/migration/mysql/1644332641078-createdAt.ts +16 -0
- package/migration/postgres/1644332647279-createdAt.ts +16 -0
- package/package.json +5 -7
@@ -0,0 +1,27 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.AdminController = void 0;
|
13
|
+
class AdminController {
|
14
|
+
constructor({ configStore }) {
|
15
|
+
this.configStore = configStore;
|
16
|
+
}
|
17
|
+
getAllConfig() {
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
19
|
+
const configList = (yield this.configStore.getAll());
|
20
|
+
if (!configList || !configList.length) {
|
21
|
+
return [];
|
22
|
+
}
|
23
|
+
return configList;
|
24
|
+
});
|
25
|
+
}
|
26
|
+
}
|
27
|
+
exports.AdminController = AdminController;
|
package/dist/controller/api.d.ts
CHANGED
@@ -18,10 +18,23 @@ export declare class APIController implements IAPIController {
|
|
18
18
|
* consumes:
|
19
19
|
* - application/x-www-form-urlencoded
|
20
20
|
* parameters:
|
21
|
+
* - name: name
|
22
|
+
* description: Name/identifier for the config
|
23
|
+
* type: string
|
24
|
+
* in: formData
|
25
|
+
* example: cal-saml-config
|
26
|
+
* - name: description
|
27
|
+
* description: A short description for the config not more than 100 characters
|
28
|
+
* type: string
|
29
|
+
* in: formData
|
30
|
+
* example: SAML login for cal.com app
|
21
31
|
* - name: encodedRawMetadata
|
22
32
|
* description: Base64 encoding of the XML metadata
|
23
33
|
* in: formData
|
24
|
-
*
|
34
|
+
* type: string
|
35
|
+
* - name: rawMetadata
|
36
|
+
* description: Raw XML metadata
|
37
|
+
* in: formData
|
25
38
|
* type: string
|
26
39
|
* - name: defaultRedirectUrl
|
27
40
|
* description: The redirect URL to use in the IdP login flow
|
@@ -63,10 +76,85 @@ export declare class APIController implements IAPIController {
|
|
63
76
|
* client_id: 8958e13053832b5af58fdf2ee83f35f5d013dc74
|
64
77
|
* client_secret: 13f01f4df5b01770c616e682d14d3ba23f20948cfa89b1d7
|
65
78
|
* type: accounts.google.com
|
79
|
+
* 400:
|
80
|
+
* description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters
|
66
81
|
* 401:
|
67
82
|
* description: Unauthorized
|
68
83
|
*/
|
69
84
|
config(body: IdPConfig): Promise<OAuth>;
|
85
|
+
/**
|
86
|
+
* @swagger
|
87
|
+
*
|
88
|
+
* /api/v1/saml/config:
|
89
|
+
* patch:
|
90
|
+
* summary: Update SAML configuration
|
91
|
+
* operationId: update-saml-config
|
92
|
+
* tags: [SAML Config]
|
93
|
+
* consumes:
|
94
|
+
* - application/json
|
95
|
+
* - application/x-www-form-urlencoded
|
96
|
+
* parameters:
|
97
|
+
* - name: clientID
|
98
|
+
* description: Client ID for the config
|
99
|
+
* type: string
|
100
|
+
* in: formData
|
101
|
+
* required: true
|
102
|
+
* - name: clientSecret
|
103
|
+
* description: Client Secret for the config
|
104
|
+
* type: string
|
105
|
+
* in: formData
|
106
|
+
* required: true
|
107
|
+
* - name: name
|
108
|
+
* description: Name/identifier for the config
|
109
|
+
* type: string
|
110
|
+
* in: formData
|
111
|
+
* example: cal-saml-config
|
112
|
+
* - name: description
|
113
|
+
* description: A short description for the config not more than 100 characters
|
114
|
+
* type: string
|
115
|
+
* in: formData
|
116
|
+
* example: SAML login for cal.com app
|
117
|
+
* - name: encodedRawMetadata
|
118
|
+
* description: Base64 encoding of the XML metadata
|
119
|
+
* in: formData
|
120
|
+
* type: string
|
121
|
+
* - name: rawMetadata
|
122
|
+
* description: Raw XML metadata
|
123
|
+
* in: formData
|
124
|
+
* type: string
|
125
|
+
* - name: defaultRedirectUrl
|
126
|
+
* description: The redirect URL to use in the IdP login flow
|
127
|
+
* in: formData
|
128
|
+
* required: true
|
129
|
+
* type: string
|
130
|
+
* example: http://localhost:3000/login/saml
|
131
|
+
* - name: redirectUrl
|
132
|
+
* description: JSON encoded array containing a list of allowed redirect URLs
|
133
|
+
* in: formData
|
134
|
+
* required: true
|
135
|
+
* type: string
|
136
|
+
* example: '["http://localhost:3000/*"]'
|
137
|
+
* - name: tenant
|
138
|
+
* description: Tenant
|
139
|
+
* in: formData
|
140
|
+
* required: true
|
141
|
+
* type: string
|
142
|
+
* example: boxyhq.com
|
143
|
+
* - name: product
|
144
|
+
* description: Product
|
145
|
+
* in: formData
|
146
|
+
* required: true
|
147
|
+
* type: string
|
148
|
+
* example: demo
|
149
|
+
* responses:
|
150
|
+
* 204:
|
151
|
+
* description: Success
|
152
|
+
* 400:
|
153
|
+
* description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata | Description should not exceed 100 characters
|
154
|
+
* 401:
|
155
|
+
* description: Unauthorized
|
156
|
+
*/
|
157
|
+
updateConfig(body: any): Promise<void>;
|
70
158
|
/**
|
71
159
|
* @swagger
|
72
160
|
*
|
@@ -96,11 +184,35 @@ export declare class APIController implements IAPIController {
|
|
96
184
|
* description: Success
|
97
185
|
* schema:
|
98
186
|
* type: object
|
99
|
-
* properties:
|
100
|
-
* provider:
|
101
|
-
* type: string
|
102
187
|
* example:
|
103
|
-
*
|
188
|
+
* {
|
189
|
+
* "config": {
|
190
|
+
* "idpMetadata": {
|
191
|
+
* "sso": {
|
192
|
+
* "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
|
193
|
+
* "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
|
194
|
+
* },
|
195
|
+
* "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
|
196
|
+
* "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
|
197
|
+
* "loginType": "idp",
|
198
|
+
* "provider": "okta.com"
|
199
|
+
* },
|
200
|
+
* "defaultRedirectUrl": "https://hoppscotch.io/",
|
201
|
+
* "redirectUrl": ["https://hoppscotch.io/"],
|
202
|
+
* "tenant": "hoppscotch.io",
|
203
|
+
* "product": "API Engine",
|
204
|
+
* "name": "Hoppscotch-SP",
|
205
|
+
* "description": "SP for hoppscotch.io",
|
206
|
+
* "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
|
207
|
+
* "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
|
208
|
+
* "certs": {
|
209
|
+
* "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
|
210
|
+
* "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
|
211
|
+
* }
|
212
|
+
* }
|
213
|
+
* }
|
214
|
+
* '400':
|
215
|
+
* description: Please provide `clientID` or `tenant` and `product`.
|
104
216
|
* '401':
|
105
217
|
* description: Unauthorized
|
106
218
|
*/
|
@@ -108,7 +220,7 @@ export declare class APIController implements IAPIController {
|
|
108
220
|
clientID: string;
|
109
221
|
tenant: string;
|
110
222
|
product: string;
|
111
|
-
}): Promise<
|
223
|
+
}): Promise<any>;
|
112
224
|
/**
|
113
225
|
* @swagger
|
114
226
|
* /api/v1/saml/config:
|
@@ -143,6 +255,8 @@ export declare class APIController implements IAPIController {
|
|
143
255
|
* responses:
|
144
256
|
* '200':
|
145
257
|
* description: Success
|
258
|
+
* '400':
|
259
|
+
* description: clientSecret mismatch | Please provide `clientID` and `clientSecret` or `tenant` and `product`.'
|
146
260
|
* '401':
|
147
261
|
* description: Unauthorized
|
148
262
|
*/
|
package/dist/controller/api.js
CHANGED
@@ -27,6 +27,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
27
27
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
28
28
|
});
|
29
29
|
};
|
30
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
31
|
+
var t = {};
|
32
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
33
|
+
t[p] = s[p];
|
34
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
35
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
36
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
37
|
+
t[p[i]] = s[p[i]];
|
38
|
+
}
|
39
|
+
return t;
|
40
|
+
};
|
30
41
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
31
42
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
32
43
|
};
|
@@ -34,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
45
|
exports.APIController = void 0;
|
35
46
|
const crypto_1 = __importDefault(require("crypto"));
|
36
47
|
const dbutils = __importStar(require("../db/utils"));
|
48
|
+
const metrics = __importStar(require("../opentelemetry/metrics"));
|
37
49
|
const saml_1 = __importDefault(require("../saml/saml"));
|
38
50
|
const x509_1 = __importDefault(require("../saml/x509"));
|
39
51
|
const error_1 = require("./error");
|
@@ -43,7 +55,7 @@ class APIController {
|
|
43
55
|
this.configStore = configStore;
|
44
56
|
}
|
45
57
|
_validateIdPConfig(body) {
|
46
|
-
const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
|
58
|
+
const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, description } = body;
|
47
59
|
if (!rawMetadata && !encodedRawMetadata) {
|
48
60
|
throw new error_1.JacksonError('Please provide rawMetadata or encodedRawMetadata', 400);
|
49
61
|
}
|
@@ -59,6 +71,9 @@ class APIController {
|
|
59
71
|
if (!product) {
|
60
72
|
throw new error_1.JacksonError('Please provide product', 400);
|
61
73
|
}
|
74
|
+
if (description && description.length > 100) {
|
75
|
+
throw new error_1.JacksonError('Description should not exceed 100 characters', 400);
|
76
|
+
}
|
62
77
|
}
|
63
78
|
/**
|
64
79
|
* @swagger
|
@@ -73,10 +88,23 @@ class APIController {
|
|
73
88
|
* consumes:
|
74
89
|
* - application/x-www-form-urlencoded
|
75
90
|
* parameters:
|
91
|
+
* - name: name
|
92
|
+
* description: Name/identifier for the config
|
93
|
+
* type: string
|
94
|
+
* in: formData
|
95
|
+
* example: cal-saml-config
|
96
|
+
* - name: description
|
97
|
+
* description: A short description for the config not more than 100 characters
|
98
|
+
* type: string
|
99
|
+
* in: formData
|
100
|
+
* example: SAML login for cal.com app
|
76
101
|
* - name: encodedRawMetadata
|
77
102
|
* description: Base64 encoding of the XML metadata
|
78
103
|
* in: formData
|
79
|
-
*
|
104
|
+
* type: string
|
105
|
+
* - name: rawMetadata
|
106
|
+
* description: Raw XML metadata
|
107
|
+
* in: formData
|
80
108
|
* type: string
|
81
109
|
* - name: defaultRedirectUrl
|
82
110
|
* description: The redirect URL to use in the IdP login flow
|
@@ -118,12 +146,15 @@ class APIController {
|
|
118
146
|
* client_id: 8958e13053832b5af58fdf2ee83f35f5d013dc74
|
119
147
|
* client_secret: 13f01f4df5b01770c616e682d14d3ba23f20948cfa89b1d7
|
120
148
|
* type: accounts.google.com
|
149
|
+
* 400:
|
150
|
+
* description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters
|
121
151
|
* 401:
|
122
152
|
* description: Unauthorized
|
123
153
|
*/
|
124
154
|
config(body) {
|
125
155
|
return __awaiter(this, void 0, void 0, function* () {
|
126
|
-
const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
|
156
|
+
const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, name, description, } = body;
|
157
|
+
metrics.increment('createConfig');
|
127
158
|
this._validateIdPConfig(body);
|
128
159
|
let metaData = rawMetadata;
|
129
160
|
if (encodedRawMetadata) {
|
@@ -155,6 +186,8 @@ class APIController {
|
|
155
186
|
redirectUrl: JSON.parse(redirectUrl),
|
156
187
|
tenant,
|
157
188
|
product,
|
189
|
+
name,
|
190
|
+
description,
|
158
191
|
clientID,
|
159
192
|
clientSecret,
|
160
193
|
certs,
|
@@ -174,6 +207,129 @@ class APIController {
|
|
174
207
|
};
|
175
208
|
});
|
176
209
|
}
|
210
|
+
/**
|
211
|
+
* @swagger
|
212
|
+
*
|
213
|
+
* /api/v1/saml/config:
|
214
|
+
* patch:
|
215
|
+
* summary: Update SAML configuration
|
216
|
+
* operationId: update-saml-config
|
217
|
+
* tags: [SAML Config]
|
218
|
+
* consumes:
|
219
|
+
* - application/json
|
220
|
+
* - application/x-www-form-urlencoded
|
221
|
+
* parameters:
|
222
|
+
* - name: clientID
|
223
|
+
* description: Client ID for the config
|
224
|
+
* type: string
|
225
|
+
* in: formData
|
226
|
+
* required: true
|
227
|
+
* - name: clientSecret
|
228
|
+
* description: Client Secret for the config
|
229
|
+
* type: string
|
230
|
+
* in: formData
|
231
|
+
* required: true
|
232
|
+
* - name: name
|
233
|
+
* description: Name/identifier for the config
|
234
|
+
* type: string
|
235
|
+
* in: formData
|
236
|
+
* example: cal-saml-config
|
237
|
+
* - name: description
|
238
|
+
* description: A short description for the config not more than 100 characters
|
239
|
+
* type: string
|
240
|
+
* in: formData
|
241
|
+
* example: SAML login for cal.com app
|
242
|
+
* - name: encodedRawMetadata
|
243
|
+
* description: Base64 encoding of the XML metadata
|
244
|
+
* in: formData
|
245
|
+
* type: string
|
246
|
+
* - name: rawMetadata
|
247
|
+
* description: Raw XML metadata
|
248
|
+
* in: formData
|
249
|
+
* type: string
|
250
|
+
* - name: defaultRedirectUrl
|
251
|
+
* description: The redirect URL to use in the IdP login flow
|
252
|
+
* in: formData
|
253
|
+
* required: true
|
254
|
+
* type: string
|
255
|
+
* example: http://localhost:3000/login/saml
|
256
|
+
* - name: redirectUrl
|
257
|
+
* description: JSON encoded array containing a list of allowed redirect URLs
|
258
|
+
* in: formData
|
259
|
+
* required: true
|
260
|
+
* type: string
|
261
|
+
* example: '["http://localhost:3000/*"]'
|
262
|
+
* - name: tenant
|
263
|
+
* description: Tenant
|
264
|
+
* in: formData
|
265
|
+
* required: true
|
266
|
+
* type: string
|
267
|
+
* example: boxyhq.com
|
268
|
+
* - name: product
|
269
|
+
* description: Product
|
270
|
+
* in: formData
|
271
|
+
* required: true
|
272
|
+
* type: string
|
273
|
+
* example: demo
|
274
|
+
* responses:
|
275
|
+
* 204:
|
276
|
+
* description: Success
|
277
|
+
* 400:
|
278
|
+
* description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata | Description should not exceed 100 characters
|
279
|
+
* 401:
|
280
|
+
* description: Unauthorized
|
281
|
+
*/
|
282
|
+
updateConfig(body) {
|
283
|
+
var _a;
|
284
|
+
return __awaiter(this, void 0, void 0, function* () {
|
285
|
+
const { encodedRawMetadata, // could be empty
|
286
|
+
rawMetadata, // could be empty
|
287
|
+
defaultRedirectUrl, redirectUrl, name, description } = body, clientInfo = __rest(body, ["encodedRawMetadata", "rawMetadata", "defaultRedirectUrl", "redirectUrl", "name", "description"]);
|
288
|
+
if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
|
289
|
+
throw new error_1.JacksonError('Please provide clientID', 400);
|
290
|
+
}
|
291
|
+
if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
|
292
|
+
throw new error_1.JacksonError('Please provide clientSecret', 400);
|
293
|
+
}
|
294
|
+
if (description && description.length > 100) {
|
295
|
+
throw new error_1.JacksonError('Description should not exceed 100 characters', 400);
|
296
|
+
}
|
297
|
+
const _currentConfig = (_a = (yield this.getConfig(clientInfo))) === null || _a === void 0 ? void 0 : _a.config;
|
298
|
+
if (_currentConfig.clientSecret !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
|
299
|
+
throw new error_1.JacksonError('clientSecret mismatch', 400);
|
300
|
+
}
|
301
|
+
let metaData = rawMetadata;
|
302
|
+
if (encodedRawMetadata) {
|
303
|
+
metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
|
304
|
+
}
|
305
|
+
let newMetadata;
|
306
|
+
if (metaData) {
|
307
|
+
newMetadata = yield saml_1.default.parseMetadataAsync(metaData);
|
308
|
+
// extract provider
|
309
|
+
let providerName = extractHostName(newMetadata.entityID);
|
310
|
+
if (!providerName) {
|
311
|
+
providerName = extractHostName(newMetadata.sso.redirectUrl || newMetadata.sso.postUrl);
|
312
|
+
}
|
313
|
+
newMetadata.provider = providerName ? providerName : 'Unknown';
|
314
|
+
}
|
315
|
+
if (newMetadata) {
|
316
|
+
// check if clientID matches with new metadata payload
|
317
|
+
const clientID = dbutils.keyDigest(dbutils.keyFromParts(clientInfo.tenant, clientInfo.product, newMetadata.entityID));
|
318
|
+
if (clientID !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
|
319
|
+
throw new error_1.JacksonError('Tenant/Product config mismatch with IdP metadata', 400);
|
320
|
+
}
|
321
|
+
}
|
322
|
+
yield this.configStore.put(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID, Object.assign(Object.assign({}, _currentConfig), { name: name ? name : _currentConfig.name, description: description ? description : _currentConfig.description, idpMetadata: newMetadata ? newMetadata : _currentConfig.idpMetadata, defaultRedirectUrl: defaultRedirectUrl ? defaultRedirectUrl : _currentConfig.defaultRedirectUrl, redirectUrl: redirectUrl ? JSON.parse(redirectUrl) : _currentConfig.redirectUrl }), {
|
323
|
+
// secondary index on entityID
|
324
|
+
name: utils_1.IndexNames.EntityID,
|
325
|
+
value: _currentConfig.idpMetadata.entityID,
|
326
|
+
}, {
|
327
|
+
// secondary index on tenant + product
|
328
|
+
name: utils_1.IndexNames.TenantProduct,
|
329
|
+
value: dbutils.keyFromParts(_currentConfig.tenant, _currentConfig.product),
|
330
|
+
});
|
331
|
+
});
|
332
|
+
}
|
177
333
|
/**
|
178
334
|
* @swagger
|
179
335
|
*
|
@@ -203,20 +359,45 @@ class APIController {
|
|
203
359
|
* description: Success
|
204
360
|
* schema:
|
205
361
|
* type: object
|
206
|
-
* properties:
|
207
|
-
* provider:
|
208
|
-
* type: string
|
209
362
|
* example:
|
210
|
-
*
|
363
|
+
* {
|
364
|
+
* "config": {
|
365
|
+
* "idpMetadata": {
|
366
|
+
* "sso": {
|
367
|
+
* "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml",
|
368
|
+
* "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxxx/sso/saml"
|
369
|
+
* },
|
370
|
+
* "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
|
371
|
+
* "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
|
372
|
+
* "loginType": "idp",
|
373
|
+
* "provider": "okta.com"
|
374
|
+
* },
|
375
|
+
* "defaultRedirectUrl": "https://hoppscotch.io/",
|
376
|
+
* "redirectUrl": ["https://hoppscotch.io/"],
|
377
|
+
* "tenant": "hoppscotch.io",
|
378
|
+
* "product": "API Engine",
|
379
|
+
* "name": "Hoppscotch-SP",
|
380
|
+
* "description": "SP for hoppscotch.io",
|
381
|
+
* "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
|
382
|
+
* "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
|
383
|
+
* "certs": {
|
384
|
+
* "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
|
385
|
+
* "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
|
386
|
+
* }
|
387
|
+
* }
|
388
|
+
* }
|
389
|
+
* '400':
|
390
|
+
* description: Please provide `clientID` or `tenant` and `product`.
|
211
391
|
* '401':
|
212
392
|
* description: Unauthorized
|
213
393
|
*/
|
214
394
|
getConfig(body) {
|
215
395
|
return __awaiter(this, void 0, void 0, function* () {
|
216
396
|
const { clientID, tenant, product } = body;
|
397
|
+
metrics.increment('getConfig');
|
217
398
|
if (clientID) {
|
218
399
|
const samlConfig = yield this.configStore.get(clientID);
|
219
|
-
return samlConfig ? {
|
400
|
+
return samlConfig ? { config: samlConfig } : {};
|
220
401
|
}
|
221
402
|
if (tenant && product) {
|
222
403
|
const samlConfigs = yield this.configStore.getByIndex({
|
@@ -226,7 +407,7 @@ class APIController {
|
|
226
407
|
if (!samlConfigs || !samlConfigs.length) {
|
227
408
|
return {};
|
228
409
|
}
|
229
|
-
return {
|
410
|
+
return { config: samlConfigs[0] };
|
230
411
|
}
|
231
412
|
throw new error_1.JacksonError('Please provide `clientID` or `tenant` and `product`.', 400);
|
232
413
|
});
|
@@ -265,12 +446,15 @@ class APIController {
|
|
265
446
|
* responses:
|
266
447
|
* '200':
|
267
448
|
* description: Success
|
449
|
+
* '400':
|
450
|
+
* description: clientSecret mismatch | Please provide `clientID` and `clientSecret` or `tenant` and `product`.'
|
268
451
|
* '401':
|
269
452
|
* description: Unauthorized
|
270
453
|
*/
|
271
454
|
deleteConfig(body) {
|
272
455
|
return __awaiter(this, void 0, void 0, function* () {
|
273
456
|
const { clientID, clientSecret, tenant, product } = body;
|
457
|
+
metrics.increment('deleteConfig');
|
274
458
|
if (clientID && clientSecret) {
|
275
459
|
const samlConfig = yield this.configStore.get(clientID);
|
276
460
|
if (!samlConfig) {
|
@@ -280,7 +464,7 @@ class APIController {
|
|
280
464
|
yield this.configStore.delete(clientID);
|
281
465
|
}
|
282
466
|
else {
|
283
|
-
throw new error_1.JacksonError('clientSecret mismatch
|
467
|
+
throw new error_1.JacksonError('clientSecret mismatch', 400);
|
284
468
|
}
|
285
469
|
return;
|
286
470
|
}
|
package/dist/controller/oauth.js
CHANGED
@@ -33,15 +33,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
33
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
34
34
|
exports.OAuthController = void 0;
|
35
35
|
const crypto_1 = __importDefault(require("crypto"));
|
36
|
+
const util_1 = require("util");
|
37
|
+
const zlib_1 = require("zlib");
|
36
38
|
const dbutils = __importStar(require("../db/utils"));
|
39
|
+
const metrics = __importStar(require("../opentelemetry/metrics"));
|
37
40
|
const saml_1 = __importDefault(require("../saml/saml"));
|
38
41
|
const error_1 = require("./error");
|
39
42
|
const allowed = __importStar(require("./oauth/allowed"));
|
40
43
|
const codeVerifier = __importStar(require("./oauth/code-verifier"));
|
41
44
|
const redirect = __importStar(require("./oauth/redirect"));
|
42
45
|
const utils_1 = require("./utils");
|
43
|
-
const util_1 = require("util");
|
44
|
-
const zlib_1 = require("zlib");
|
45
46
|
const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
|
46
47
|
const relayStatePrefix = 'boxyhq_jackson_';
|
47
48
|
function getEncodedClientId(client_id) {
|
@@ -74,6 +75,7 @@ class OAuthController {
|
|
74
75
|
const { response_type = 'code', client_id, redirect_uri, state, tenant, product, code_challenge, code_challenge_method = '',
|
75
76
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
76
77
|
provider = 'saml', } = body;
|
78
|
+
metrics.increment('oauthAuthorize');
|
77
79
|
if (!redirect_uri) {
|
78
80
|
throw new error_1.JacksonError('Please specify a redirect URL.', 400);
|
79
81
|
}
|
@@ -119,7 +121,20 @@ class OAuthController {
|
|
119
121
|
if (!allowed.redirect(redirect_uri, samlConfig.redirectUrl)) {
|
120
122
|
throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
|
121
123
|
}
|
124
|
+
let ssoUrl;
|
125
|
+
let post = false;
|
126
|
+
const { sso } = samlConfig.idpMetadata;
|
127
|
+
if ('redirectUrl' in sso) {
|
128
|
+
// HTTP Redirect binding
|
129
|
+
ssoUrl = sso.redirectUrl;
|
130
|
+
}
|
131
|
+
else if ('postUrl' in sso) {
|
132
|
+
// HTTP-POST binding
|
133
|
+
ssoUrl = sso.postUrl;
|
134
|
+
post = true;
|
135
|
+
}
|
122
136
|
const samlReq = saml_1.default.request({
|
137
|
+
ssoUrl,
|
123
138
|
entityID: this.opts.samlAudience,
|
124
139
|
callbackUrl: this.opts.externalUrl + this.opts.samlPath,
|
125
140
|
signingKey: samlConfig.certs.privateKey,
|
@@ -133,13 +148,24 @@ class OAuthController {
|
|
133
148
|
code_challenge,
|
134
149
|
code_challenge_method,
|
135
150
|
});
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
151
|
+
const relayState = relayStatePrefix + sessionId;
|
152
|
+
let redirectUrl;
|
153
|
+
let authorizeForm;
|
154
|
+
if (!post) {
|
155
|
+
// HTTP Redirect binding
|
156
|
+
redirectUrl = redirect.success(ssoUrl, {
|
157
|
+
RelayState: relayState,
|
158
|
+
SAMLRequest: Buffer.from(yield deflateRawAsync(samlReq.request)).toString('base64'),
|
159
|
+
});
|
160
|
+
}
|
161
|
+
else {
|
162
|
+
// HTTP POST binding
|
163
|
+
authorizeForm = (0, utils_1.createAuthorizeForm)(relayState, encodeURI(Buffer.from(samlReq.request).toString('base64')), ssoUrl);
|
164
|
+
}
|
165
|
+
return {
|
166
|
+
redirect_url: redirectUrl,
|
167
|
+
authorize_form: authorizeForm,
|
168
|
+
};
|
143
169
|
});
|
144
170
|
}
|
145
171
|
samlResponse(body) {
|
@@ -262,6 +288,7 @@ class OAuthController {
|
|
262
288
|
token(body) {
|
263
289
|
return __awaiter(this, void 0, void 0, function* () {
|
264
290
|
const { client_id, client_secret, code_verifier, code, grant_type = 'authorization_code' } = body;
|
291
|
+
metrics.increment('oauthToken');
|
265
292
|
if (grant_type !== 'authorization_code') {
|
266
293
|
throw new error_1.JacksonError('Unsupported grant_type', 400);
|
267
294
|
}
|
@@ -292,6 +319,12 @@ class OAuthController {
|
|
292
319
|
throw new error_1.JacksonError('Invalid client_id or client_secret', 401);
|
293
320
|
}
|
294
321
|
}
|
322
|
+
else {
|
323
|
+
// encoded client_id, verify client_secret
|
324
|
+
if (client_secret !== this.opts.clientSecretVerifier) {
|
325
|
+
throw new error_1.JacksonError('Invalid client_secret', 401);
|
326
|
+
}
|
327
|
+
}
|
295
328
|
}
|
296
329
|
}
|
297
330
|
else if (codeVal && codeVal.session) {
|
@@ -339,6 +372,7 @@ class OAuthController {
|
|
339
372
|
userInfo(token) {
|
340
373
|
return __awaiter(this, void 0, void 0, function* () {
|
341
374
|
const rsp = yield this.tokenStore.get(token);
|
375
|
+
metrics.increment('oauthUserInfo');
|
342
376
|
if (!rsp || !rsp.claims) {
|
343
377
|
throw new error_1.JacksonError('Invalid token', 403);
|
344
378
|
}
|
package/dist/controller/utils.js
CHANGED
@@ -1,8 +1,32 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.IndexNames = void 0;
|
3
|
+
exports.createAuthorizeForm = exports.IndexNames = void 0;
|
4
4
|
var IndexNames;
|
5
5
|
(function (IndexNames) {
|
6
6
|
IndexNames["EntityID"] = "entityID";
|
7
7
|
IndexNames["TenantProduct"] = "tenantProduct";
|
8
8
|
})(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('');
|
31
|
+
};
|
32
|
+
exports.createAuthorizeForm = createAuthorizeForm;
|
package/dist/db/db.d.ts
CHANGED
@@ -4,6 +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
8
|
getByIndex(namespace: string, idx: Index): Promise<unknown[]>;
|
8
9
|
put(namespace: string, key: string, val: unknown, ttl?: number, ...indexes: Index[]): Promise<unknown>;
|
9
10
|
delete(namespace: string, key: string): Promise<unknown>;
|
package/dist/db/db.js
CHANGED
@@ -57,6 +57,15 @@ class DB {
|
|
57
57
|
return decrypt(res, this.encryptionKey);
|
58
58
|
});
|
59
59
|
}
|
60
|
+
getAll(namespace) {
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
62
|
+
const res = (yield this.db.getAll(namespace));
|
63
|
+
const encryptionKey = this.encryptionKey;
|
64
|
+
return res.map((r) => {
|
65
|
+
return decrypt(r, encryptionKey);
|
66
|
+
});
|
67
|
+
});
|
68
|
+
}
|
60
69
|
getByIndex(namespace, idx) {
|
61
70
|
return __awaiter(this, void 0, void 0, function* () {
|
62
71
|
const res = yield this.db.getByIndex(namespace, idx);
|
package/dist/db/mem.d.ts
CHANGED
@@ -10,6 +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
14
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
14
15
|
put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<any>;
|
15
16
|
delete(namespace: string, key: string): Promise<any>;
|
package/dist/db/mem.js
CHANGED
@@ -66,6 +66,21 @@ class Mem {
|
|
66
66
|
return null;
|
67
67
|
});
|
68
68
|
}
|
69
|
+
getAll(namespace) {
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
71
|
+
const returnValue = [];
|
72
|
+
if (namespace) {
|
73
|
+
for (const key in this.store) {
|
74
|
+
if (key.startsWith(namespace)) {
|
75
|
+
returnValue.push(this.store[key]);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
if (returnValue)
|
80
|
+
return returnValue;
|
81
|
+
return [];
|
82
|
+
});
|
83
|
+
}
|
69
84
|
getByIndex(namespace, idx) {
|
70
85
|
return __awaiter(this, void 0, void 0, function* () {
|
71
86
|
const dbKeys = yield this.indexes[dbutils.keyForIndex(namespace, idx)];
|
@@ -80,6 +95,10 @@ class Mem {
|
|
80
95
|
return __awaiter(this, void 0, void 0, function* () {
|
81
96
|
const k = dbutils.key(namespace, key);
|
82
97
|
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
|
+
// console.log(this.store)
|
83
102
|
if (ttl) {
|
84
103
|
this.ttlStore[k] = {
|
85
104
|
namespace,
|
package/dist/db/mongo.d.ts
CHANGED
@@ -7,6 +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
11
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
11
12
|
put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
|
12
13
|
delete(namespace: string, key: string): Promise<any>;
|
package/dist/db/mongo.js
CHANGED
@@ -36,8 +36,16 @@ class Mongo {
|
|
36
36
|
}
|
37
37
|
init() {
|
38
38
|
return __awaiter(this, void 0, void 0, function* () {
|
39
|
-
|
40
|
-
|
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
|
+
}
|
41
49
|
this.db = this.client.db();
|
42
50
|
this.collection = this.db.collection('jacksonStore');
|
43
51
|
yield this.collection.createIndex({ indexes: 1 });
|
@@ -56,6 +64,15 @@ class Mongo {
|
|
56
64
|
return null;
|
57
65
|
});
|
58
66
|
}
|
67
|
+
getAll(namespace) {
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
69
|
+
const _namespaceMatch = new RegExp(`^${namespace}:.*`);
|
70
|
+
const docs = yield this.collection.find({ _id: _namespaceMatch }).toArray();
|
71
|
+
if (docs)
|
72
|
+
return docs.map(({ value }) => value);
|
73
|
+
return [];
|
74
|
+
});
|
75
|
+
}
|
59
76
|
getByIndex(namespace, idx) {
|
60
77
|
return __awaiter(this, void 0, void 0, function* () {
|
61
78
|
const docs = yield this.collection
|
@@ -86,8 +103,12 @@ class Mongo {
|
|
86
103
|
}
|
87
104
|
doc.indexes.push(idxKey);
|
88
105
|
}
|
106
|
+
doc.modifiedAt = new Date().toISOString();
|
89
107
|
yield this.collection.updateOne({ _id: dbutils.key(namespace, key) }, {
|
90
108
|
$set: doc,
|
109
|
+
$setOnInsert: {
|
110
|
+
createdAt: new Date().toISOString(),
|
111
|
+
},
|
91
112
|
}, { upsert: true });
|
92
113
|
});
|
93
114
|
}
|
package/dist/db/redis.d.ts
CHANGED
@@ -5,6 +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
9
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
9
10
|
put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
|
10
11
|
delete(namespace: string, key: string): Promise<any>;
|
package/dist/db/redis.js
CHANGED
@@ -57,6 +57,26 @@ class Redis {
|
|
57
57
|
return null;
|
58
58
|
});
|
59
59
|
}
|
60
|
+
getAll(namespace) {
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
62
|
+
const keys = yield this.client.sendCommand(['keys', namespace + ':*']);
|
63
|
+
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));
|
69
|
+
}
|
70
|
+
}
|
71
|
+
catch (error) {
|
72
|
+
console.error(error);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
if (returnValue)
|
76
|
+
return returnValue;
|
77
|
+
return [];
|
78
|
+
});
|
79
|
+
}
|
60
80
|
getByIndex(namespace, idx) {
|
61
81
|
return __awaiter(this, void 0, void 0, function* () {
|
62
82
|
const dbKeys = yield this.client.sMembers(dbutils.keyForIndex(namespace, idx));
|
@@ -36,6 +36,19 @@ __decorate([
|
|
36
36
|
nullable: true,
|
37
37
|
})
|
38
38
|
], JacksonStore.prototype, "tag", void 0);
|
39
|
+
__decorate([
|
40
|
+
(0, typeorm_1.Column)({
|
41
|
+
type: 'timestamp',
|
42
|
+
default: () => 'CURRENT_TIMESTAMP',
|
43
|
+
nullable: false,
|
44
|
+
})
|
45
|
+
], JacksonStore.prototype, "createdAt", void 0);
|
46
|
+
__decorate([
|
47
|
+
(0, typeorm_1.Column)({
|
48
|
+
type: 'timestamp',
|
49
|
+
nullable: true,
|
50
|
+
})
|
51
|
+
], JacksonStore.prototype, "modifiedAt", void 0);
|
39
52
|
JacksonStore = __decorate([
|
40
53
|
(0, typeorm_1.Entity)()
|
41
54
|
], JacksonStore);
|
package/dist/db/sql/sql.d.ts
CHANGED
@@ -10,6 +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
14
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
14
15
|
put(namespace: string, key: string, val: Encrypted, ttl?: number, ...indexes: any[]): Promise<void>;
|
15
16
|
delete(namespace: string, key: string): Promise<any>;
|
package/dist/db/sql/sql.js
CHANGED
@@ -95,7 +95,7 @@ class Sql {
|
|
95
95
|
}
|
96
96
|
get(namespace, key) {
|
97
97
|
return __awaiter(this, void 0, void 0, function* () {
|
98
|
-
|
98
|
+
const res = yield this.storeRepository.findOne({
|
99
99
|
key: dbutils.key(namespace, key),
|
100
100
|
});
|
101
101
|
if (res && res.value) {
|
@@ -108,6 +108,22 @@ class Sql {
|
|
108
108
|
return null;
|
109
109
|
});
|
110
110
|
}
|
111
|
+
getAll(namespace) {
|
112
|
+
return __awaiter(this, void 0, void 0, function* () {
|
113
|
+
const response = yield this.storeRepository.find({
|
114
|
+
where: { key: (0, typeorm_1.Like)(`%${namespace}%`) },
|
115
|
+
select: ['value', 'iv', 'tag'],
|
116
|
+
order: {
|
117
|
+
['createdAt']: 'DESC',
|
118
|
+
// ['createdAt']: 'ASC',
|
119
|
+
},
|
120
|
+
});
|
121
|
+
const returnValue = JSON.parse(JSON.stringify(response));
|
122
|
+
if (returnValue)
|
123
|
+
return returnValue;
|
124
|
+
return [];
|
125
|
+
});
|
126
|
+
}
|
111
127
|
getByIndex(namespace, idx) {
|
112
128
|
return __awaiter(this, void 0, void 0, function* () {
|
113
129
|
const res = yield this.indexRepository.find({
|
@@ -135,6 +151,7 @@ class Sql {
|
|
135
151
|
store.value = val.value;
|
136
152
|
store.iv = val.iv;
|
137
153
|
store.tag = val.tag;
|
154
|
+
store.modifiedAt = new Date().toISOString();
|
138
155
|
yield transactionalEntityManager.save(store);
|
139
156
|
if (ttl) {
|
140
157
|
const ttlRec = new JacksonTTL_1.JacksonTTL();
|
package/dist/db/store.js
CHANGED
@@ -40,6 +40,11 @@ class Store {
|
|
40
40
|
return yield this.db.get(this.namespace, dbutils.keyDigest(key));
|
41
41
|
});
|
42
42
|
}
|
43
|
+
getAll() {
|
44
|
+
return __awaiter(this, void 0, void 0, function* () {
|
45
|
+
return yield this.db.getAll(this.namespace);
|
46
|
+
});
|
47
|
+
}
|
43
48
|
getByIndex(idx) {
|
44
49
|
return __awaiter(this, void 0, void 0, function* () {
|
45
50
|
idx.value = dbutils.keyDigest(idx.value);
|
package/dist/index.d.ts
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
import { JacksonOption } from './typings';
|
2
1
|
import { APIController } from './controller/api';
|
3
2
|
import { OAuthController } from './controller/oauth';
|
3
|
+
import { AdminController } from './controller/admin';
|
4
|
+
import { JacksonOption } from './typings';
|
4
5
|
export declare const controllers: (opts: JacksonOption) => Promise<{
|
5
6
|
apiController: APIController;
|
6
7
|
oauthController: OAuthController;
|
8
|
+
adminController: AdminController;
|
7
9
|
}>;
|
8
10
|
export default controllers;
|
9
11
|
export * from './typings';
|
package/dist/index.js
CHANGED
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.controllers = void 0;
|
26
26
|
const api_1 = require("./controller/api");
|
27
27
|
const oauth_1 = require("./controller/oauth");
|
28
|
+
const admin_1 = require("./controller/admin");
|
28
29
|
const db_1 = __importDefault(require("./db/db"));
|
29
30
|
const read_config_1 = __importDefault(require("./read-config"));
|
30
31
|
const defaultOpts = (opts) => {
|
@@ -44,6 +45,7 @@ const defaultOpts = (opts) => {
|
|
44
45
|
newOpts.db.type = newOpts.db.type || 'postgres'; // Only needed if DB_ENGINE is sql.
|
45
46
|
newOpts.db.ttl = (newOpts.db.ttl || 300) * 1; // TTL for the code, session and token stores (in seconds)
|
46
47
|
newOpts.db.cleanupLimit = (newOpts.db.cleanupLimit || 1000) * 1; // Limit cleanup of TTL entries to this many items at a time
|
48
|
+
newOpts.clientSecretVerifier = newOpts.clientSecretVerifier || 'dummy';
|
47
49
|
return newOpts;
|
48
50
|
};
|
49
51
|
const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
@@ -54,6 +56,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
56
|
const codeStore = db.store('oauth:code', opts.db.ttl);
|
55
57
|
const tokenStore = db.store('oauth:token', opts.db.ttl);
|
56
58
|
const apiController = new api_1.APIController({ configStore });
|
59
|
+
const adminController = new admin_1.AdminController({ configStore });
|
57
60
|
const oauthController = new oauth_1.OAuthController({
|
58
61
|
configStore,
|
59
62
|
sessionStore,
|
@@ -74,6 +77,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
74
77
|
return {
|
75
78
|
apiController,
|
76
79
|
oauthController,
|
80
|
+
adminController,
|
77
81
|
};
|
78
82
|
});
|
79
83
|
exports.controllers = controllers;
|
@@ -0,0 +1,42 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.increment = void 0;
|
4
|
+
const api_metrics_1 = require("@opentelemetry/api-metrics");
|
5
|
+
const counters = {
|
6
|
+
createConfig: {
|
7
|
+
name: 'jackson.config.create',
|
8
|
+
description: 'Number of SAML config create requests',
|
9
|
+
},
|
10
|
+
getConfig: {
|
11
|
+
name: 'jackson.config.get',
|
12
|
+
description: 'Number of SAML config get requests',
|
13
|
+
},
|
14
|
+
deleteConfig: {
|
15
|
+
name: 'jackson.config.delete',
|
16
|
+
description: 'Number of SAML config delete requests',
|
17
|
+
},
|
18
|
+
oauthAuthorize: {
|
19
|
+
name: 'jackson.oauth.authorize',
|
20
|
+
description: 'Number of SAML oauth authorize requests',
|
21
|
+
},
|
22
|
+
oauthToken: {
|
23
|
+
name: 'jackson.oauth.token',
|
24
|
+
description: 'Number of SAML oauth token requests',
|
25
|
+
},
|
26
|
+
oauthUserInfo: {
|
27
|
+
name: 'jackson.oauth.userinfo',
|
28
|
+
description: 'Number of SAML oauth user info requests',
|
29
|
+
},
|
30
|
+
};
|
31
|
+
const createCounter = (action) => {
|
32
|
+
const meter = api_metrics_1.metrics.getMeterProvider().getMeter('jackson');
|
33
|
+
const counter = counters[action];
|
34
|
+
return meter.createCounter(counter.name, {
|
35
|
+
description: counter.description,
|
36
|
+
});
|
37
|
+
};
|
38
|
+
const increment = (action) => {
|
39
|
+
const counter = createCounter(action);
|
40
|
+
counter.add(1, { provider: 'saml' });
|
41
|
+
};
|
42
|
+
exports.increment = increment;
|
package/dist/saml/saml.js
CHANGED
@@ -68,7 +68,7 @@ const request = ({ ssoUrl, entityID, callbackUrl, isPassive = false, forceAuthn
|
|
68
68
|
'@ID': id,
|
69
69
|
'@Version': '2.0',
|
70
70
|
'@IssueInstant': date,
|
71
|
-
'@ProtocolBinding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-
|
71
|
+
'@ProtocolBinding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
|
72
72
|
'@Destination': ssoUrl,
|
73
73
|
'saml:Issuer': {
|
74
74
|
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
package/dist/typings.d.ts
CHANGED
@@ -3,6 +3,8 @@ export declare type IdPConfig = {
|
|
3
3
|
redirectUrl: string;
|
4
4
|
tenant: string;
|
5
5
|
product: string;
|
6
|
+
name: string;
|
7
|
+
description: string;
|
6
8
|
rawMetadata?: string;
|
7
9
|
encodedRawMetadata?: string;
|
8
10
|
};
|
@@ -13,11 +15,12 @@ export interface OAuth {
|
|
13
15
|
}
|
14
16
|
export interface IAPIController {
|
15
17
|
config(body: IdPConfig): Promise<OAuth>;
|
18
|
+
updateConfig(body: any): Promise<void>;
|
16
19
|
getConfig(body: {
|
17
20
|
clientID?: string;
|
18
21
|
tenant?: string;
|
19
22
|
product?: string;
|
20
|
-
}): Promise<
|
23
|
+
}): Promise<any>;
|
21
24
|
deleteConfig(body: {
|
22
25
|
clientID?: string;
|
23
26
|
clientSecret?: string;
|
@@ -27,7 +30,8 @@ export interface IAPIController {
|
|
27
30
|
}
|
28
31
|
export interface IOAuthController {
|
29
32
|
authorize(body: OAuthReqBody): Promise<{
|
30
|
-
redirect_url
|
33
|
+
redirect_url?: string;
|
34
|
+
authorize_form?: string;
|
31
35
|
}>;
|
32
36
|
samlResponse(body: SAMLResponsePayload): Promise<{
|
33
37
|
redirect_url: string;
|
@@ -35,6 +39,9 @@ export interface IOAuthController {
|
|
35
39
|
token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
|
36
40
|
userInfo(token: string): Promise<Profile>;
|
37
41
|
}
|
42
|
+
export interface IAdminController {
|
43
|
+
getAllConfig(): any;
|
44
|
+
}
|
38
45
|
export interface OAuthReqBody {
|
39
46
|
response_type: 'code';
|
40
47
|
client_id: string;
|
@@ -73,12 +80,14 @@ export interface Index {
|
|
73
80
|
value: string;
|
74
81
|
}
|
75
82
|
export interface DatabaseDriver {
|
83
|
+
getAll(namespace: string): Promise<unknown[]>;
|
76
84
|
get(namespace: string, key: string): Promise<any>;
|
77
85
|
put(namespace: string, key: string, val: any, ttl: number, ...indexes: Index[]): Promise<any>;
|
78
86
|
delete(namespace: string, key: string): Promise<any>;
|
79
87
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
80
88
|
}
|
81
89
|
export interface Storable {
|
90
|
+
getAll(): Promise<unknown[]>;
|
82
91
|
get(key: string): Promise<any>;
|
83
92
|
put(key: string, val: any, ...indexes: Index[]): Promise<any>;
|
84
93
|
delete(key: string): Promise<any>;
|
@@ -123,4 +132,5 @@ export interface JacksonOption {
|
|
123
132
|
preLoadedConfig?: string;
|
124
133
|
idpEnabled?: boolean;
|
125
134
|
db: DatabaseOption;
|
135
|
+
clientSecretVerifier?: string;
|
126
136
|
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import {MigrationInterface, QueryRunner} from "typeorm";
|
2
|
+
|
3
|
+
export class createdAt1644332636666 implements MigrationInterface {
|
4
|
+
name = 'createdAt1644332636666'
|
5
|
+
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
7
|
+
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`createdAt\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP()`);
|
8
|
+
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`modifiedAt\` timestamp NULL`);
|
9
|
+
}
|
10
|
+
|
11
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
12
|
+
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`modifiedAt\``);
|
13
|
+
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`createdAt\``);
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import {MigrationInterface, QueryRunner} from "typeorm";
|
2
|
+
|
3
|
+
export class createdAt1644332641078 implements MigrationInterface {
|
4
|
+
name = 'createdAt1644332641078'
|
5
|
+
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
7
|
+
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`createdAt\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP`);
|
8
|
+
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`modifiedAt\` timestamp NULL`);
|
9
|
+
}
|
10
|
+
|
11
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
12
|
+
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`modifiedAt\``);
|
13
|
+
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`createdAt\``);
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import {MigrationInterface, QueryRunner} from "typeorm";
|
2
|
+
|
3
|
+
export class createdAt1644332647279 implements MigrationInterface {
|
4
|
+
name = 'createdAt1644332647279'
|
5
|
+
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
7
|
+
await queryRunner.query(`ALTER TABLE "jackson_store" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT now()`);
|
8
|
+
await queryRunner.query(`ALTER TABLE "jackson_store" ADD "modifiedAt" TIMESTAMP`);
|
9
|
+
}
|
10
|
+
|
11
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
12
|
+
await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "modifiedAt"`);
|
13
|
+
await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "createdAt"`);
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "0.3.
|
3
|
+
"version": "0.3.8-beta.763",
|
4
4
|
"description": "SAML 2.0 service",
|
5
5
|
"keywords": [
|
6
6
|
"SAML 2.0"
|
@@ -18,9 +18,9 @@
|
|
18
18
|
],
|
19
19
|
"scripts": {
|
20
20
|
"build": "tsc -p tsconfig.build.json",
|
21
|
-
"db:migration:generate:postgres": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n
|
22
|
-
"db:migration:generate:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n
|
23
|
-
"db:migration:generate:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n
|
21
|
+
"db:migration:generate:postgres": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n createdAt",
|
22
|
+
"db:migration:generate:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n createdAt",
|
23
|
+
"db:migration:generate:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n createdAt",
|
24
24
|
"db:migration:run:postgres": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run",
|
25
25
|
"db:migration:run:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run",
|
26
26
|
"db:migration:run:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run",
|
@@ -37,10 +37,9 @@
|
|
37
37
|
},
|
38
38
|
"dependencies": {
|
39
39
|
"@boxyhq/saml20": "0.2.0",
|
40
|
+
"@opentelemetry/api-metrics": "0.27.0",
|
40
41
|
"@peculiar/webcrypto": "1.2.3",
|
41
42
|
"@peculiar/x509": "1.6.1",
|
42
|
-
"cors": "2.8.5",
|
43
|
-
"express": "4.17.2",
|
44
43
|
"mongodb": "4.3.1",
|
45
44
|
"mysql2": "2.3.3",
|
46
45
|
"pg": "8.7.3",
|
@@ -55,7 +54,6 @@
|
|
55
54
|
"xmlbuilder": "15.1.1"
|
56
55
|
},
|
57
56
|
"devDependencies": {
|
58
|
-
"@types/express": "4.17.13",
|
59
57
|
"@types/node": "17.0.17",
|
60
58
|
"@types/sinon": "10.0.11",
|
61
59
|
"@types/tap": "15.0.5",
|