@boxyhq/saml-jackson 0.5.1 → 1.0.0

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/README.md CHANGED
@@ -14,7 +14,7 @@ npm i @boxyhq/saml-jackson
14
14
 
15
15
  ## Documentation
16
16
 
17
- For full documentation, visit [boxyhq.com/docs/jackson/npm-library](https://boxyhq.com/docs/jackson/npm-library)
17
+ For full documentation, visit [boxyhq.com/docs/jackson/deploy/npm-library](https://boxyhq.com/docs/jackson/deploy/npm-library)
18
18
 
19
19
  ## License
20
20
 
@@ -4,6 +4,7 @@ export declare class APIController implements IAPIController {
4
4
  constructor({ configStore }: {
5
5
  configStore: any;
6
6
  });
7
+ private _validateRedirectUrl;
7
8
  private _validateIdPConfig;
8
9
  /**
9
10
  * @swagger
@@ -58,6 +58,19 @@ class APIController {
58
58
  constructor({ configStore }) {
59
59
  this.configStore = configStore;
60
60
  }
61
+ _validateRedirectUrl({ redirectUrlList, defaultRedirectUrl }) {
62
+ if (redirectUrlList) {
63
+ if (redirectUrlList.length > 100) {
64
+ throw new error_1.JacksonError('Exceeded maximum number of allowed redirect urls', 400);
65
+ }
66
+ for (const url of redirectUrlList) {
67
+ (0, utils_1.validateAbsoluteUrl)(url, 'redirectUrl is invalid');
68
+ }
69
+ }
70
+ if (defaultRedirectUrl) {
71
+ (0, utils_1.validateAbsoluteUrl)(defaultRedirectUrl, 'defaultRedirectUrl is invalid');
72
+ }
73
+ }
61
74
  _validateIdPConfig(body) {
62
75
  const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, description } = body;
63
76
  if (!rawMetadata && !encodedRawMetadata) {
@@ -168,6 +181,8 @@ class APIController {
168
181
  const { encodedRawMetadata, rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product, name, description, } = body;
169
182
  metrics.increment('createConfig');
170
183
  this._validateIdPConfig(body);
184
+ const redirectUrlList = extractRedirectUrls(redirectUrl);
185
+ this._validateRedirectUrl({ defaultRedirectUrl, redirectUrlList });
171
186
  let metaData = rawMetadata;
172
187
  if (encodedRawMetadata) {
173
188
  metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
@@ -195,7 +210,7 @@ class APIController {
195
210
  const record = {
196
211
  idpMetadata,
197
212
  defaultRedirectUrl,
198
- redirectUrl: JSON.parse(redirectUrl),
213
+ redirectUrl: redirectUrlList,
199
214
  tenant,
200
215
  product,
201
216
  name,
@@ -296,6 +311,8 @@ class APIController {
296
311
  if (description && description.length > 100) {
297
312
  throw new error_1.JacksonError('Description should not exceed 100 characters', 400);
298
313
  }
314
+ const redirectUrlList = redirectUrl ? extractRedirectUrls(redirectUrl) : null;
315
+ this._validateRedirectUrl({ defaultRedirectUrl, redirectUrlList });
299
316
  const _currentConfig = yield this.getConfig(clientInfo);
300
317
  if (_currentConfig.clientSecret !== (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientSecret)) {
301
318
  throw new error_1.JacksonError('clientSecret mismatch', 400);
@@ -321,7 +338,7 @@ class APIController {
321
338
  throw new error_1.JacksonError('Tenant/Product config mismatch with IdP metadata', 400);
322
339
  }
323
340
  }
324
- 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: redirectUrl ? JSON.parse(redirectUrl) : _currentConfig.redirectUrl });
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 });
325
342
  yield this.configStore.put(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientID, record, {
326
343
  // secondary index on entityID
327
344
  name: utils_1.IndexNames.EntityID,
@@ -495,3 +512,18 @@ const extractHostName = (url) => {
495
512
  return null;
496
513
  }
497
514
  };
515
+ const extractRedirectUrls = (urls) => {
516
+ if (!urls) {
517
+ return [];
518
+ }
519
+ if (typeof urls === 'string') {
520
+ if (urls.startsWith('[')) {
521
+ // redirectUrl is a stringified array
522
+ return JSON.parse(urls);
523
+ }
524
+ // redirectUrl is a single URL
525
+ return [urls];
526
+ }
527
+ // redirectUrl is an array of URLs
528
+ return urls;
529
+ };
@@ -0,0 +1,11 @@
1
+ import { IHealthCheckController, Storable } from '../typings';
2
+ export declare class HealthCheckController implements IHealthCheckController {
3
+ healthCheckStore: Storable;
4
+ constructor({ healthCheckStore }: {
5
+ healthCheckStore: any;
6
+ });
7
+ init(): Promise<void>;
8
+ status(): Promise<{
9
+ status: number;
10
+ }>;
11
+ }
@@ -0,0 +1,53 @@
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.HealthCheckController = void 0;
13
+ const error_1 = require("./error");
14
+ const healthKey = 'amihealthy';
15
+ const healthValue = 'fit';
16
+ const g = global;
17
+ class HealthCheckController {
18
+ constructor({ healthCheckStore }) {
19
+ this.healthCheckStore = healthCheckStore;
20
+ }
21
+ init() {
22
+ return __awaiter(this, void 0, void 0, function* () {
23
+ this.healthCheckStore.put(healthKey, healthValue);
24
+ });
25
+ }
26
+ status() {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ try {
29
+ if (!g.isJacksonReady) {
30
+ return {
31
+ status: 503,
32
+ };
33
+ }
34
+ const response = yield Promise.race([
35
+ this.healthCheckStore.get(healthKey),
36
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 1000)),
37
+ ]);
38
+ if (response === healthValue) {
39
+ return {
40
+ status: 200,
41
+ };
42
+ }
43
+ return {
44
+ status: 503,
45
+ };
46
+ }
47
+ catch (err) {
48
+ throw new error_1.JacksonError('Service not available', 503);
49
+ }
50
+ });
51
+ }
52
+ }
53
+ exports.HealthCheckController = HealthCheckController;
@@ -172,7 +172,7 @@ class OAuthController {
172
172
  }
173
173
  else {
174
174
  // HTTP POST binding
175
- authorizeForm = (0, utils_1.createAuthorizeForm)(relayState, encodeURI(Buffer.from(samlReq.request).toString('base64')), ssoUrl);
175
+ authorizeForm = (0, utils_1.createRequestForm)(relayState, encodeURI(Buffer.from(samlReq.request).toString('base64')), ssoUrl);
176
176
  }
177
177
  return {
178
178
  redirect_url: redirectUrl,
@@ -346,6 +346,11 @@ class OAuthController {
346
346
  }
347
347
  }
348
348
  }
349
+ else {
350
+ if (client_secret !== this.opts.clientSecretVerifier && client_secret !== codeVal.clientSecret) {
351
+ throw new error_1.JacksonError('Invalid client_secret', 401);
352
+ }
353
+ }
349
354
  }
350
355
  else if (codeVal && codeVal.session) {
351
356
  throw new error_1.JacksonError('Please specify client_secret or code_verifier', 401);
@@ -0,0 +1,18 @@
1
+ import { SAMLResponsePayload, SLORequestParams } from '../typings';
2
+ export declare class LogoutController {
3
+ private configStore;
4
+ private sessionStore;
5
+ private opts;
6
+ constructor({ configStore, sessionStore, opts }: {
7
+ configStore: any;
8
+ sessionStore: any;
9
+ opts: any;
10
+ });
11
+ createRequest({ nameId, tenant, product, redirectUrl }: SLORequestParams): Promise<{
12
+ logoutUrl: string | null;
13
+ logoutForm: string | null;
14
+ }>;
15
+ handleResponse({ SAMLResponse, RelayState }: SAMLResponsePayload): Promise<{
16
+ redirectUrl: any;
17
+ }>;
18
+ }
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
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
+ };
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
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.LogoutController = void 0;
39
+ const xmldom_1 = require("@xmldom/xmldom");
40
+ const crypto_1 = __importDefault(require("crypto"));
41
+ const thumbprint_1 = __importDefault(require("thumbprint"));
42
+ const util_1 = require("util");
43
+ const xml_crypto_1 = require("xml-crypto");
44
+ const xml2js_1 = __importDefault(require("xml2js"));
45
+ const xmlbuilder_1 = __importDefault(require("xmlbuilder"));
46
+ const zlib_1 = require("zlib");
47
+ const dbutils = __importStar(require("../db/utils"));
48
+ const saml_1 = __importDefault(require("../saml/saml"));
49
+ const error_1 = require("./error");
50
+ const redirect = __importStar(require("./oauth/redirect"));
51
+ const utils_1 = require("./utils");
52
+ const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
53
+ const relayStatePrefix = 'boxyhq_jackson_';
54
+ class LogoutController {
55
+ constructor({ configStore, sessionStore, opts }) {
56
+ this.opts = opts;
57
+ this.configStore = configStore;
58
+ this.sessionStore = sessionStore;
59
+ }
60
+ // Create SLO Request
61
+ createRequest({ nameId, tenant, product, redirectUrl }) {
62
+ return __awaiter(this, void 0, void 0, function* () {
63
+ let samlConfig = null;
64
+ if (tenant && product) {
65
+ const samlConfigs = yield this.configStore.getByIndex({
66
+ name: utils_1.IndexNames.TenantProduct,
67
+ value: dbutils.keyFromParts(tenant, product),
68
+ });
69
+ if (!samlConfigs || samlConfigs.length === 0) {
70
+ throw new error_1.JacksonError('SAML configuration not found.', 403);
71
+ }
72
+ samlConfig = samlConfigs[0];
73
+ }
74
+ if (!samlConfig) {
75
+ throw new error_1.JacksonError('SAML configuration not found.', 403);
76
+ }
77
+ const { idpMetadata: { slo, provider }, certs: { privateKey, publicKey }, } = samlConfig;
78
+ if ('redirectUrl' in slo === false && 'postUrl' in slo === false) {
79
+ throw new error_1.JacksonError(`${provider} doesn't support SLO or disabled by IdP.`, 400);
80
+ }
81
+ const { id, xml } = buildRequestXML(nameId, this.opts.samlAudience, slo.redirectUrl);
82
+ const sessionId = crypto_1.default.randomBytes(16).toString('hex');
83
+ let logoutUrl = null;
84
+ let logoutForm = null;
85
+ const relayState = relayStatePrefix + sessionId;
86
+ const signedXML = yield signXML(xml, privateKey, publicKey);
87
+ yield this.sessionStore.put(sessionId, {
88
+ id,
89
+ redirectUrl,
90
+ });
91
+ // HTTP-Redirect binding
92
+ if ('redirectUrl' in slo) {
93
+ logoutUrl = redirect.success(slo.redirectUrl, {
94
+ SAMLRequest: Buffer.from(yield deflateRawAsync(signedXML)).toString('base64'),
95
+ RelayState: relayState,
96
+ });
97
+ }
98
+ // HTTP-POST binding
99
+ if ('postUrl' in slo) {
100
+ logoutForm = (0, utils_1.createRequestForm)(relayState, encodeURI(Buffer.from(signedXML).toString('base64')), slo.postUrl);
101
+ }
102
+ return { logoutUrl, logoutForm };
103
+ });
104
+ }
105
+ // Handle SLO Response
106
+ handleResponse({ SAMLResponse, RelayState }) {
107
+ var _a;
108
+ return __awaiter(this, void 0, void 0, function* () {
109
+ const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
110
+ const sessionId = RelayState.replace(relayStatePrefix, '');
111
+ const session = yield this.sessionStore.get(sessionId);
112
+ if (!session) {
113
+ throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
114
+ }
115
+ const parsedResponse = yield parseSAMLResponse(rawResponse);
116
+ if (parsedResponse.status !== 'urn:oasis:names:tc:SAML:2.0:status:Success') {
117
+ throw new error_1.JacksonError(`SLO failed with status ${parsedResponse.status}.`, 400);
118
+ }
119
+ if (parsedResponse.inResponseTo !== session.id) {
120
+ throw new error_1.JacksonError(`SLO failed with mismatched request ID.`, 400);
121
+ }
122
+ const samlConfigs = yield this.configStore.getByIndex({
123
+ name: utils_1.IndexNames.EntityID,
124
+ value: parsedResponse.issuer,
125
+ });
126
+ if (!samlConfigs || samlConfigs.length === 0) {
127
+ throw new error_1.JacksonError('SAML configuration not found.', 403);
128
+ }
129
+ const { idpMetadata, defaultRedirectUrl } = samlConfigs[0];
130
+ if (!(yield hasValidSignature(rawResponse, idpMetadata.thumbprint))) {
131
+ throw new error_1.JacksonError('Invalid signature.', 403);
132
+ }
133
+ try {
134
+ yield this.sessionStore.delete(sessionId);
135
+ }
136
+ catch (_err) {
137
+ // Ignore
138
+ }
139
+ return {
140
+ redirectUrl: (_a = session.redirectUrl) !== null && _a !== void 0 ? _a : defaultRedirectUrl,
141
+ };
142
+ });
143
+ }
144
+ }
145
+ exports.LogoutController = LogoutController;
146
+ // Create the XML for the SLO Request
147
+ const buildRequestXML = (nameId, providerName, sloUrl) => {
148
+ const id = '_' + crypto_1.default.randomBytes(10).toString('hex');
149
+ const xml = {
150
+ 'samlp:LogoutRequest': {
151
+ '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
152
+ '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
153
+ '@ID': id,
154
+ '@Version': '2.0',
155
+ '@IssueInstant': new Date().toISOString(),
156
+ '@Destination': sloUrl,
157
+ 'saml:Issuer': {
158
+ '#text': providerName,
159
+ },
160
+ 'saml:NameID': {
161
+ '@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
162
+ '#text': nameId,
163
+ },
164
+ },
165
+ };
166
+ return {
167
+ id,
168
+ xml: xmlbuilder_1.default.create(xml).end({}),
169
+ };
170
+ };
171
+ // Parse SAMLResponse
172
+ const parseSAMLResponse = (rawResponse) => __awaiter(void 0, void 0, void 0, function* () {
173
+ return new Promise((resolve, reject) => {
174
+ xml2js_1.default.parseString(rawResponse, { tagNameProcessors: [xml2js_1.default.processors.stripPrefix] }, (err, { LogoutResponse }) => {
175
+ if (err) {
176
+ reject(err);
177
+ return;
178
+ }
179
+ resolve({
180
+ issuer: LogoutResponse.Issuer[0]._,
181
+ id: LogoutResponse.$.ID,
182
+ status: LogoutResponse.Status[0].StatusCode[0].$.Value,
183
+ destination: LogoutResponse.$.Destination,
184
+ inResponseTo: LogoutResponse.$.InResponseTo,
185
+ });
186
+ });
187
+ });
188
+ });
189
+ // Sign the XML
190
+ const signXML = (xml, signingKey, publicKey) => __awaiter(void 0, void 0, void 0, function* () {
191
+ const sig = new xml_crypto_1.SignedXml();
192
+ sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
193
+ sig.keyInfoProvider = new saml_1.default.PubKeyInfo(publicKey);
194
+ sig.signingKey = signingKey;
195
+ sig.addReference("/*[local-name(.)='LogoutRequest']", ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'], 'http://www.w3.org/2001/04/xmlenc#sha256');
196
+ sig.computeSignature(xml);
197
+ return sig.getSignedXml();
198
+ });
199
+ // Validate signature
200
+ const hasValidSignature = (xml, certThumbprint) => __awaiter(void 0, void 0, void 0, function* () {
201
+ return new Promise((resolve, reject) => {
202
+ const doc = new xmldom_1.DOMParser().parseFromString(xml);
203
+ const signed = new xml_crypto_1.SignedXml();
204
+ let calculatedThumbprint;
205
+ const signature = (0, xml_crypto_1.xpath)(doc, "/*/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0] ||
206
+ (0, xml_crypto_1.xpath)(doc, "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0] ||
207
+ (0, xml_crypto_1.xpath)(doc, "/*/*/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];
208
+ signed.keyInfoProvider = {
209
+ getKey: function getKey(keyInfo) {
210
+ if (certThumbprint) {
211
+ const embeddedSignature = keyInfo[0].getElementsByTagNameNS('http://www.w3.org/2000/09/xmldsig#', 'X509Certificate');
212
+ if (embeddedSignature.length > 0) {
213
+ const base64cer = embeddedSignature[0].firstChild.toString();
214
+ calculatedThumbprint = thumbprint_1.default.calculate(base64cer);
215
+ return saml_1.default.certToPEM(base64cer);
216
+ }
217
+ }
218
+ },
219
+ getKeyInfo: function getKeyInfo() {
220
+ return '<X509Data></X509Data>';
221
+ },
222
+ };
223
+ signed.loadSignature(signature.toString());
224
+ try {
225
+ return resolve(signed.checkSignature(xml) && calculatedThumbprint.toUpperCase() === certThumbprint.toUpperCase());
226
+ }
227
+ catch (err) {
228
+ return reject(err);
229
+ }
230
+ });
231
+ });
@@ -2,4 +2,5 @@ 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;
5
+ export declare const createRequestForm: (relayState: string, samlReqEnc: string, postUrl: string) => string;
6
+ export declare const validateAbsoluteUrl: (url: any, message: any) => void;
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAuthorizeForm = exports.IndexNames = void 0;
3
+ exports.validateAbsoluteUrl = exports.createRequestForm = exports.IndexNames = void 0;
4
+ const error_1 = require("./error");
4
5
  var IndexNames;
5
6
  (function (IndexNames) {
6
7
  IndexNames["EntityID"] = "entityID";
7
8
  IndexNames["TenantProduct"] = "tenantProduct";
8
9
  })(IndexNames = exports.IndexNames || (exports.IndexNames = {}));
9
- const createAuthorizeForm = (relayState, samlReqEnc, postUrl) => {
10
+ const createRequestForm = (relayState, samlReqEnc, postUrl) => {
10
11
  const formElements = [
11
12
  '<!DOCTYPE html>',
12
13
  '<html>',
@@ -29,4 +30,13 @@ const createAuthorizeForm = (relayState, samlReqEnc, postUrl) => {
29
30
  ];
30
31
  return formElements.join('');
31
32
  };
32
- exports.createAuthorizeForm = createAuthorizeForm;
33
+ exports.createRequestForm = createRequestForm;
34
+ const validateAbsoluteUrl = (url, message) => {
35
+ try {
36
+ new URL(url);
37
+ }
38
+ catch (err) {
39
+ throw new error_1.JacksonError(message ? message : 'Invalid url', 400);
40
+ }
41
+ };
42
+ exports.validateAbsoluteUrl = validateAbsoluteUrl;
package/dist/db/mem.js CHANGED
@@ -79,16 +79,16 @@ class Mem {
79
79
  take += skip;
80
80
  const returnValue = [];
81
81
  if (namespace) {
82
- for (const key in this.store) {
83
- if (key.startsWith(namespace)) {
84
- if (count >= take) {
85
- break;
86
- }
87
- if (count >= skip) {
88
- returnValue.push(this.store[key]);
89
- }
90
- count++;
82
+ const val = Array.from(this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)]);
83
+ const iterator = val.reverse().values();
84
+ for (const value of iterator) {
85
+ if (count >= take) {
86
+ break;
87
+ }
88
+ if (count >= skip) {
89
+ returnValue.push(this.store[dbutils.keyFromParts(namespace, value)]);
91
90
  }
91
+ count++;
92
92
  }
93
93
  }
94
94
  return returnValue || [];
@@ -108,9 +108,6 @@ class Mem {
108
108
  return __awaiter(this, void 0, void 0, function* () {
109
109
  const k = dbutils.key(namespace, key);
110
110
  this.store[k] = val;
111
- if (!Date.parse(this.store['createdAt']))
112
- this.store['createdAt'] = new Date().toISOString();
113
- this.store['modifiedAt'] = new Date().toISOString();
114
111
  // console.log(this.store)
115
112
  if (ttl) {
116
113
  this.ttlStore[k] = {
@@ -136,6 +133,26 @@ class Mem {
136
133
  }
137
134
  cleanup.add(idxKey);
138
135
  }
136
+ let createdAtSet = this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)];
137
+ if (!createdAtSet) {
138
+ createdAtSet = new Set();
139
+ this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)] = createdAtSet;
140
+ this.store['createdAt'] = new Date().toISOString();
141
+ createdAtSet.add(key);
142
+ }
143
+ else {
144
+ if (!this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)].has(key)) {
145
+ createdAtSet.add(key);
146
+ this.store['createdAt'] = new Date().toISOString();
147
+ }
148
+ }
149
+ let modifiedAtSet = this.indexes[dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace)];
150
+ if (!modifiedAtSet) {
151
+ modifiedAtSet = new Set();
152
+ this.indexes[dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace)] = modifiedAtSet;
153
+ }
154
+ modifiedAtSet.add(key);
155
+ this.store['modifiedAt'] = new Date().toISOString();
139
156
  });
140
157
  }
141
158
  delete(namespace, key) {
@@ -148,6 +165,8 @@ class Mem {
148
165
  for (const dbKey of dbKeys || []) {
149
166
  this.indexes[dbKey] && this.indexes[dbKey].delete(key);
150
167
  }
168
+ this.indexes[dbutils.keyFromParts(dbutils.createdAtPrefix, namespace)].delete(key);
169
+ this.indexes[dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace)].delete(key);
151
170
  delete this.cleanup[idxKey];
152
171
  delete this.ttlStore[k];
153
172
  });
package/dist/db/mongo.js CHANGED
@@ -40,16 +40,9 @@ class Mongo {
40
40
  }
41
41
  init() {
42
42
  return __awaiter(this, void 0, void 0, function* () {
43
- try {
44
- if (!this.options.url) {
45
- throw Error('Please specify a db url');
46
- }
47
- this.client = new mongodb_1.MongoClient(this.options.url);
48
- yield this.client.connect();
49
- }
50
- catch (err) {
51
- console.error(`error connecting to ${this.options.type} db: ${err}`);
52
- }
43
+ const dbUrl = this.options.url;
44
+ this.client = new mongodb_1.MongoClient(dbUrl);
45
+ yield this.client.connect();
53
46
  this.db = this.client.db();
54
47
  this.collection = this.db.collection('jacksonStore');
55
48
  yield this.collection.createIndex({ indexes: 1 });
package/dist/db/redis.js CHANGED
@@ -79,16 +79,13 @@ class Redis {
79
79
  let count = 0;
80
80
  take += skip;
81
81
  try {
82
- for (var _b = __asyncValues(this.client.scanIterator({
83
- MATCH: dbutils.keyFromParts(namespace, '*'),
84
- COUNT: Math.min(take, 1000),
85
- })), _c; _c = yield _b.next(), !_c.done;) {
86
- const key = _c.value;
82
+ for (var _b = __asyncValues(this.client.zScanIterator(dbutils.keyFromParts(dbutils.createdAtPrefix, namespace), Math.min(take, 1000))), _c; _c = yield _b.next(), !_c.done;) {
83
+ const { score, value } = _c.value;
87
84
  if (count >= take) {
88
85
  break;
89
86
  }
90
87
  if (count >= skip) {
91
- keyArray.push(key);
88
+ keyArray.push(dbutils.keyFromParts(namespace, value));
92
89
  }
93
90
  count++;
94
91
  }
@@ -137,6 +134,18 @@ class Redis {
137
134
  tx = tx.sAdd(dbutils.keyFromParts(dbutils.indexPrefix, idxKey), key);
138
135
  tx = tx.sAdd(dbutils.keyFromParts(dbutils.indexPrefix, k), idxKey);
139
136
  }
137
+ const timestamp = Number(Date.now());
138
+ //Converting Timestamp in negative so that when we get the value, it will be found in reverse order (descending order).
139
+ const negativeTimestamp = -Math.abs(timestamp);
140
+ const value = yield this.client.get(k);
141
+ if (!value) {
142
+ tx = tx.zAdd(dbutils.keyFromParts(dbutils.createdAtPrefix, namespace), [
143
+ { score: negativeTimestamp, value: key },
144
+ ]);
145
+ }
146
+ tx = tx.zAdd(dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace), [
147
+ { score: negativeTimestamp, value: key },
148
+ ]);
140
149
  yield tx.exec();
141
150
  });
142
151
  }
@@ -151,6 +160,8 @@ class Redis {
151
160
  for (const dbKey of dbKeys || []) {
152
161
  tx.sRem(dbutils.keyFromParts(dbutils.indexPrefix, dbKey), key);
153
162
  }
163
+ tx.ZREM(dbutils.keyFromParts(dbutils.createdAtPrefix, namespace), key);
164
+ tx.ZREM(dbutils.keyFromParts(dbutils.modifiedAtPrefix, namespace), key);
154
165
  tx.del(idxKey);
155
166
  return yield tx.exec();
156
167
  });
@@ -1,7 +1,7 @@
1
1
  import { DatabaseDriver, DatabaseOption, Index, Encrypted } from '../../typings';
2
2
  declare class Sql implements DatabaseDriver {
3
3
  private options;
4
- private connection;
4
+ private dataSource;
5
5
  private storeRepository;
6
6
  private indexRepository;
7
7
  private ttlRepository;
@@ -47,8 +47,8 @@ class Sql {
47
47
  return __awaiter(this, void 0, void 0, function* () {
48
48
  while (true) {
49
49
  try {
50
- this.connection = yield (0, typeorm_1.createConnection)({
51
- name: this.options.type + Math.floor(Math.random() * 100000),
50
+ this.dataSource = new typeorm_1.DataSource({
51
+ // name: this.options.type! + Math.floor(Math.random() * 100000),
52
52
  type: this.options.type,
53
53
  url: this.options.url,
54
54
  synchronize: true,
@@ -56,6 +56,7 @@ class Sql {
56
56
  logging: ['error'],
57
57
  entities: [JacksonStore_1.JacksonStore, JacksonIndex_1.JacksonIndex, JacksonTTL_1.JacksonTTL],
58
58
  });
59
+ yield this.dataSource.initialize();
59
60
  break;
60
61
  }
61
62
  catch (err) {
@@ -64,9 +65,9 @@ class Sql {
64
65
  continue;
65
66
  }
66
67
  }
67
- this.storeRepository = this.connection.getRepository(JacksonStore_1.JacksonStore);
68
- this.indexRepository = this.connection.getRepository(JacksonIndex_1.JacksonIndex);
69
- this.ttlRepository = this.connection.getRepository(JacksonTTL_1.JacksonTTL);
68
+ this.storeRepository = this.dataSource.getRepository(JacksonStore_1.JacksonStore);
69
+ this.indexRepository = this.dataSource.getRepository(JacksonIndex_1.JacksonIndex);
70
+ this.ttlRepository = this.dataSource.getRepository(JacksonTTL_1.JacksonTTL);
70
71
  if (this.options.ttl && this.options.cleanupLimit) {
71
72
  this.ttlCleanup = () => __awaiter(this, void 0, void 0, function* () {
72
73
  const now = Date.now();
@@ -99,7 +100,7 @@ class Sql {
99
100
  }
100
101
  get(namespace, key) {
101
102
  return __awaiter(this, void 0, void 0, function* () {
102
- const res = yield this.storeRepository.findOne({
103
+ const res = yield this.storeRepository.findOneBy({
103
104
  key: dbutils.key(namespace, key),
104
105
  });
105
106
  if (res && res.value) {
@@ -133,7 +134,7 @@ class Sql {
133
134
  }
134
135
  getByIndex(namespace, idx) {
135
136
  return __awaiter(this, void 0, void 0, function* () {
136
- const res = yield this.indexRepository.find({
137
+ const res = yield this.indexRepository.findBy({
137
138
  key: dbutils.keyForIndex(namespace, idx),
138
139
  });
139
140
  const ret = [];
@@ -151,7 +152,7 @@ class Sql {
151
152
  }
152
153
  put(namespace, key, val, ttl = 0, ...indexes) {
153
154
  return __awaiter(this, void 0, void 0, function* () {
154
- yield this.connection.transaction((transactionalEntityManager) => __awaiter(this, void 0, void 0, function* () {
155
+ yield this.dataSource.transaction((transactionalEntityManager) => __awaiter(this, void 0, void 0, function* () {
155
156
  const dbKey = dbutils.key(namespace, key);
156
157
  const store = new JacksonStore_1.JacksonStore();
157
158
  store.key = dbKey;
@@ -169,7 +170,7 @@ class Sql {
169
170
  // no ttl support for secondary indexes
170
171
  for (const idx of indexes || []) {
171
172
  const key = dbutils.keyForIndex(namespace, idx);
172
- const rec = yield this.indexRepository.findOne({
173
+ const rec = yield this.indexRepository.findOneBy({
173
174
  key,
174
175
  storeKey: store.key,
175
176
  });
@@ -6,3 +6,5 @@ export declare const keyFromParts: (...parts: string[]) => string;
6
6
  export declare const sleep: (ms: number) => Promise<void>;
7
7
  export declare function isNumeric(num: any): boolean;
8
8
  export declare const indexPrefix = "_index";
9
+ export declare const createdAtPrefix = "_createdAt";
10
+ export declare const modifiedAtPrefix = "_modifiedAt";
package/dist/db/utils.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.indexPrefix = exports.isNumeric = exports.sleep = exports.keyFromParts = exports.keyDigest = exports.keyForIndex = exports.key = void 0;
6
+ exports.modifiedAtPrefix = exports.createdAtPrefix = exports.indexPrefix = exports.isNumeric = exports.sleep = exports.keyFromParts = exports.keyDigest = exports.keyForIndex = exports.key = void 0;
7
7
  const ripemd160_1 = __importDefault(require("ripemd160"));
8
8
  const key = (namespace, k) => {
9
9
  return namespace + ':' + k;
@@ -31,3 +31,5 @@ function isNumeric(num) {
31
31
  }
32
32
  exports.isNumeric = isNumeric;
33
33
  exports.indexPrefix = '_index';
34
+ exports.createdAtPrefix = '_createdAt';
35
+ exports.modifiedAtPrefix = '_modifiedAt';
package/dist/index.d.ts CHANGED
@@ -1,11 +1,15 @@
1
+ import { AdminController } from './controller/admin';
1
2
  import { APIController } from './controller/api';
2
3
  import { OAuthController } from './controller/oauth';
3
- import { AdminController } from './controller/admin';
4
+ import { HealthCheckController } from './controller/health-check';
5
+ import { LogoutController } from './controller/signout';
4
6
  import { JacksonOption } from './typings';
5
7
  export declare const controllers: (opts: JacksonOption) => Promise<{
6
8
  apiController: APIController;
7
9
  oauthController: OAuthController;
8
10
  adminController: AdminController;
11
+ logoutController: LogoutController;
12
+ healthCheckController: HealthCheckController;
9
13
  }>;
10
14
  export default controllers;
11
15
  export * from './typings';
package/dist/index.js CHANGED
@@ -27,12 +27,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.controllers = void 0;
30
+ const admin_1 = require("./controller/admin");
30
31
  const api_1 = require("./controller/api");
31
32
  const oauth_1 = require("./controller/oauth");
32
- const admin_1 = require("./controller/admin");
33
+ const health_check_1 = require("./controller/health-check");
34
+ const signout_1 = require("./controller/signout");
33
35
  const db_1 = __importDefault(require("./db/db"));
34
- const read_config_1 = __importDefault(require("./read-config"));
35
36
  const defaultDb_1 = __importDefault(require("./db/defaultDb"));
37
+ const read_config_1 = __importDefault(require("./read-config"));
36
38
  const defaultOpts = (opts) => {
37
39
  const newOpts = Object.assign({}, opts);
38
40
  if (!newOpts.externalUrl) {
@@ -56,8 +58,11 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
56
58
  const sessionStore = db.store('oauth:session', opts.db.ttl);
57
59
  const codeStore = db.store('oauth:code', opts.db.ttl);
58
60
  const tokenStore = db.store('oauth:token', opts.db.ttl);
61
+ const healthCheckStore = db.store('_health');
59
62
  const apiController = new api_1.APIController({ configStore });
60
63
  const adminController = new admin_1.AdminController({ configStore });
64
+ const healthCheckController = new health_check_1.HealthCheckController({ healthCheckStore });
65
+ yield healthCheckController.init();
61
66
  const oauthController = new oauth_1.OAuthController({
62
67
  configStore,
63
68
  sessionStore,
@@ -65,6 +70,11 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
65
70
  tokenStore,
66
71
  opts,
67
72
  });
73
+ const logoutController = new signout_1.LogoutController({
74
+ configStore,
75
+ sessionStore,
76
+ opts,
77
+ });
68
78
  // write pre-loaded config if present
69
79
  if (opts.preLoadedConfig && opts.preLoadedConfig.length > 0) {
70
80
  const configs = yield (0, read_config_1.default)(opts.preLoadedConfig);
@@ -79,6 +89,8 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
79
89
  apiController,
80
90
  oauthController,
81
91
  adminController,
92
+ logoutController,
93
+ healthCheckController,
82
94
  };
83
95
  });
84
96
  exports.controllers = controllers;
@@ -1,5 +1,6 @@
1
1
  import { SAMLProfile, SAMLReq } from '../typings';
2
2
  export declare const stripCertHeaderAndFooter: (cert: string) => string;
3
+ declare function PubKeyInfo(this: any, pubKey: string): void;
3
4
  declare const _default: {
4
5
  request: ({ ssoUrl, entityID, callbackUrl, isPassive, forceAuthn, identifierFormat, providerName, signingKey, publicKey, }: SAMLReq) => {
5
6
  id: string;
@@ -8,5 +9,7 @@ declare const _default: {
8
9
  parseAsync: (rawAssertion: string) => Promise<SAMLProfile>;
9
10
  validateAsync: (rawAssertion: string, options: any) => Promise<SAMLProfile>;
10
11
  parseMetadataAsync: (idpMeta: string) => Promise<Record<string, any>>;
12
+ PubKeyInfo: typeof PubKeyInfo;
13
+ certToPEM: (cert: string) => string;
11
14
  };
12
15
  export default _default;
package/dist/saml/saml.js CHANGED
@@ -37,12 +37,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
38
  exports.stripCertHeaderAndFooter = void 0;
39
39
  const saml20_1 = __importDefault(require("@boxyhq/saml20"));
40
- const xml2js_1 = __importDefault(require("xml2js"));
40
+ const crypto_1 = __importDefault(require("crypto"));
41
+ const rambda = __importStar(require("rambda"));
41
42
  const thumbprint_1 = __importDefault(require("thumbprint"));
42
43
  const xml_crypto_1 = __importDefault(require("xml-crypto"));
43
- const rambda = __importStar(require("rambda"));
44
+ const xml2js_1 = __importDefault(require("xml2js"));
44
45
  const xmlbuilder_1 = __importDefault(require("xmlbuilder"));
45
- const crypto_1 = __importDefault(require("crypto"));
46
46
  const claims_1 = __importDefault(require("./claims"));
47
47
  const idPrefix = '_';
48
48
  const authnXPath = '/*[local-name(.)="AuthnRequest" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
@@ -162,6 +162,8 @@ const parseMetadataAsync = (idpMeta) => __awaiter(void 0, void 0, void 0, functi
162
162
  let ssoPostUrl = null;
163
163
  let ssoRedirectUrl = null;
164
164
  let loginType = 'idp';
165
+ let sloRedirectUrl = null;
166
+ let sloPostUrl = null;
165
167
  let ssoDes = rambda.pathOr(null, 'EntityDescriptor.IDPSSODescriptor', res);
166
168
  if (!ssoDes) {
167
169
  ssoDes = rambda.pathOr([], 'EntityDescriptor.SPSSODescriptor', res);
@@ -187,9 +189,19 @@ const parseMetadataAsync = (idpMeta) => __awaiter(void 0, void 0, void 0, functi
187
189
  ssoRedirectUrl = rambda.path('$.Location', ssoSvcRec);
188
190
  }
189
191
  }
192
+ const sloSvc = ssoDesRec['SingleLogoutService'] || [];
193
+ for (const sloSvcRec of sloSvc) {
194
+ if (rambda.pathOr('', '$.Binding', sloSvcRec).endsWith('HTTP-Redirect')) {
195
+ sloRedirectUrl = rambda.path('$.Location', sloSvcRec);
196
+ }
197
+ else if (rambda.pathOr('', '$.Binding', sloSvcRec).endsWith('HTTP-POST')) {
198
+ sloPostUrl = rambda.path('$.Location', sloSvcRec);
199
+ }
200
+ }
190
201
  }
191
202
  const ret = {
192
203
  sso: {},
204
+ slo: {},
193
205
  };
194
206
  if (entityID) {
195
207
  ret.entityID = entityID;
@@ -203,9 +215,26 @@ const parseMetadataAsync = (idpMeta) => __awaiter(void 0, void 0, void 0, functi
203
215
  if (ssoRedirectUrl) {
204
216
  ret.sso.redirectUrl = ssoRedirectUrl;
205
217
  }
218
+ if (sloRedirectUrl) {
219
+ ret.slo.redirectUrl = sloRedirectUrl;
220
+ }
221
+ if (sloPostUrl) {
222
+ ret.slo.postUrl = sloPostUrl;
223
+ }
206
224
  ret.loginType = loginType;
207
225
  resolve(ret);
208
226
  });
209
227
  });
210
228
  });
211
- exports.default = { request, parseAsync, validateAsync, parseMetadataAsync };
229
+ const certToPEM = (cert) => {
230
+ if (cert.indexOf('BEGIN CERTIFICATE') === -1 && cert.indexOf('END CERTIFICATE') === -1) {
231
+ const matches = cert.match(/.{1,64}/g);
232
+ if (matches) {
233
+ cert = matches.join('\n');
234
+ cert = '-----BEGIN CERTIFICATE-----\n' + cert;
235
+ cert = cert + '\n-----END CERTIFICATE-----\n';
236
+ }
237
+ }
238
+ return cert;
239
+ };
240
+ exports.default = { request, parseAsync, validateAsync, parseMetadataAsync, PubKeyInfo, certToPEM };
package/dist/typings.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export declare type IdPConfig = {
2
2
  defaultRedirectUrl: string;
3
- redirectUrl: string;
3
+ redirectUrl: string[] | string;
4
4
  tenant: string;
5
5
  product: string;
6
6
  name: string;
@@ -37,6 +37,12 @@ export interface IOAuthController {
37
37
  export interface IAdminController {
38
38
  getAllConfig(pageOffset?: number, pageLimit?: number): any;
39
39
  }
40
+ export interface IHealthCheckController {
41
+ status(): Promise<{
42
+ status: number;
43
+ }>;
44
+ init(): Promise<void>;
45
+ }
40
46
  export interface OAuthReqBody {
41
47
  response_type: 'code';
42
48
  client_id: string;
@@ -131,3 +137,39 @@ export interface JacksonOption {
131
137
  db: DatabaseOption;
132
138
  clientSecretVerifier?: string;
133
139
  }
140
+ export interface SLORequestParams {
141
+ nameId: string;
142
+ tenant: string;
143
+ product: string;
144
+ redirectUrl?: string;
145
+ }
146
+ interface Metadata {
147
+ sso: {
148
+ postUrl?: string;
149
+ redirectUrl: string;
150
+ };
151
+ slo: {
152
+ redirectUrl?: string;
153
+ postUrl?: string;
154
+ };
155
+ entityID: string;
156
+ thumbprint: string;
157
+ loginType: 'idp';
158
+ provider: string;
159
+ }
160
+ export interface SAMLConfig {
161
+ idpMetadata: Metadata;
162
+ certs: {
163
+ privateKey: string;
164
+ publicKey: string;
165
+ };
166
+ defaultRedirectUrl: string;
167
+ }
168
+ export interface ILogoutController {
169
+ createRequest(body: SLORequestParams): Promise<{
170
+ logoutUrl: string | null;
171
+ logoutForm: string | null;
172
+ }>;
173
+ handleResponse(body: SAMLResponsePayload): Promise<any>;
174
+ }
175
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "0.5.1",
3
+ "version": "1.0.0",
4
4
  "description": "SAML Jackson library",
5
5
  "keywords": [
6
6
  "SAML 2.0"
@@ -18,12 +18,12 @@
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 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
- "db:migration:run:postgres": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run",
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
- "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",
21
+ "db:migration:generate:postgres": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/postgres/pg_${MIGRATION_NAME}",
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
+ "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}",
24
+ "db:migration:run:postgres": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
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 -d typeorm.ts",
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 -d typeorm.ts",
27
27
  "prepublishOnly": "npm run build",
28
28
  "test": "tap --ts --timeout=100 --coverage test/**/*.test.ts",
29
29
  "sort": "npx sort-package-json"
@@ -36,7 +36,7 @@
36
36
  "statements": 70
37
37
  },
38
38
  "dependencies": {
39
- "@boxyhq/saml20": "0.2.0",
39
+ "@boxyhq/saml20": "0.2.1",
40
40
  "@opentelemetry/api-metrics": "0.27.0",
41
41
  "@peculiar/webcrypto": "1.3.2",
42
42
  "@peculiar/x509": "1.6.1",
@@ -48,25 +48,25 @@
48
48
  "reflect-metadata": "0.1.13",
49
49
  "ripemd160": "2.0.2",
50
50
  "thumbprint": "0.0.1",
51
- "typeorm": "0.2.45",
51
+ "typeorm": "0.3.3",
52
52
  "xml-crypto": "2.1.3",
53
53
  "xml2js": "0.4.23",
54
54
  "xmlbuilder": "15.1.1"
55
55
  },
56
56
  "devDependencies": {
57
- "@types/node": "17.0.21",
57
+ "@types/node": "17.0.23",
58
58
  "@types/sinon": "10.0.11",
59
59
  "@types/tap": "15.0.6",
60
- "@typescript-eslint/eslint-plugin": "5.15.0",
61
- "@typescript-eslint/parser": "5.15.0",
60
+ "@typescript-eslint/eslint-plugin": "5.16.0",
61
+ "@typescript-eslint/parser": "5.16.0",
62
62
  "cross-env": "7.0.3",
63
63
  "eslint": "8.11.0",
64
64
  "eslint-config-prettier": "8.5.0",
65
65
  "prettier": "2.6.0",
66
66
  "sinon": "13.0.1",
67
- "tap": "16.0.0",
67
+ "tap": "16.0.1",
68
68
  "ts-node": "10.7.0",
69
- "tsconfig-paths": "3.14.0",
69
+ "tsconfig-paths": "3.14.1",
70
70
  "typescript": "4.6.2"
71
71
  },
72
72
  "engines": {