@boxyhq/saml-jackson 0.3.7 → 0.3.8-beta.760
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/api.js +4 -0
- 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/index.d.ts +1 -1
- package/dist/index.js +1 -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 +3 -1
- package/package.json +2 -4
package/dist/controller/api.js
CHANGED
@@ -34,6 +34,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
34
|
exports.APIController = void 0;
|
35
35
|
const crypto_1 = __importDefault(require("crypto"));
|
36
36
|
const dbutils = __importStar(require("../db/utils"));
|
37
|
+
const metrics = __importStar(require("../opentelemetry/metrics"));
|
37
38
|
const saml_1 = __importDefault(require("../saml/saml"));
|
38
39
|
const x509_1 = __importDefault(require("../saml/x509"));
|
39
40
|
const error_1 = require("./error");
|
@@ -124,6 +125,7 @@ class APIController {
|
|
124
125
|
config(body) {
|
125
126
|
return __awaiter(this, void 0, void 0, function* () {
|
126
127
|
const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
|
128
|
+
metrics.increment('createConfig');
|
127
129
|
this._validateIdPConfig(body);
|
128
130
|
let metaData = rawMetadata;
|
129
131
|
if (encodedRawMetadata) {
|
@@ -214,6 +216,7 @@ class APIController {
|
|
214
216
|
getConfig(body) {
|
215
217
|
return __awaiter(this, void 0, void 0, function* () {
|
216
218
|
const { clientID, tenant, product } = body;
|
219
|
+
metrics.increment('getConfig');
|
217
220
|
if (clientID) {
|
218
221
|
const samlConfig = yield this.configStore.get(clientID);
|
219
222
|
return samlConfig ? { provider: samlConfig.idpMetadata.provider } : {};
|
@@ -271,6 +274,7 @@ class APIController {
|
|
271
274
|
deleteConfig(body) {
|
272
275
|
return __awaiter(this, void 0, void 0, function* () {
|
273
276
|
const { clientID, clientSecret, tenant, product } = body;
|
277
|
+
metrics.increment('deleteConfig');
|
274
278
|
if (clientID && clientSecret) {
|
275
279
|
const samlConfig = yield this.configStore.get(clientID);
|
276
280
|
if (!samlConfig) {
|
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/index.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import { JacksonOption } from './typings';
|
2
1
|
import { APIController } from './controller/api';
|
3
2
|
import { OAuthController } from './controller/oauth';
|
3
|
+
import { JacksonOption } from './typings';
|
4
4
|
export declare const controllers: (opts: JacksonOption) => Promise<{
|
5
5
|
apiController: APIController;
|
6
6
|
oauthController: OAuthController;
|
package/dist/index.js
CHANGED
@@ -44,6 +44,7 @@ const defaultOpts = (opts) => {
|
|
44
44
|
newOpts.db.type = newOpts.db.type || 'postgres'; // Only needed if DB_ENGINE is sql.
|
45
45
|
newOpts.db.ttl = (newOpts.db.ttl || 300) * 1; // TTL for the code, session and token stores (in seconds)
|
46
46
|
newOpts.db.cleanupLimit = (newOpts.db.cleanupLimit || 1000) * 1; // Limit cleanup of TTL entries to this many items at a time
|
47
|
+
newOpts.clientSecretVerifier = newOpts.clientSecretVerifier || 'dummy';
|
47
48
|
return newOpts;
|
48
49
|
};
|
49
50
|
const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
@@ -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
@@ -27,7 +27,8 @@ export interface IAPIController {
|
|
27
27
|
}
|
28
28
|
export interface IOAuthController {
|
29
29
|
authorize(body: OAuthReqBody): Promise<{
|
30
|
-
redirect_url
|
30
|
+
redirect_url?: string;
|
31
|
+
authorize_form?: string;
|
31
32
|
}>;
|
32
33
|
samlResponse(body: SAMLResponsePayload): Promise<{
|
33
34
|
redirect_url: string;
|
@@ -123,4 +124,5 @@ export interface JacksonOption {
|
|
123
124
|
preLoadedConfig?: string;
|
124
125
|
idpEnabled?: boolean;
|
125
126
|
db: DatabaseOption;
|
127
|
+
clientSecretVerifier?: string;
|
126
128
|
}
|
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.760",
|
4
4
|
"description": "SAML 2.0 service",
|
5
5
|
"keywords": [
|
6
6
|
"SAML 2.0"
|
@@ -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",
|