@boxyhq/saml-jackson 0.5.1 → 1.0.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.
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;
@@ -29,7 +29,8 @@ export interface IOAuthController {
29
29
  authorize_form?: string;
30
30
  }>;
31
31
  samlResponse(body: SAMLResponsePayload): Promise<{
32
- redirect_url: string;
32
+ redirect_url?: string;
33
+ app_select_form?: string;
33
34
  }>;
34
35
  token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
35
36
  userInfo(token: string): Promise<Profile>;
@@ -37,20 +38,28 @@ export interface IOAuthController {
37
38
  export interface IAdminController {
38
39
  getAllConfig(pageOffset?: number, pageLimit?: number): any;
39
40
  }
41
+ export interface IHealthCheckController {
42
+ status(): Promise<{
43
+ status: number;
44
+ }>;
45
+ init(): Promise<void>;
46
+ }
40
47
  export interface OAuthReqBody {
41
48
  response_type: 'code';
42
49
  client_id: string;
43
50
  redirect_uri: string;
44
51
  state: string;
45
- tenant: string;
46
- product: string;
52
+ tenant?: string;
53
+ product?: string;
47
54
  code_challenge: string;
48
55
  code_challenge_method: 'plain' | 'S256' | '';
49
56
  provider: 'saml';
57
+ idp_hint?: string;
50
58
  }
51
59
  export interface SAMLResponsePayload {
52
60
  SAMLResponse: string;
53
61
  RelayState: string;
62
+ idp_hint?: string;
54
63
  }
55
64
  export interface OAuthTokenReq {
56
65
  client_id: string;
@@ -105,23 +114,6 @@ export interface DatabaseOption {
105
114
  encryptionKey?: string;
106
115
  pageLimit?: number;
107
116
  }
108
- export interface SAMLReq {
109
- ssoUrl?: string;
110
- entityID: string;
111
- callbackUrl: string;
112
- isPassive?: boolean;
113
- forceAuthn?: boolean;
114
- identifierFormat?: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress';
115
- providerName?: 'BoxyHQ';
116
- signingKey: string;
117
- publicKey: string;
118
- }
119
- export interface SAMLProfile {
120
- audience: string;
121
- claims: Record<string, any>;
122
- issuer: string;
123
- sessionIndex: string;
124
- }
125
117
  export interface JacksonOption {
126
118
  externalUrl: string;
127
119
  samlPath: string;
@@ -130,4 +122,41 @@ export interface JacksonOption {
130
122
  idpEnabled?: boolean;
131
123
  db: DatabaseOption;
132
124
  clientSecretVerifier?: string;
125
+ idpDiscoveryPath?: string;
126
+ }
127
+ export interface SLORequestParams {
128
+ nameId: string;
129
+ tenant: string;
130
+ product: string;
131
+ redirectUrl?: string;
132
+ }
133
+ interface Metadata {
134
+ sso: {
135
+ postUrl?: string;
136
+ redirectUrl: string;
137
+ };
138
+ slo: {
139
+ redirectUrl?: string;
140
+ postUrl?: string;
141
+ };
142
+ entityID: string;
143
+ thumbprint: string;
144
+ loginType: 'idp';
145
+ provider: string;
146
+ }
147
+ export interface SAMLConfig {
148
+ idpMetadata: Metadata;
149
+ certs: {
150
+ privateKey: string;
151
+ publicKey: string;
152
+ };
153
+ defaultRedirectUrl: string;
154
+ }
155
+ export interface ILogoutController {
156
+ createRequest(body: SLORequestParams): Promise<{
157
+ logoutUrl: string | null;
158
+ logoutForm: string | null;
159
+ }>;
160
+ handleResponse(body: SAMLResponsePayload): Promise<any>;
133
161
  }
162
+ 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.2",
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,38 +36,35 @@
36
36
  "statements": 70
37
37
  },
38
38
  "dependencies": {
39
- "@boxyhq/saml20": "0.2.0",
39
+ "@boxyhq/saml20": "1.0.2",
40
40
  "@opentelemetry/api-metrics": "0.27.0",
41
- "@peculiar/webcrypto": "1.3.2",
41
+ "@peculiar/webcrypto": "1.3.3",
42
42
  "@peculiar/x509": "1.6.1",
43
- "mongodb": "4.4.1",
43
+ "mongodb": "4.5.0",
44
44
  "mysql2": "2.3.3",
45
45
  "pg": "8.7.3",
46
- "rambda": "7.0.3",
47
- "redis": "4.0.4",
46
+ "redis": "4.0.6",
48
47
  "reflect-metadata": "0.1.13",
49
48
  "ripemd160": "2.0.2",
50
- "thumbprint": "0.0.1",
51
- "typeorm": "0.2.45",
52
- "xml-crypto": "2.1.3",
49
+ "typeorm": "0.3.6",
53
50
  "xml2js": "0.4.23",
54
51
  "xmlbuilder": "15.1.1"
55
52
  },
56
53
  "devDependencies": {
57
- "@types/node": "17.0.21",
54
+ "@types/node": "17.0.30",
58
55
  "@types/sinon": "10.0.11",
59
- "@types/tap": "15.0.6",
60
- "@typescript-eslint/eslint-plugin": "5.15.0",
61
- "@typescript-eslint/parser": "5.15.0",
56
+ "@types/tap": "15.0.7",
57
+ "@typescript-eslint/eslint-plugin": "5.21.0",
58
+ "@typescript-eslint/parser": "5.21.0",
62
59
  "cross-env": "7.0.3",
63
- "eslint": "8.11.0",
60
+ "eslint": "8.14.0",
64
61
  "eslint-config-prettier": "8.5.0",
65
- "prettier": "2.6.0",
66
- "sinon": "13.0.1",
67
- "tap": "16.0.0",
62
+ "prettier": "2.6.2",
63
+ "sinon": "13.0.2",
64
+ "tap": "16.1.0",
68
65
  "ts-node": "10.7.0",
69
- "tsconfig-paths": "3.14.0",
70
- "typescript": "4.6.2"
66
+ "tsconfig-paths": "3.14.1",
67
+ "typescript": "4.6.4"
71
68
  },
72
69
  "engines": {
73
70
  "node": ">=14.18.1 <=16.x"
@@ -1,12 +0,0 @@
1
- import { SAMLProfile, SAMLReq } from '../typings';
2
- export declare const stripCertHeaderAndFooter: (cert: string) => string;
3
- declare const _default: {
4
- request: ({ ssoUrl, entityID, callbackUrl, isPassive, forceAuthn, identifierFormat, providerName, signingKey, publicKey, }: SAMLReq) => {
5
- id: string;
6
- request: string;
7
- };
8
- parseAsync: (rawAssertion: string) => Promise<SAMLProfile>;
9
- validateAsync: (rawAssertion: string, options: any) => Promise<SAMLProfile>;
10
- parseMetadataAsync: (idpMeta: string) => Promise<Record<string, any>>;
11
- };
12
- export default _default;
package/dist/saml/saml.js DELETED
@@ -1,211 +0,0 @@
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.stripCertHeaderAndFooter = void 0;
39
- const saml20_1 = __importDefault(require("@boxyhq/saml20"));
40
- const xml2js_1 = __importDefault(require("xml2js"));
41
- const thumbprint_1 = __importDefault(require("thumbprint"));
42
- const xml_crypto_1 = __importDefault(require("xml-crypto"));
43
- const rambda = __importStar(require("rambda"));
44
- const xmlbuilder_1 = __importDefault(require("xmlbuilder"));
45
- const crypto_1 = __importDefault(require("crypto"));
46
- const claims_1 = __importDefault(require("./claims"));
47
- const idPrefix = '_';
48
- const authnXPath = '/*[local-name(.)="AuthnRequest" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
49
- const issuerXPath = '/*[local-name(.)="Issuer" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:assertion"]';
50
- const stripCertHeaderAndFooter = (cert) => {
51
- cert = cert.replace(/-+BEGIN CERTIFICATE-+\r?\n?/, '');
52
- cert = cert.replace(/-+END CERTIFICATE-+\r?\n?/, '');
53
- cert = cert.replace(/\r\n/g, '\n');
54
- return cert;
55
- };
56
- exports.stripCertHeaderAndFooter = stripCertHeaderAndFooter;
57
- function PubKeyInfo(pubKey) {
58
- this.pubKey = (0, exports.stripCertHeaderAndFooter)(pubKey);
59
- this.getKeyInfo = function (_key, prefix) {
60
- prefix = prefix || '';
61
- prefix = prefix ? prefix + ':' : prefix;
62
- return `<${prefix}X509Data><${prefix}X509Certificate>${this.pubKey}</${prefix}X509Certificate</${prefix}X509Data>`;
63
- };
64
- }
65
- const signRequest = (xml, signingKey, publicKey) => {
66
- if (!xml) {
67
- throw new Error('Please specify xml');
68
- }
69
- if (!signingKey) {
70
- throw new Error('Please specify signingKey');
71
- }
72
- const sig = new xml_crypto_1.default.SignedXml();
73
- sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
74
- sig.keyInfoProvider = new PubKeyInfo(publicKey);
75
- sig.signingKey = signingKey;
76
- sig.addReference(authnXPath, ['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');
77
- sig.computeSignature(xml, {
78
- location: { reference: authnXPath + issuerXPath, action: 'after' },
79
- });
80
- return sig.getSignedXml();
81
- };
82
- const request = ({ ssoUrl, entityID, callbackUrl, isPassive = false, forceAuthn = false, identifierFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', providerName = 'BoxyHQ', signingKey, publicKey, }) => {
83
- const id = idPrefix + crypto_1.default.randomBytes(10).toString('hex');
84
- const date = new Date().toISOString();
85
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
- const samlReq = {
87
- 'samlp:AuthnRequest': {
88
- '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
89
- '@ID': id,
90
- '@Version': '2.0',
91
- '@IssueInstant': date,
92
- '@ProtocolBinding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
93
- '@Destination': ssoUrl,
94
- 'saml:Issuer': {
95
- '@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
96
- '#text': entityID,
97
- },
98
- },
99
- };
100
- if (isPassive)
101
- samlReq['samlp:AuthnRequest']['@IsPassive'] = true;
102
- if (forceAuthn) {
103
- samlReq['samlp:AuthnRequest']['@ForceAuthn'] = true;
104
- }
105
- samlReq['samlp:AuthnRequest']['@AssertionConsumerServiceURL'] = callbackUrl;
106
- samlReq['samlp:AuthnRequest']['samlp:NameIDPolicy'] = {
107
- '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
108
- '@Format': identifierFormat,
109
- '@AllowCreate': 'true',
110
- };
111
- if (providerName != null) {
112
- samlReq['samlp:AuthnRequest']['@ProviderName'] = providerName;
113
- }
114
- let xml = xmlbuilder_1.default.create(samlReq).end({});
115
- if (signingKey) {
116
- xml = signRequest(xml, signingKey, publicKey);
117
- }
118
- return {
119
- id,
120
- request: xml,
121
- };
122
- };
123
- const parseAsync = (rawAssertion) => __awaiter(void 0, void 0, void 0, function* () {
124
- return new Promise((resolve, reject) => {
125
- saml20_1.default.parse(rawAssertion, function onParseAsync(err, profile) {
126
- if (err) {
127
- reject(err);
128
- return;
129
- }
130
- resolve(profile);
131
- });
132
- });
133
- });
134
- const validateAsync = (rawAssertion, options) => __awaiter(void 0, void 0, void 0, function* () {
135
- return new Promise((resolve, reject) => {
136
- saml20_1.default.validate(rawAssertion, options, function onValidateAsync(err, profile) {
137
- if (err) {
138
- reject(err);
139
- return;
140
- }
141
- if (profile && profile.claims) {
142
- // we map claims to our attributes id, email, firstName, lastName where possible. We also map original claims to raw
143
- profile.claims = claims_1.default.map(profile.claims);
144
- // some providers don't return the id in the assertion, we set it to a sha256 hash of the email
145
- if (!profile.claims.id) {
146
- profile.claims.id = crypto_1.default.createHash('sha256').update(profile.claims.email).digest('hex');
147
- }
148
- }
149
- resolve(profile);
150
- });
151
- });
152
- });
153
- const parseMetadataAsync = (idpMeta) => __awaiter(void 0, void 0, void 0, function* () {
154
- return new Promise((resolve, reject) => {
155
- xml2js_1.default.parseString(idpMeta, { tagNameProcessors: [xml2js_1.default.processors.stripPrefix] }, (err, res) => {
156
- if (err) {
157
- reject(err);
158
- return;
159
- }
160
- const entityID = rambda.path('EntityDescriptor.$.entityID', res);
161
- let X509Certificate = null;
162
- let ssoPostUrl = null;
163
- let ssoRedirectUrl = null;
164
- let loginType = 'idp';
165
- let ssoDes = rambda.pathOr(null, 'EntityDescriptor.IDPSSODescriptor', res);
166
- if (!ssoDes) {
167
- ssoDes = rambda.pathOr([], 'EntityDescriptor.SPSSODescriptor', res);
168
- if (!ssoDes) {
169
- loginType = 'sp';
170
- }
171
- }
172
- for (const ssoDesRec of ssoDes) {
173
- const keyDes = ssoDesRec['KeyDescriptor'];
174
- for (const keyDesRec of keyDes) {
175
- if (keyDesRec['$'] && keyDesRec['$'].use === 'signing') {
176
- const ki = keyDesRec['KeyInfo'][0];
177
- const cd = ki['X509Data'][0];
178
- X509Certificate = cd['X509Certificate'][0];
179
- }
180
- }
181
- const ssoSvc = ssoDesRec['SingleSignOnService'] || ssoDesRec['AssertionConsumerService'] || [];
182
- for (const ssoSvcRec of ssoSvc) {
183
- if (rambda.pathOr('', '$.Binding', ssoSvcRec).endsWith('HTTP-POST')) {
184
- ssoPostUrl = rambda.path('$.Location', ssoSvcRec);
185
- }
186
- else if (rambda.pathOr('', '$.Binding', ssoSvcRec).endsWith('HTTP-Redirect')) {
187
- ssoRedirectUrl = rambda.path('$.Location', ssoSvcRec);
188
- }
189
- }
190
- }
191
- const ret = {
192
- sso: {},
193
- };
194
- if (entityID) {
195
- ret.entityID = entityID;
196
- }
197
- if (X509Certificate) {
198
- ret.thumbprint = thumbprint_1.default.calculate(X509Certificate);
199
- }
200
- if (ssoPostUrl) {
201
- ret.sso.postUrl = ssoPostUrl;
202
- }
203
- if (ssoRedirectUrl) {
204
- ret.sso.redirectUrl = ssoRedirectUrl;
205
- }
206
- ret.loginType = loginType;
207
- resolve(ret);
208
- });
209
- });
210
- });
211
- exports.default = { request, parseAsync, validateAsync, parseMetadataAsync };