@boxyhq/saml-jackson 1.2.1 → 1.2.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.
@@ -179,6 +179,7 @@ class APIController {
179
179
  config(body) {
180
180
  return __awaiter(this, void 0, void 0, function* () {
181
181
  const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, name, description, } = body;
182
+ const forceAuthn = body.forceAuthn == 'true' || body.forceAuthn == true;
182
183
  metrics.increment('createConfig');
183
184
  this._validateIdPConfig(body);
184
185
  const redirectUrlList = extractRedirectUrls(redirectUrl);
@@ -205,7 +206,7 @@ class APIController {
205
206
  }
206
207
  const certs = yield x509_1.default.generate();
207
208
  if (!certs) {
208
- throw new Error('Error generating x59 certs');
209
+ throw new Error('Error generating x509 certs');
209
210
  }
210
211
  const record = {
211
212
  idpMetadata,
@@ -218,6 +219,7 @@ class APIController {
218
219
  clientID,
219
220
  clientSecret,
220
221
  certs,
222
+ forceAuthn,
221
223
  };
222
224
  yield this.configStore.put(clientID, record, {
223
225
  // secondary index on entityID
@@ -301,7 +303,7 @@ class APIController {
301
303
  return __awaiter(this, void 0, void 0, function* () {
302
304
  const { encodedRawMetadata, // could be empty
303
305
  rawMetadata, // could be empty
304
- defaultRedirectUrl, redirectUrl, name, description } = body, clientInfo = __rest(body, ["encodedRawMetadata", "rawMetadata", "defaultRedirectUrl", "redirectUrl", "name", "description"]);
306
+ defaultRedirectUrl, redirectUrl, name, description, forceAuthn = false } = body, clientInfo = __rest(body, ["encodedRawMetadata", "rawMetadata", "defaultRedirectUrl", "redirectUrl", "name", "description", "forceAuthn"]);
305
307
  if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID)) {
306
308
  throw new error_1.JacksonError('Please provide clientID', 400);
307
309
  }
@@ -338,7 +340,7 @@ class APIController {
338
340
  throw new error_1.JacksonError('Tenant/Product config mismatch with IdP metadata', 400);
339
341
  }
340
342
  }
341
- const record = 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: redirectUrlList ? redirectUrlList : _currentConfig.redirectUrl });
343
+ const record = 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: redirectUrlList ? redirectUrlList : _currentConfig.redirectUrl, forceAuthn });
342
344
  yield this.configStore.put(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID, record, {
343
345
  // secondary index on entityID
344
346
  name: utils_1.IndexNames.EntityID,
@@ -49,6 +49,7 @@ const allowed = __importStar(require("./oauth/allowed"));
49
49
  const codeVerifier = __importStar(require("./oauth/code-verifier"));
50
50
  const redirect = __importStar(require("./oauth/redirect"));
51
51
  const utils_1 = require("./utils");
52
+ const x509_1 = __importDefault(require("../saml/x509"));
52
53
  const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
53
54
  const validateResponse = (rawResponse, validateOpts) => __awaiter(void 0, void 0, void 0, function* () {
54
55
  const profile = yield saml20_1.default.validate(rawResponse, validateOpts);
@@ -134,7 +135,7 @@ class OAuthController {
134
135
  return __awaiter(this, void 0, void 0, function* () {
135
136
  const { response_type = 'code', client_id, redirect_uri, state, tenant, product, access_type, resource, scope, nonce, code_challenge, code_challenge_method = '',
136
137
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
137
- provider = 'saml', idp_hint, } = body;
138
+ provider = 'saml', idp_hint, prompt, } = body;
138
139
  let requestedTenant = tenant;
139
140
  let requestedProduct = product;
140
141
  metrics.increment('oauthAuthorize');
@@ -294,12 +295,37 @@ class OAuthController {
294
295
  };
295
296
  }
296
297
  try {
298
+ const { validTo } = new crypto_1.default.X509Certificate(samlConfig.certs.publicKey);
299
+ const isValidExpiry = validTo != 'Bad time value' && new Date(validTo) > new Date();
300
+ if (!isValidExpiry) {
301
+ const certs = yield x509_1.default.generate();
302
+ samlConfig.certs = certs;
303
+ if (certs) {
304
+ yield this.configStore.put(samlConfig.clientID, samlConfig, {
305
+ // secondary index on entityID
306
+ name: utils_1.IndexNames.EntityID,
307
+ value: samlConfig.idpMetadata.entityID,
308
+ }, {
309
+ // secondary index on tenant + product
310
+ name: utils_1.IndexNames.TenantProduct,
311
+ value: dbutils.keyFromParts(samlConfig.tenant, samlConfig.product),
312
+ });
313
+ }
314
+ else {
315
+ throw new Error('Error generating x509 certs');
316
+ }
317
+ }
318
+ // We will get undefined or Space delimited, case sensitive list of ASCII string values in prompt
319
+ // If login is one of the value in prompt we want to enable forceAuthn
320
+ // Else use the saml config forceAuthn value
321
+ const promptOptions = prompt ? prompt.split(' ').filter((p) => p === 'login') : [];
297
322
  const samlReq = saml20_1.default.request({
298
323
  ssoUrl,
299
324
  entityID: this.opts.samlAudience,
300
325
  callbackUrl: this.opts.externalUrl + this.opts.samlPath,
301
326
  signingKey: samlConfig.certs.privateKey,
302
327
  publicKey: samlConfig.certs.publicKey,
328
+ forceAuthn: promptOptions.length > 0 ? true : !!samlConfig.forceAuthn,
303
329
  });
304
330
  const sessionId = crypto_1.default.randomBytes(16).toString('hex');
305
331
  const requested = { client_id, state, redirect_uri };
@@ -550,7 +576,7 @@ class OAuthController {
550
576
  * expires_in: 300
551
577
  */
552
578
  token(body) {
553
- var _a, _b, _c;
579
+ var _a, _b, _c, _d, _e;
554
580
  return __awaiter(this, void 0, void 0, function* () {
555
581
  const { client_id, client_secret, code_verifier, code, grant_type = 'authorization_code', redirect_uri, } = body;
556
582
  metrics.increment('oauthToken');
@@ -590,6 +616,9 @@ class OAuthController {
590
616
  }
591
617
  }
592
618
  else {
619
+ if (sp.tenant !== ((_b = codeVal.requested) === null || _b === void 0 ? void 0 : _b.tenant) || sp.product !== ((_c = codeVal.requested) === null || _c === void 0 ? void 0 : _c.product)) {
620
+ throw new error_1.JacksonError('Invalid tenant or product', 401);
621
+ }
593
622
  // encoded client_id, verify client_secret
594
623
  if (client_secret !== this.opts.clientSecretVerifier) {
595
624
  throw new error_1.JacksonError('Invalid client_secret', 401);
@@ -608,8 +637,8 @@ class OAuthController {
608
637
  // store details against a token
609
638
  const token = crypto_1.default.randomBytes(20).toString('hex');
610
639
  const tokenVal = Object.assign(Object.assign({}, codeVal.profile), { requested: codeVal.requested });
611
- const requestedOIDCFlow = !!((_b = codeVal.requested) === null || _b === void 0 ? void 0 : _b.oidc);
612
- const requestHasNonce = !!((_c = codeVal.requested) === null || _c === void 0 ? void 0 : _c.nonce);
640
+ const requestedOIDCFlow = !!((_d = codeVal.requested) === null || _d === void 0 ? void 0 : _d.oidc);
641
+ const requestHasNonce = !!((_e = codeVal.requested) === null || _e === void 0 ? void 0 : _e.nonce);
613
642
  if (requestedOIDCFlow) {
614
643
  const { jwtSigningKeys, jwsAlg } = this.opts.openid;
615
644
  if (!jwtSigningKeys || !(0, utils_1.isJWSKeyPairLoaded)(jwtSigningKeys)) {
@@ -143,6 +143,10 @@ class DirectoryUsers {
143
143
  this.users.setTenantAndProduct(directory.tenant, directory.product);
144
144
  // Get the user
145
145
  const { data: user } = userId ? yield this.users.get(userId) : { data: null };
146
+ // Delete password if exists in the body
147
+ if (body && 'password' in body) {
148
+ delete body['password'];
149
+ }
146
150
  if (user) {
147
151
  switch (method) {
148
152
  case 'GET':
@@ -1,7 +1,7 @@
1
1
  declare const _default: {
2
- generate: () => Promise<{
3
- publicKey: string;
4
- privateKey: string;
5
- } | undefined>;
2
+ generate: () => {
3
+ publicKey: any;
4
+ privateKey: any;
5
+ };
6
6
  };
7
7
  export default _default;
package/dist/saml/x509.js CHANGED
@@ -22,50 +22,46 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
- return new (P || (P = Promise))(function (resolve, reject) {
28
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
- step((generator = generator.apply(thisArg, _arguments || [])).next());
32
- });
33
- };
34
25
  Object.defineProperty(exports, "__esModule", { value: true });
35
- const x509 = __importStar(require("@peculiar/x509"));
36
- const webcrypto_1 = require("@peculiar/webcrypto");
37
- const crypto = new webcrypto_1.Crypto();
38
- x509.cryptoProvider.set(crypto);
39
- const alg = {
40
- name: 'RSASSA-PKCS1-v1_5',
41
- hash: 'SHA-256',
42
- publicExponent: new Uint8Array([1, 0, 1]),
43
- modulusLength: 2048,
26
+ const forge = __importStar(require("node-forge"));
27
+ const pki = forge.pki;
28
+ const generate = () => {
29
+ const today = new Date();
30
+ const keys = pki.rsa.generateKeyPair(2048);
31
+ const cert = pki.createCertificate();
32
+ cert.publicKey = keys.publicKey;
33
+ cert.serialNumber = '01';
34
+ cert.validity.notBefore = new Date();
35
+ cert.validity.notAfter = new Date(today.setFullYear(today.getFullYear() + 10));
36
+ const attrs = [
37
+ {
38
+ name: 'commonName',
39
+ value: 'BoxyHQ Jackson',
40
+ },
41
+ ];
42
+ cert.setSubject(attrs);
43
+ cert.setIssuer(attrs);
44
+ cert.setExtensions([
45
+ {
46
+ name: 'basicConstraints',
47
+ cA: false,
48
+ },
49
+ {
50
+ name: 'keyUsage',
51
+ keyCertSign: false,
52
+ digitalSignature: true,
53
+ nonRepudiation: false,
54
+ keyEncipherment: false,
55
+ dataEncipherment: false,
56
+ },
57
+ ]);
58
+ // self-sign certificate
59
+ cert.sign(keys.privateKey, forge.md.sha256.create());
60
+ return {
61
+ publicKey: pki.certificateToPem(cert),
62
+ privateKey: pki.privateKeyToPem(keys.privateKey),
63
+ };
44
64
  };
45
- const generate = () => __awaiter(void 0, void 0, void 0, function* () {
46
- const keys = yield crypto.subtle.generateKey(alg, true, ['sign', 'verify']);
47
- const extensions = [new x509.BasicConstraintsExtension(false, undefined, true)];
48
- extensions.push(new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true));
49
- if (keys.publicKey) {
50
- extensions.push(yield x509.SubjectKeyIdentifierExtension.create(keys.publicKey));
51
- }
52
- const cert = yield x509.X509CertificateGenerator.createSelfSigned({
53
- serialNumber: '01',
54
- name: 'CN=BoxyHQ Jackson',
55
- notBefore: new Date(),
56
- notAfter: new Date('3021/01/01'),
57
- signingAlgorithm: alg,
58
- keys: keys,
59
- extensions,
60
- });
61
- if (keys.privateKey) {
62
- const pkcs8 = yield crypto.subtle.exportKey('pkcs8', keys.privateKey);
63
- return {
64
- publicKey: cert.toString('pem'),
65
- privateKey: x509.PemConverter.encode(pkcs8, 'private key'),
66
- };
67
- }
68
- });
69
65
  exports.default = {
70
66
  generate,
71
67
  };
package/dist/typings.d.ts CHANGED
@@ -8,6 +8,7 @@ export declare type IdPConfig = {
8
8
  description?: string;
9
9
  rawMetadata?: string;
10
10
  encodedRawMetadata?: string;
11
+ forceAuthn: boolean | string;
11
12
  };
12
13
  export interface IAPIController {
13
14
  config(body: IdPConfig): Promise<any>;
@@ -84,6 +85,7 @@ export interface OAuthReqBody {
84
85
  code_challenge_method: 'plain' | 'S256' | '';
85
86
  provider: 'saml';
86
87
  idp_hint?: string;
88
+ prompt?: string;
87
89
  }
88
90
  export interface SAMLResponsePayload {
89
91
  SAMLResponse: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "SAML Jackson library",
5
5
  "keywords": [
6
6
  "SAML 2.0"
@@ -38,40 +38,39 @@
38
38
  "statements": 70
39
39
  },
40
40
  "dependencies": {
41
- "@boxyhq/saml20": "1.0.6",
41
+ "@boxyhq/saml20": "1.0.7",
42
42
  "@opentelemetry/api": "1.0.4",
43
43
  "@opentelemetry/api-metrics": "0.27.0",
44
- "@peculiar/webcrypto": "1.4.0",
45
44
  "axios": "^0.27.2",
46
- "@peculiar/x509": "1.8.3",
47
- "jose": "4.9.2",
45
+ "jose": "4.9.3",
48
46
  "marked": "4.1.0",
49
- "mongodb": "4.9.1",
47
+ "mongodb": "4.10.0",
50
48
  "mysql2": "2.3.3",
49
+ "node-forge": "1.3.1",
51
50
  "pg": "8.8.0",
52
51
  "redis": "4.3.1",
53
52
  "reflect-metadata": "0.1.13",
54
53
  "ripemd160": "2.0.2",
55
- "typeorm": "0.3.9",
54
+ "typeorm": "0.3.10",
56
55
  "xml2js": "0.4.23",
57
56
  "xmlbuilder": "15.1.1"
58
57
  },
59
58
  "devDependencies": {
60
- "@faker-js/faker": "7.2.0",
61
- "@types/node": "18.7.16",
59
+ "@faker-js/faker": "7.5.0",
60
+ "@types/node": "18.7.23",
62
61
  "@types/sinon": "10.0.13",
63
62
  "@types/tap": "15.0.7",
64
- "@typescript-eslint/eslint-plugin": "5.36.2",
65
- "@typescript-eslint/parser": "5.36.2",
63
+ "@typescript-eslint/eslint-plugin": "5.38.1",
64
+ "@typescript-eslint/parser": "5.38.1",
66
65
  "cross-env": "7.0.3",
67
- "eslint": "8.23.0",
66
+ "eslint": "8.24.0",
68
67
  "eslint-config-prettier": "8.5.0",
69
68
  "prettier": "2.7.1",
70
69
  "sinon": "14.0.0",
71
70
  "tap": "16.3.0",
72
71
  "ts-node": "10.9.1",
73
72
  "tsconfig-paths": "4.1.0",
74
- "typescript": "4.8.2"
73
+ "typescript": "4.8.3"
75
74
  },
76
75
  "engines": {
77
76
  "node": ">=14.18.1 <=16.x"