@boxyhq/saml-jackson 1.3.4 → 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -31,10 +31,6 @@ export declare class ConnectionAPIController implements IConnectionAPIController
31
31
  * "description": "SP for hoppscotch.io",
32
32
  * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
33
33
  * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
34
- * "certs": {
35
- * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
36
- * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
37
- * }
38
34
  * }
39
35
  * validationErrorsPost:
40
36
  * description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | redirectUrl is invalid | Exceeded maximum number of allowed redirect urls | defaultRedirectUrl is invalid | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters | Strategy: xxxx not supported | Please provide the clientId from OpenID Provider | Please provide the clientSecret from OpenID Provider | Please provide the discoveryUrl for the OpenID Provider
@@ -362,9 +358,6 @@ export declare class ConnectionAPIController implements IConnectionAPIController
362
358
  * idpMetadata:
363
359
  * type: object
364
360
  * description: SAML IdP metadata
365
- * certs:
366
- * type: object
367
- * description: Certs generated for SAML connection
368
361
  * oidcProvider:
369
362
  * type: object
370
363
  * description: OIDC IdP metadata
@@ -435,10 +428,6 @@ export declare class ConnectionAPIController implements IConnectionAPIController
435
428
  * "description": "SP for hoppscotch.io",
436
429
  * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
437
430
  * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
438
- * "certs": {
439
- * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
440
- * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
441
- * }
442
431
  * }
443
432
  * '400':
444
433
  * $ref: '#/responses/400Get'
@@ -72,10 +72,6 @@ class ConnectionAPIController {
72
72
  * "description": "SP for hoppscotch.io",
73
73
  * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
74
74
  * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
75
- * "certs": {
76
- * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
77
- * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
78
- * }
79
75
  * }
80
76
  * validationErrorsPost:
81
77
  * description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | redirectUrl is invalid | Exceeded maximum number of allowed redirect urls | defaultRedirectUrl is invalid | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters | Strategy: xxxx not supported | Please provide the clientId from OpenID Provider | Please provide the clientSecret from OpenID Provider | Please provide the discoveryUrl for the OpenID Provider
@@ -431,9 +427,6 @@ class ConnectionAPIController {
431
427
  * idpMetadata:
432
428
  * type: object
433
429
  * description: SAML IdP metadata
434
- * certs:
435
- * type: object
436
- * description: Certs generated for SAML connection
437
430
  * oidcProvider:
438
431
  * type: object
439
432
  * description: OIDC IdP metadata
@@ -549,10 +542,6 @@ class ConnectionAPIController {
549
542
  * "description": "SP for hoppscotch.io",
550
543
  * "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
551
544
  * "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
552
- * "certs": {
553
- * "publicKey": "-----BEGIN CERTIFICATE-----.......-----END CERTIFICATE-----",
554
- * "privateKey": "-----BEGIN PRIVATE KEY-----......-----END PRIVATE KEY-----"
555
- * }
556
545
  * }
557
546
  * '400':
558
547
  * $ref: '#/responses/400Get'
@@ -50,7 +50,6 @@ const crypto_1 = __importDefault(require("crypto"));
50
50
  const dbutils = __importStar(require("../../db/utils"));
51
51
  const utils_1 = require("../utils");
52
52
  const saml20_1 = __importDefault(require("@boxyhq/saml20"));
53
- const x509_1 = __importDefault(require("../../saml/x509"));
54
53
  const error_1 = require("../error");
55
54
  const saml = {
56
55
  create: (body, connectionStore) => __awaiter(void 0, void 0, void 0, function* () {
@@ -86,12 +85,7 @@ const saml = {
86
85
  }
87
86
  idpMetadata.provider = providerName ? providerName : 'Unknown';
88
87
  record.clientID = dbutils.keyDigest(dbutils.keyFromParts(tenant, product, idpMetadata.entityID));
89
- const certs = yield x509_1.default.generate();
90
- if (!certs) {
91
- throw new error_1.JacksonError('Error generating x509 certs');
92
- }
93
88
  record.idpMetadata = idpMetadata;
94
- record.certs = certs;
95
89
  const exists = yield connectionStore.get(record.clientID);
96
90
  if (exists) {
97
91
  connectionClientSecret = exists.clientSecret;
@@ -46,6 +46,7 @@ const saml20_1 = __importDefault(require("@boxyhq/saml20"));
46
46
  const error_1 = require("./error");
47
47
  const redirect = __importStar(require("./oauth/redirect"));
48
48
  const utils_1 = require("./utils");
49
+ const x509_1 = require("../saml/x509");
49
50
  const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
50
51
  const relayStatePrefix = 'boxyhq_jackson_';
51
52
  const logoutXPath = "/*[local-name(.)='LogoutRequest']";
@@ -72,7 +73,8 @@ class LogoutController {
72
73
  if (!samlConnection) {
73
74
  throw new error_1.JacksonError('SAML connection not found.', 403);
74
75
  }
75
- const { idpMetadata: { slo, provider }, certs: { privateKey, publicKey }, } = samlConnection;
76
+ const { idpMetadata: { slo, provider }, } = samlConnection;
77
+ const { privateKey, publicKey } = yield (0, x509_1.getDefaultCertificate)();
76
78
  if ('redirectUrl' in slo === false && 'postUrl' in slo === false) {
77
79
  throw new error_1.JacksonError(`${provider} doesn't support SLO or disabled by IdP.`, 400);
78
80
  }
@@ -50,7 +50,7 @@ const allowed = __importStar(require("./oauth/allowed"));
50
50
  const codeVerifier = __importStar(require("./oauth/code-verifier"));
51
51
  const redirect = __importStar(require("./oauth/redirect"));
52
52
  const utils_1 = require("./utils");
53
- const x509_1 = __importDefault(require("../saml/x509"));
53
+ const x509_1 = require("../saml/x509");
54
54
  const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
55
55
  const validateSAMLResponse = (rawResponse, validateOpts) => __awaiter(void 0, void 0, void 0, function* () {
56
56
  const profile = yield saml20_1.default.validate(rawResponse, validateOpts);
@@ -312,27 +312,8 @@ class OAuthController {
312
312
  }),
313
313
  };
314
314
  }
315
+ const cert = yield (0, x509_1.getDefaultCertificate)();
315
316
  try {
316
- const { validTo } = new crypto_1.default.X509Certificate(connection.certs.publicKey);
317
- const isValidExpiry = validTo != 'Bad time value' && new Date(validTo) > new Date();
318
- if (!isValidExpiry) {
319
- const certs = yield x509_1.default.generate();
320
- connection.certs = certs;
321
- if (certs) {
322
- yield this.connectionStore.put(connection.clientID, connection, {
323
- // secondary index on entityID
324
- name: utils_1.IndexNames.EntityID,
325
- value: connection.idpMetadata.entityID,
326
- }, {
327
- // secondary index on tenant + product
328
- name: utils_1.IndexNames.TenantProduct,
329
- value: dbutils.keyFromParts(connection.tenant, connection.product),
330
- });
331
- }
332
- else {
333
- throw new Error('Error generating x509 certs');
334
- }
335
- }
336
317
  // We will get undefined or Space delimited, case sensitive list of ASCII string values in prompt
337
318
  // If login is one of the value in prompt we want to enable forceAuthn
338
319
  // Else use the saml connection forceAuthn value
@@ -341,8 +322,8 @@ class OAuthController {
341
322
  ssoUrl,
342
323
  entityID: this.opts.samlAudience,
343
324
  callbackUrl: this.opts.externalUrl + this.opts.samlPath,
344
- signingKey: connection.certs.privateKey,
345
- publicKey: connection.certs.publicKey,
325
+ signingKey: cert.privateKey,
326
+ publicKey: cert.publicKey,
346
327
  forceAuthn: promptOptions.length > 0 ? true : !!connection.forceAuthn,
347
328
  });
348
329
  }
@@ -382,7 +363,7 @@ class OAuthController {
382
363
  oidcCodeVerifier = openid_client_1.generators.codeVerifier();
383
364
  const code_challenge = openid_client_1.generators.codeChallenge(oidcCodeVerifier);
384
365
  ssoUrl = oidcClient.authorizationUrl({
385
- scope: [...requestedScopes, 'openid', 'email', 'profile', 'groups']
366
+ scope: [...requestedScopes, 'openid', 'email', 'profile']
386
367
  .filter((value, index, self) => self.indexOf(value) === index) // filter out duplicates
387
368
  .join(' '),
388
369
  code_challenge,
@@ -544,10 +525,11 @@ class OAuthController {
544
525
  if (!samlConnection) {
545
526
  throw new error_1.JacksonError('SAML connection not found.', 403);
546
527
  }
528
+ const { privateKey } = yield (0, x509_1.getDefaultCertificate)();
547
529
  const validateOpts = {
548
530
  thumbprint: samlConnection.idpMetadata.thumbprint,
549
531
  audience: this.opts.samlAudience,
550
- privateKey: samlConnection.certs.privateKey,
532
+ privateKey,
551
533
  };
552
534
  if (session &&
553
535
  session.redirect_uri &&
@@ -1,21 +1,22 @@
1
1
  import type { JacksonOption } from '../typings';
2
2
  export declare class SPSAMLConfig {
3
3
  private opts;
4
- constructor(opts: JacksonOption);
4
+ private getDefaultCertificate;
5
+ constructor(opts: JacksonOption, getDefaultCertificate: any);
5
6
  private get acsUrl();
6
7
  private get entityId();
7
8
  private get responseSigned();
8
9
  private get assertionSignature();
9
10
  private get signatureAlgorithm();
10
- private get assertionEncryption();
11
- get(): {
11
+ get(): Promise<{
12
12
  acsUrl: string;
13
13
  entityId: string;
14
14
  response: string;
15
15
  assertionSignature: string;
16
16
  signatureAlgorithm: string;
17
- assertionEncryption: string;
18
- };
17
+ publicKey: string;
18
+ publicKeyString: string;
19
+ }>;
19
20
  toMarkdown(): string;
20
21
  toHTML(): string;
21
22
  }
@@ -1,11 +1,25 @@
1
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
2
14
  Object.defineProperty(exports, "__esModule", { value: true });
3
15
  exports.SPSAMLConfig = void 0;
4
16
  const marked_1 = require("marked");
17
+ const saml20_1 = __importDefault(require("@boxyhq/saml20"));
5
18
  // Service Provider SAML Configuration
6
19
  class SPSAMLConfig {
7
- constructor(opts) {
20
+ constructor(opts, getDefaultCertificate) {
8
21
  this.opts = opts;
22
+ this.getDefaultCertificate = getDefaultCertificate;
9
23
  }
10
24
  get acsUrl() {
11
25
  return `${this.opts.externalUrl}${this.opts.samlPath}`;
@@ -22,18 +36,19 @@ class SPSAMLConfig {
22
36
  get signatureAlgorithm() {
23
37
  return 'RSA-SHA256';
24
38
  }
25
- get assertionEncryption() {
26
- return 'Unencrypted';
27
- }
28
39
  get() {
29
- return {
30
- acsUrl: this.acsUrl,
31
- entityId: this.entityId,
32
- response: this.responseSigned,
33
- assertionSignature: this.assertionSignature,
34
- signatureAlgorithm: this.signatureAlgorithm,
35
- assertionEncryption: this.assertionEncryption,
36
- };
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ const cert = yield this.getDefaultCertificate();
42
+ return {
43
+ acsUrl: this.acsUrl,
44
+ entityId: this.entityId,
45
+ response: this.responseSigned,
46
+ assertionSignature: this.assertionSignature,
47
+ signatureAlgorithm: this.signatureAlgorithm,
48
+ publicKey: cert.publicKey,
49
+ publicKeyString: saml20_1.default.stripCertHeaderAndFooter(cert.publicKey),
50
+ };
51
+ });
37
52
  }
38
53
  toMarkdown() {
39
54
  return markdownTemplate
@@ -41,8 +56,7 @@ class SPSAMLConfig {
41
56
  .replace('{{entityId}}', this.entityId)
42
57
  .replace('{{responseSigned}}', this.responseSigned)
43
58
  .replace('{{assertionSignature}}', this.assertionSignature)
44
- .replace('{{signatureAlgorithm}}', this.signatureAlgorithm)
45
- .replace('{{assertionEncryption}}', this.assertionEncryption);
59
+ .replace('{{signatureAlgorithm}}', this.signatureAlgorithm);
46
60
  }
47
61
  toHTML() {
48
62
  return marked_1.marked.parse(this.toMarkdown());
@@ -70,5 +84,5 @@ Your Identity Provider (IdP) will ask for the following information while config
70
84
  {{signatureAlgorithm}}
71
85
 
72
86
  **Assertion Encryption** <br />
73
- {{assertionEncryption}}
87
+ If you want to encrypt the assertion, you can download our [public certificate](/.well-known/saml.cer). Otherwise select the 'Unencrypted' option.
74
88
  `;
package/dist/db/db.js CHANGED
@@ -47,6 +47,9 @@ const JacksonTTL_1 = require("./sql/entity/JacksonTTL");
47
47
  const JacksonStore_2 = require("./planetscale/entity/JacksonStore");
48
48
  const JacksonIndex_2 = require("./planetscale/entity/JacksonIndex");
49
49
  const JacksonTTL_2 = require("./planetscale/entity/JacksonTTL");
50
+ const JacksonStore_3 = require("./sql/mssql/entity/JacksonStore");
51
+ const JacksonIndex_3 = require("./sql/mssql/entity/JacksonIndex");
52
+ const JacksonTTL_3 = require("./sql/mssql/entity/JacksonTTL");
50
53
  const decrypt = (res, encryptionKey) => {
51
54
  if (res.iv && res.tag) {
52
55
  return JSON.parse(encrypter.decrypt(res.value, res.iv, res.tag, encryptionKey));
@@ -113,11 +116,20 @@ exports.default = {
113
116
  case 'redis':
114
117
  return new DB(yield redis_1.default.new(options), encryptionKey);
115
118
  case 'sql':
116
- return new DB(yield sql_1.default.new(options, {
117
- JacksonStore: JacksonStore_1.JacksonStore,
118
- JacksonIndex: JacksonIndex_1.JacksonIndex,
119
- JacksonTTL: JacksonTTL_1.JacksonTTL,
120
- }), encryptionKey);
119
+ switch (options.type) {
120
+ case 'mssql':
121
+ return new DB(yield sql_1.default.new(options, {
122
+ JacksonStore: JacksonStore_3.JacksonStore,
123
+ JacksonIndex: JacksonIndex_3.JacksonIndex,
124
+ JacksonTTL: JacksonTTL_3.JacksonTTL,
125
+ }), encryptionKey);
126
+ default:
127
+ return new DB(yield sql_1.default.new(options, {
128
+ JacksonStore: JacksonStore_1.JacksonStore,
129
+ JacksonIndex: JacksonIndex_1.JacksonIndex,
130
+ JacksonTTL: JacksonTTL_1.JacksonTTL,
131
+ }), encryptionKey);
132
+ }
121
133
  case 'planetscale':
122
134
  return new DB(yield sql_1.default.new(options, {
123
135
  JacksonStore: JacksonStore_2.JacksonStore,
package/dist/db/redis.js CHANGED
@@ -54,7 +54,7 @@ class Redis {
54
54
  };
55
55
  }
56
56
  this.client = redis.createClient(opts);
57
- this.client.on('error', (err) => console.log('Redis Client Error', err));
57
+ this.client.on('error', (err) => console.info('Redis Client Error', err));
58
58
  yield this.client.connect();
59
59
  return this;
60
60
  });
@@ -0,0 +1,7 @@
1
+ import { JacksonStore } from './JacksonStore';
2
+ export declare class JacksonIndex {
3
+ id: number;
4
+ key: string;
5
+ storeKey: string;
6
+ store?: JacksonStore;
7
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.JacksonIndex = void 0;
10
+ const JacksonStore_1 = require("./JacksonStore");
11
+ const typeorm_1 = require("typeorm");
12
+ let JacksonIndex = class JacksonIndex {
13
+ };
14
+ __decorate([
15
+ (0, typeorm_1.PrimaryGeneratedColumn)()
16
+ ], JacksonIndex.prototype, "id", void 0);
17
+ __decorate([
18
+ (0, typeorm_1.Index)('_jackson_index_key'),
19
+ (0, typeorm_1.Column)({
20
+ type: 'varchar',
21
+ length: 1500,
22
+ })
23
+ ], JacksonIndex.prototype, "key", void 0);
24
+ __decorate([
25
+ (0, typeorm_1.Column)({
26
+ type: 'varchar',
27
+ length: 1500,
28
+ })
29
+ ], JacksonIndex.prototype, "storeKey", void 0);
30
+ __decorate([
31
+ (0, typeorm_1.ManyToOne)(() => JacksonStore_1.JacksonStore, undefined, {
32
+ //inverseSide: 'in',
33
+ eager: true,
34
+ onDelete: 'CASCADE',
35
+ })
36
+ ], JacksonIndex.prototype, "store", void 0);
37
+ JacksonIndex = __decorate([
38
+ (0, typeorm_1.Index)('_jackson_index_key_store', ['key', 'storeKey']),
39
+ (0, typeorm_1.Entity)()
40
+ ], JacksonIndex);
41
+ exports.JacksonIndex = JacksonIndex;
@@ -0,0 +1,8 @@
1
+ export declare class JacksonStore {
2
+ key: string;
3
+ value: string;
4
+ iv?: string;
5
+ tag?: string;
6
+ createdAt?: Date;
7
+ modifiedAt?: string;
8
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.JacksonStore = void 0;
10
+ const typeorm_1 = require("typeorm");
11
+ let JacksonStore = class JacksonStore {
12
+ };
13
+ __decorate([
14
+ (0, typeorm_1.Column)({
15
+ primary: true,
16
+ type: 'varchar',
17
+ length: 1500,
18
+ })
19
+ ], JacksonStore.prototype, "key", void 0);
20
+ __decorate([
21
+ (0, typeorm_1.Column)({
22
+ type: 'text',
23
+ })
24
+ ], JacksonStore.prototype, "value", void 0);
25
+ __decorate([
26
+ (0, typeorm_1.Column)({
27
+ type: 'varchar',
28
+ length: 64,
29
+ nullable: true,
30
+ })
31
+ ], JacksonStore.prototype, "iv", void 0);
32
+ __decorate([
33
+ (0, typeorm_1.Column)({
34
+ type: 'varchar',
35
+ length: 64,
36
+ nullable: true,
37
+ })
38
+ ], JacksonStore.prototype, "tag", void 0);
39
+ __decorate([
40
+ (0, typeorm_1.Column)({
41
+ type: 'datetime',
42
+ default: () => 'CURRENT_TIMESTAMP',
43
+ nullable: false,
44
+ })
45
+ ], JacksonStore.prototype, "createdAt", void 0);
46
+ __decorate([
47
+ (0, typeorm_1.Column)({
48
+ type: 'datetime',
49
+ nullable: true,
50
+ })
51
+ ], JacksonStore.prototype, "modifiedAt", void 0);
52
+ JacksonStore = __decorate([
53
+ (0, typeorm_1.Entity)()
54
+ ], JacksonStore);
55
+ exports.JacksonStore = JacksonStore;
@@ -0,0 +1,4 @@
1
+ export declare class JacksonTTL {
2
+ key: string;
3
+ expiresAt: number;
4
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.JacksonTTL = void 0;
10
+ const typeorm_1 = require("typeorm");
11
+ let JacksonTTL = class JacksonTTL {
12
+ };
13
+ __decorate([
14
+ (0, typeorm_1.Column)({
15
+ primary: true,
16
+ type: 'varchar',
17
+ length: 1500,
18
+ })
19
+ ], JacksonTTL.prototype, "key", void 0);
20
+ __decorate([
21
+ (0, typeorm_1.Index)('_jackson_ttl_expires_at'),
22
+ (0, typeorm_1.Column)({
23
+ type: 'bigint',
24
+ })
25
+ ], JacksonTTL.prototype, "expiresAt", void 0);
26
+ JacksonTTL = __decorate([
27
+ (0, typeorm_1.Entity)()
28
+ ], JacksonTTL);
29
+ exports.JacksonTTL = JacksonTTL;
@@ -0,0 +1 @@
1
+ export declare const parseURL: (url?: string) => any;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseURL = void 0;
4
+ const parseURL = (url) => {
5
+ if (!url) {
6
+ throw new Error('URL is required');
7
+ }
8
+ const parts = url.split('://');
9
+ if (parts.length != 2) {
10
+ throw new Error('Invalid connection string');
11
+ }
12
+ //const scheme = parts[0];
13
+ const connectionString = parts[1];
14
+ const connParts = connectionString.split(';');
15
+ if (connParts.length == 0) {
16
+ throw new Error('Invalid connection string');
17
+ }
18
+ // sqlserver://[serverName[\instanceName][:portNumber]][;property=value[;property=value]]
19
+ const hostPort = connParts[0];
20
+ const hostPortParts = hostPort.split(':');
21
+ const host = hostPortParts[0];
22
+ const port = hostPortParts.length > 1 ? parseInt(hostPortParts[1], 10) : 1433;
23
+ const options = {};
24
+ connParts.slice(1).map((p) => {
25
+ const ps = p.split('=');
26
+ options[ps[0]] = ps[1];
27
+ });
28
+ const username = options.username || options.user;
29
+ const password = options.password || options.pass;
30
+ const database = options.database;
31
+ delete options.username;
32
+ delete options.user;
33
+ delete options.password;
34
+ delete options.pass;
35
+ delete options.database;
36
+ options.encrypt = Boolean(options.encrypt || false);
37
+ return {
38
+ host,
39
+ port,
40
+ database,
41
+ username,
42
+ password,
43
+ options,
44
+ };
45
+ };
46
+ exports.parseURL = parseURL;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  require('reflect-metadata');
37
37
  const typeorm_1 = require("typeorm");
38
38
  const dbutils = __importStar(require("../utils"));
39
+ const mssql = __importStar(require("./mssql"));
39
40
  class Sql {
40
41
  constructor(options) {
41
42
  this.options = options;
@@ -45,15 +46,20 @@ class Sql {
45
46
  const sqlType = this.options.engine === 'planetscale' ? 'mysql' : this.options.type;
46
47
  while (true) {
47
48
  try {
48
- this.dataSource = new typeorm_1.DataSource({
49
+ const baseOpts = {
49
50
  type: sqlType,
50
- url: this.options.url,
51
51
  synchronize: this.options.engine !== 'planetscale',
52
52
  migrationsTableName: '_jackson_migrations',
53
53
  logging: ['error'],
54
54
  entities: [JacksonStore, JacksonIndex, JacksonTTL],
55
- ssl: this.options.ssl,
56
- });
55
+ };
56
+ if (sqlType === 'mssql') {
57
+ const mssqlOpts = mssql.parseURL(this.options.url);
58
+ this.dataSource = new typeorm_1.DataSource(Object.assign({ host: mssqlOpts.host, port: mssqlOpts.port, database: mssqlOpts.database, username: mssqlOpts.username, password: mssqlOpts.password, options: mssqlOpts.options }, baseOpts));
59
+ }
60
+ else {
61
+ this.dataSource = new typeorm_1.DataSource(Object.assign({ url: this.options.url, ssl: this.options.ssl }, baseOpts));
62
+ }
57
63
  yield this.dataSource.initialize();
58
64
  break;
59
65
  }
@@ -94,7 +100,7 @@ class Sql {
94
100
  this.timerId = setTimeout(this.ttlCleanup, this.options.ttl * 1000);
95
101
  }
96
102
  else {
97
- console.log('Warning: ttl cleanup not enabled, set both "ttl" and "cleanupLimit" options to enable it!');
103
+ console.warn('Warning: ttl cleanup not enabled, set both "ttl" and "cleanupLimit" options to enable it!');
98
104
  }
99
105
  return this;
100
106
  });
@@ -173,7 +179,7 @@ class Sql {
173
179
  // no ttl support for secondary indexes
174
180
  for (const idx of indexes || []) {
175
181
  const key = dbutils.keyForIndex(namespace, idx);
176
- const rec = yield this.indexRepository.findOneBy({
182
+ const rec = yield transactionalEntityManager.findOneBy(this.JacksonIndex, {
177
183
  key,
178
184
  storeKey: store.key,
179
185
  });
package/dist/index.js CHANGED
@@ -10,6 +10,18 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
10
10
  if (k2 === undefined) k2 = k;
11
11
  o[k2] = m[k];
12
12
  }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
13
25
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
26
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
27
  };
@@ -38,6 +50,7 @@ const logout_1 = require("./controller/logout");
38
50
  const directory_sync_1 = __importDefault(require("./directory-sync"));
39
51
  const oidc_discovery_1 = require("./controller/oidc-discovery");
40
52
  const sp_config_1 = require("./controller/sp-config");
53
+ const x509 = __importStar(require("./saml/x509"));
41
54
  const defaultOpts = (opts) => {
42
55
  const newOpts = Object.assign({}, opts);
43
56
  if (!newOpts.externalUrl) {
@@ -67,10 +80,13 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
67
80
  const codeStore = db.store('oauth:code', opts.db.ttl);
68
81
  const tokenStore = db.store('oauth:token', opts.db.ttl);
69
82
  const healthCheckStore = db.store('_health:check');
83
+ const certificateStore = db.store('x509:certificates');
70
84
  const connectionAPIController = new api_1.ConnectionAPIController({ connectionStore, opts });
71
85
  const adminController = new admin_1.AdminController({ connectionStore });
72
86
  const healthCheckController = new health_check_1.HealthCheckController({ healthCheckStore });
73
87
  yield healthCheckController.init();
88
+ // Create default certificate if it doesn't exist.
89
+ yield x509.init(certificateStore);
74
90
  const oauthController = new oauth_1.OAuthController({
75
91
  connectionStore,
76
92
  sessionStore,
@@ -85,7 +101,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
85
101
  });
86
102
  const directorySync = yield (0, directory_sync_1.default)({ db, opts });
87
103
  const oidcDiscoveryController = new oidc_discovery_1.OidcDiscoveryController({ opts });
88
- const spConfig = new sp_config_1.SPSAMLConfig(opts);
104
+ const spConfig = new sp_config_1.SPSAMLConfig(opts, x509.getDefaultCertificate);
89
105
  // write pre-loaded connections if present
90
106
  const preLoadedConnection = opts.preLoadedConnection || opts.preLoadedConfig;
91
107
  if (preLoadedConnection && preLoadedConnection.length > 0) {
@@ -97,11 +113,11 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
97
113
  else {
98
114
  yield connectionAPIController.createSAMLConnection(connection);
99
115
  }
100
- console.log(`loaded connection for tenant "${connection.tenant}" and product "${connection.product}"`);
116
+ console.info(`loaded connection for tenant "${connection.tenant}" and product "${connection.product}"`);
101
117
  }
102
118
  }
103
119
  const type = opts.db.engine === 'sql' && opts.db.type ? ' Type: ' + opts.db.type : '';
104
- console.log(`Using engine: ${opts.db.engine}.${type}`);
120
+ console.info(`Using engine: ${opts.db.engine}.${type}`);
105
121
  return {
106
122
  spConfig,
107
123
  apiController: connectionAPIController,
@@ -1,7 +1,13 @@
1
- declare const _default: {
2
- generate: () => {
3
- publicKey: any;
4
- privateKey: any;
5
- };
1
+ import type { Storable } from '../typings';
2
+ export declare const init: (store: Storable) => Promise<{
3
+ publicKey: string;
4
+ privateKey: string;
5
+ }>;
6
+ export declare const generateCertificate: () => {
7
+ publicKey: any;
8
+ privateKey: any;
6
9
  };
7
- export default _default;
10
+ export declare const getDefaultCertificate: () => Promise<{
11
+ publicKey: string;
12
+ privateKey: string;
13
+ }>;
package/dist/saml/x509.js CHANGED
@@ -22,17 +22,38 @@ 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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
25
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.getDefaultCertificate = exports.generateCertificate = exports.init = void 0;
26
39
  const forge = __importStar(require("node-forge"));
40
+ const crypto_1 = __importDefault(require("crypto"));
27
41
  const pki = forge.pki;
28
- const generate = () => {
42
+ let certificateStore;
43
+ let cachedCertificate;
44
+ const init = (store) => __awaiter(void 0, void 0, void 0, function* () {
45
+ certificateStore = store;
46
+ return yield (0, exports.getDefaultCertificate)();
47
+ });
48
+ exports.init = init;
49
+ const generateCertificate = () => {
29
50
  const today = new Date();
30
51
  const keys = pki.rsa.generateKeyPair(2048);
31
52
  const cert = pki.createCertificate();
32
53
  cert.publicKey = keys.publicKey;
33
54
  cert.serialNumber = '01';
34
55
  cert.validity.notBefore = new Date();
35
- cert.validity.notAfter = new Date(today.setFullYear(today.getFullYear() + 10));
56
+ cert.validity.notAfter = new Date(today.setFullYear(today.getFullYear() + 30));
36
57
  const attrs = [
37
58
  {
38
59
  name: 'commonName',
@@ -62,6 +83,26 @@ const generate = () => {
62
83
  privateKey: pki.privateKeyToPem(keys.privateKey),
63
84
  };
64
85
  };
65
- exports.default = {
66
- generate,
67
- };
86
+ exports.generateCertificate = generateCertificate;
87
+ const getDefaultCertificate = () => __awaiter(void 0, void 0, void 0, function* () {
88
+ if (cachedCertificate && !(yield isCertificateExpired(cachedCertificate.publicKey))) {
89
+ return cachedCertificate;
90
+ }
91
+ if (!certificateStore) {
92
+ throw new Error('Certificate store not initialized');
93
+ }
94
+ cachedCertificate = yield certificateStore.get('default');
95
+ // If certificate is expired let it drop through so it creates a new cert
96
+ if (cachedCertificate && !(yield isCertificateExpired(cachedCertificate.publicKey))) {
97
+ return cachedCertificate;
98
+ }
99
+ // If default certificate is not found or has expired, create one and store it.
100
+ cachedCertificate = (0, exports.generateCertificate)();
101
+ yield certificateStore.put('default', cachedCertificate);
102
+ return cachedCertificate;
103
+ });
104
+ exports.getDefaultCertificate = getDefaultCertificate;
105
+ const isCertificateExpired = (publicKey) => __awaiter(void 0, void 0, void 0, function* () {
106
+ const { validTo } = new crypto_1.default.X509Certificate(publicKey);
107
+ return !(validTo != 'Bad time value' && new Date(validTo) > new Date());
108
+ });
package/dist/typings.d.ts CHANGED
@@ -41,10 +41,6 @@ export interface SAMLSSORecord extends SAMLSSOConnection {
41
41
  thumbprint?: string;
42
42
  validTo?: string;
43
43
  };
44
- certs: {
45
- privateKey: string;
46
- publicKey: string;
47
- };
48
44
  }
49
45
  export interface OIDCSSORecord extends SSOConnection {
50
46
  clientID: string;
@@ -261,7 +257,7 @@ export interface Encrypted {
261
257
  }
262
258
  export declare type EncryptionKey = any;
263
259
  export declare type DatabaseEngine = 'redis' | 'sql' | 'mongo' | 'mem' | 'planetscale';
264
- export declare type DatabaseType = 'postgres' | 'mysql' | 'mariadb';
260
+ export declare type DatabaseType = 'postgres' | 'mysql' | 'mariadb' | 'mssql';
265
261
  export interface DatabaseOption {
266
262
  engine?: DatabaseEngine;
267
263
  url?: string;
@@ -314,10 +310,6 @@ interface Metadata {
314
310
  }
315
311
  export interface SAMLConnection {
316
312
  idpMetadata: Metadata;
317
- certs: {
318
- privateKey: string;
319
- publicKey: string;
320
- };
321
313
  defaultRedirectUrl: string;
322
314
  }
323
315
  export interface OAuthErrorHandlerParams {
@@ -328,14 +320,15 @@ export interface OAuthErrorHandlerParams {
328
320
  }
329
321
  export declare type OIDCErrorCodes = 'interaction_required' | 'login_required' | 'account_selection_required' | 'consent_required' | 'invalid_request_uri' | 'invalid_request_object' | 'request_not_supported' | 'request_uri_not_supported' | 'registration_not_supported';
330
322
  export interface ISPSAMLConfig {
331
- get(): {
323
+ get(): Promise<{
332
324
  acsUrl: string;
333
325
  entityId: string;
334
326
  response: string;
335
327
  assertionSignature: string;
336
328
  signatureAlgorithm: string;
337
- assertionEncryption: string;
338
- };
329
+ publicKey: string;
330
+ publicKeyString: string;
331
+ }>;
339
332
  toMarkdown(): string;
340
333
  toHTML(): string;
341
334
  }
@@ -0,0 +1,26 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class mssInitial1667949639424 implements MigrationInterface {
4
+ name = 'mssInitial1667949639424'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`CREATE TABLE "jackson_ttl" ("key" varchar(1500) NOT NULL, "expiresAt" bigint NOT NULL, CONSTRAINT "PK_7c9bcdfb4d82e873e19935ec806" PRIMARY KEY ("key"))`);
8
+ await queryRunner.query(`CREATE INDEX "_jackson_ttl_expires_at" ON "jackson_ttl" ("expiresAt") `);
9
+ await queryRunner.query(`CREATE TABLE "jackson_store" ("key" varchar(1500) NOT NULL, "value" text NOT NULL, "iv" varchar(64), "tag" varchar(64), "createdAt" datetime NOT NULL CONSTRAINT "DF_49022ed8c543adefc99ff762200" DEFAULT getdate(), "modifiedAt" datetime, CONSTRAINT "PK_87b6fc1475fbd1228d2f53c6f4a" PRIMARY KEY ("key"))`);
10
+ await queryRunner.query(`CREATE TABLE "jackson_index" ("id" int NOT NULL IDENTITY(1,1), "key" varchar(1500) NOT NULL, "storeKey" varchar(1500) NOT NULL, CONSTRAINT "PK_a95aa83f01e3c73e126856b7820" PRIMARY KEY ("id"))`);
11
+ await queryRunner.query(`CREATE INDEX "_jackson_index_key" ON "jackson_index" ("key") `);
12
+ await queryRunner.query(`CREATE INDEX "_jackson_index_key_store" ON "jackson_index" ("key", "storeKey") `);
13
+ await queryRunner.query(`ALTER TABLE "jackson_index" ADD CONSTRAINT "FK_937b040fb2592b4671cbde09e83" FOREIGN KEY ("storeKey") REFERENCES "jackson_store"("key") ON DELETE CASCADE ON UPDATE NO ACTION`);
14
+ }
15
+
16
+ public async down(queryRunner: QueryRunner): Promise<void> {
17
+ await queryRunner.query(`ALTER TABLE "jackson_index" DROP CONSTRAINT "FK_937b040fb2592b4671cbde09e83"`);
18
+ await queryRunner.query(`DROP INDEX "_jackson_index_key_store" ON "jackson_index"`);
19
+ await queryRunner.query(`DROP INDEX "_jackson_index_key" ON "jackson_index"`);
20
+ await queryRunner.query(`DROP TABLE "jackson_index"`);
21
+ await queryRunner.query(`DROP TABLE "jackson_store"`);
22
+ await queryRunner.query(`DROP INDEX "_jackson_ttl_expires_at" ON "jackson_ttl"`);
23
+ await queryRunner.query(`DROP TABLE "jackson_ttl"`);
24
+ }
25
+
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "SAML Jackson library",
5
5
  "keywords": [
6
6
  "SAML 2.0"
@@ -22,10 +22,12 @@
22
22
  "db:migration:generate:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/mysql/ms_${MIGRATION_NAME}",
23
23
  "db:migration:generate:planetscale": "cross-env DB_ENGINE=planetscale DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/mysql/ms_${MIGRATION_NAME}",
24
24
  "db:migration:generate:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/mariadb/md_${MIGRATION_NAME}",
25
+ "db:migration:generate:mssql": "cross-env DB_TYPE=mssql DB_URL='sqlserver://localhost:1433;database=master;username=sa;password=123ABabc!' ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/mssql/mss_${MIGRATION_NAME}",
25
26
  "db:migration:run:postgres": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
26
27
  "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 -d typeorm.ts",
27
28
  "db:migration:run:planetscale": "cross-env DB_SSL=true DB_ENGINE=planetscale DB_URL=${PLANETSCALE_URL} ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
28
29
  "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 -d typeorm.ts",
30
+ "db:migration:run:mssql": "cross-env DB_TYPE=mssql DB_URL='sqlserver://localhost:1433;database=master;username=sa;password=123ABabc!' ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
29
31
  "prepublishOnly": "npm run build",
30
32
  "test": "tap --ts --timeout=100 --coverage-map=map.js test/**/*.test.ts",
31
33
  "sort": "npx sort-package-json"
@@ -42,14 +44,15 @@
42
44
  "@opentelemetry/api": "1.0.4",
43
45
  "@opentelemetry/api-metrics": "0.27.0",
44
46
  "axios": "1.1.3",
45
- "jose": "4.10.3",
46
- "marked": "4.1.1",
47
+ "jose": "4.10.4",
48
+ "marked": "4.2.2",
47
49
  "mongodb": "4.11.0",
50
+ "mssql": "9.0.1",
48
51
  "mysql2": "2.3.3",
49
- "openid-client": "5.1.10",
50
52
  "node-forge": "1.3.1",
53
+ "openid-client": "5.2.1",
51
54
  "pg": "8.8.0",
52
- "redis": "4.3.1",
55
+ "redis": "4.4.0",
53
56
  "reflect-metadata": "0.1.13",
54
57
  "ripemd160": "2.0.2",
55
58
  "typeorm": "0.3.10",
@@ -58,13 +61,13 @@
58
61
  },
59
62
  "devDependencies": {
60
63
  "@faker-js/faker": "7.6.0",
61
- "@types/node": "18.11.5",
64
+ "@types/node": "18.11.9",
62
65
  "@types/sinon": "10.0.13",
63
66
  "@types/tap": "15.0.7",
64
- "@typescript-eslint/eslint-plugin": "5.40.0",
65
- "@typescript-eslint/parser": "5.41.0",
67
+ "@typescript-eslint/eslint-plugin": "5.42.0",
68
+ "@typescript-eslint/parser": "5.42.0",
66
69
  "cross-env": "7.0.3",
67
- "eslint": "8.26.0",
70
+ "eslint": "8.27.0",
68
71
  "eslint-config-prettier": "8.5.0",
69
72
  "prettier": "2.7.1",
70
73
  "sinon": "14.0.1",
@@ -74,6 +77,6 @@
74
77
  "typescript": "4.8.4"
75
78
  },
76
79
  "engines": {
77
- "node": ">=14.18.1 <=16.x"
80
+ "node": ">=14.18.1 <=18.x"
78
81
  }
79
82
  }