@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.
@@ -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) {
@@ -14,6 +14,7 @@ export declare class OAuthController implements IOAuthController {
14
14
  });
15
15
  authorize(body: OAuthReqBody): Promise<{
16
16
  redirect_url: string;
17
+ authorize_form: string;
17
18
  }>;
18
19
  samlResponse(body: SAMLResponsePayload): Promise<{
19
20
  redirect_url: string;
@@ -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
- // deepak: When supporting HTTP-POST skip deflate
137
- const samlReqEnc = yield deflateRawAsync(samlReq.request);
138
- const redirectUrl = redirect.success(samlConfig.idpMetadata.sso.redirectUrl, {
139
- RelayState: relayStatePrefix + sessionId,
140
- SAMLRequest: Buffer.from(samlReqEnc).toString('base64'),
141
- });
142
- return { redirect_url: redirectUrl };
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
  }
@@ -2,3 +2,4 @@ export declare enum IndexNames {
2
2
  EntityID = "entityID",
3
3
  TenantProduct = "tenantProduct"
4
4
  }
5
+ export declare const createAuthorizeForm: (relayState: string, samlReqEnc: string, postUrl: string) => string;
@@ -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,2 @@
1
+ declare const increment: (action: string) => void;
2
+ export { increment };
@@ -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-Redirect',
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: string;
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.7",
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",