@boxyhq/saml-jackson 0.2.3 → 0.3.0-beta.247
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/Dockerfile +9 -7
- package/README.md +1 -2
- package/dist/controller/api.d.ts +32 -0
- package/dist/controller/api.js +193 -0
- package/dist/controller/error.d.ts +5 -0
- package/dist/controller/error.js +12 -0
- package/dist/controller/oauth/allowed.d.ts +1 -0
- package/dist/controller/oauth/allowed.js +17 -0
- package/dist/controller/oauth/code-verifier.d.ts +2 -0
- package/dist/controller/oauth/code-verifier.js +15 -0
- package/dist/controller/oauth/redirect.d.ts +1 -0
- package/dist/controller/oauth/redirect.js +11 -0
- package/dist/controller/oauth.d.ts +23 -0
- package/dist/controller/oauth.js +263 -0
- package/dist/controller/utils.d.ts +6 -0
- package/dist/controller/utils.js +17 -0
- package/dist/db/db.d.ts +15 -0
- package/dist/db/db.js +107 -0
- package/dist/db/encrypter.d.ts +3 -0
- package/dist/db/encrypter.js +29 -0
- package/dist/db/mem.d.ts +20 -0
- package/dist/db/mem.js +128 -0
- package/dist/db/mongo.d.ts +17 -0
- package/dist/db/mongo.js +106 -0
- package/dist/db/redis.d.ts +15 -0
- package/dist/db/redis.js +107 -0
- package/dist/db/sql/entity/JacksonIndex.d.ts +7 -0
- package/dist/db/sql/entity/JacksonIndex.js +41 -0
- package/dist/db/sql/entity/JacksonStore.d.ts +6 -0
- package/dist/db/sql/entity/JacksonStore.js +42 -0
- package/dist/db/sql/entity/JacksonTTL.d.ts +4 -0
- package/dist/db/sql/entity/JacksonTTL.js +29 -0
- package/dist/db/sql/sql.d.ts +20 -0
- package/dist/db/sql/sql.js +174 -0
- package/dist/db/store.d.ts +5 -0
- package/dist/db/store.js +68 -0
- package/dist/db/utils.d.ts +7 -0
- package/dist/db/utils.js +29 -0
- package/dist/env.d.ts +22 -0
- package/dist/env.js +35 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +82 -0
- package/dist/jackson.d.ts +1 -0
- package/dist/jackson.js +153 -0
- package/dist/read-config.d.ts +3 -0
- package/dist/read-config.js +50 -0
- package/dist/saml/claims.d.ts +6 -0
- package/dist/saml/claims.js +35 -0
- package/dist/saml/saml.d.ts +11 -0
- package/dist/saml/saml.js +200 -0
- package/dist/saml/x509.d.ts +7 -0
- package/dist/saml/x509.js +69 -0
- package/dist/typings.d.ts +137 -0
- package/dist/typings.js +2 -0
- package/package.json +41 -21
- package/.dockerignore +0 -2
- package/.eslintrc.js +0 -13
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -43
- package/.github/pull_request_template.md +0 -31
- package/.github/workflows/codesee-arch-diagram.yml +0 -81
- package/.github/workflows/main.yml +0 -123
- package/_dev/docker-compose.yml +0 -37
- package/map.js +0 -1
- package/prettier.config.js +0 -4
- package/src/controller/api.js +0 -167
- package/src/controller/error.js +0 -12
- package/src/controller/oauth/allowed.js +0 -19
- package/src/controller/oauth/code-verifier.js +0 -16
- package/src/controller/oauth/redirect.js +0 -18
- package/src/controller/oauth.js +0 -321
- package/src/controller/utils.js +0 -19
- package/src/db/db.js +0 -81
- package/src/db/db.test.js +0 -302
- package/src/db/encrypter.js +0 -36
- package/src/db/mem.js +0 -111
- package/src/db/mongo.js +0 -89
- package/src/db/redis.js +0 -88
- package/src/db/sql/entity/JacksonIndex.js +0 -42
- package/src/db/sql/entity/JacksonStore.js +0 -42
- package/src/db/sql/entity/JacksonTTL.js +0 -23
- package/src/db/sql/model/JacksonIndex.js +0 -9
- package/src/db/sql/model/JacksonStore.js +0 -10
- package/src/db/sql/model/JacksonTTL.js +0 -8
- package/src/db/sql/sql.js +0 -153
- package/src/db/store.js +0 -42
- package/src/db/utils.js +0 -30
- package/src/env.js +0 -39
- package/src/index.js +0 -67
- package/src/jackson.js +0 -161
- package/src/read-config.js +0 -24
- package/src/saml/claims.js +0 -40
- package/src/saml/saml.js +0 -223
- package/src/saml/x509.js +0 -48
- package/src/test/api.test.js +0 -186
- package/src/test/data/metadata/boxyhq.js +0 -6
- package/src/test/data/metadata/boxyhq.xml +0 -30
- package/src/test/data/saml_response +0 -1
- package/src/test/oauth.test.js +0 -342
@@ -0,0 +1,50 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
5
|
+
}) : (function(o, m, k, k2) {
|
6
|
+
if (k2 === undefined) k2 = k;
|
7
|
+
o[k2] = m[k];
|
8
|
+
}));
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
11
|
+
}) : function(o, v) {
|
12
|
+
o["default"] = v;
|
13
|
+
});
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
15
|
+
if (mod && mod.__esModule) return mod;
|
16
|
+
var result = {};
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
18
|
+
__setModuleDefault(result, mod);
|
19
|
+
return result;
|
20
|
+
};
|
21
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
22
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
23
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
24
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
25
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
26
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
27
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
28
|
+
});
|
29
|
+
};
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
31
|
+
const fs = __importStar(require("fs"));
|
32
|
+
const path = __importStar(require("path"));
|
33
|
+
const readConfig = (preLoadedConfig) => __awaiter(void 0, void 0, void 0, function* () {
|
34
|
+
if (preLoadedConfig.startsWith('./')) {
|
35
|
+
preLoadedConfig = path.resolve(process.cwd(), preLoadedConfig);
|
36
|
+
}
|
37
|
+
const files = yield fs.promises.readdir(preLoadedConfig);
|
38
|
+
const configs = [];
|
39
|
+
for (const idx in files) {
|
40
|
+
const file = files[idx];
|
41
|
+
if (file.endsWith('.js')) {
|
42
|
+
const config = require(path.join(preLoadedConfig, file));
|
43
|
+
const rawMetadata = yield fs.promises.readFile(path.join(preLoadedConfig, path.parse(file).name + '.xml'), 'utf8');
|
44
|
+
config.rawMetadata = rawMetadata;
|
45
|
+
configs.push(config);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
return configs;
|
49
|
+
});
|
50
|
+
exports.default = readConfig;
|
@@ -0,0 +1,6 @@
|
|
1
|
+
declare const _default: {
|
2
|
+
map: (claims: Record<"id" | "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" | "email" | "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" | "firstName" | "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" | "lastName" | "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", unknown>) => {
|
3
|
+
raw: Record<"id" | "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" | "email" | "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" | "firstName" | "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" | "lastName" | "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", unknown>;
|
4
|
+
};
|
5
|
+
};
|
6
|
+
export default _default;
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const mapping = [
|
4
|
+
{
|
5
|
+
attribute: 'id',
|
6
|
+
schema: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier',
|
7
|
+
},
|
8
|
+
{
|
9
|
+
attribute: 'email',
|
10
|
+
schema: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
|
11
|
+
},
|
12
|
+
{
|
13
|
+
attribute: 'firstName',
|
14
|
+
schema: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',
|
15
|
+
},
|
16
|
+
{
|
17
|
+
attribute: 'lastName',
|
18
|
+
schema: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',
|
19
|
+
},
|
20
|
+
];
|
21
|
+
const map = (claims) => {
|
22
|
+
const profile = {
|
23
|
+
raw: claims,
|
24
|
+
};
|
25
|
+
mapping.forEach((m) => {
|
26
|
+
if (claims[m.attribute]) {
|
27
|
+
profile[m.attribute] = claims[m.attribute];
|
28
|
+
}
|
29
|
+
else if (claims[m.schema]) {
|
30
|
+
profile[m.attribute] = claims[m.schema];
|
31
|
+
}
|
32
|
+
});
|
33
|
+
return profile;
|
34
|
+
};
|
35
|
+
exports.default = { map };
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { SAMLProfile, SAMLReq } from '../typings';
|
2
|
+
declare const _default: {
|
3
|
+
request: ({ ssoUrl, entityID, callbackUrl, isPassive, forceAuthn, identifierFormat, providerName, signingKey, }: SAMLReq) => {
|
4
|
+
id: string;
|
5
|
+
request: string;
|
6
|
+
};
|
7
|
+
parseAsync: (rawAssertion: string) => Promise<SAMLProfile>;
|
8
|
+
validateAsync: (rawAssertion: string, options: any) => Promise<SAMLProfile>;
|
9
|
+
parseMetadataAsync: (idpMeta: string) => Promise<Record<string, any>>;
|
10
|
+
};
|
11
|
+
export default _default;
|
@@ -0,0 +1,200 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
5
|
+
}) : (function(o, m, k, k2) {
|
6
|
+
if (k2 === undefined) k2 = k;
|
7
|
+
o[k2] = m[k];
|
8
|
+
}));
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
11
|
+
}) : function(o, v) {
|
12
|
+
o["default"] = v;
|
13
|
+
});
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
15
|
+
if (mod && mod.__esModule) return mod;
|
16
|
+
var result = {};
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
18
|
+
__setModuleDefault(result, mod);
|
19
|
+
return result;
|
20
|
+
};
|
21
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
22
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
23
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
24
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
25
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
26
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
27
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
28
|
+
});
|
29
|
+
};
|
30
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
31
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
32
|
+
};
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
34
|
+
const saml20_1 = __importDefault(require("@boxyhq/saml20"));
|
35
|
+
const xml2js_1 = __importDefault(require("xml2js"));
|
36
|
+
const thumbprint_1 = __importDefault(require("thumbprint"));
|
37
|
+
const xml_crypto_1 = __importDefault(require("xml-crypto"));
|
38
|
+
const rambda = __importStar(require("rambda"));
|
39
|
+
const xmlbuilder_1 = __importDefault(require("xmlbuilder"));
|
40
|
+
const crypto_1 = __importDefault(require("crypto"));
|
41
|
+
const claims_1 = __importDefault(require("./claims"));
|
42
|
+
const idPrefix = '_';
|
43
|
+
const authnXPath = '/*[local-name(.)="AuthnRequest" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:protocol"]';
|
44
|
+
const issuerXPath = '/*[local-name(.)="Issuer" and namespace-uri(.)="urn:oasis:names:tc:SAML:2.0:assertion"]';
|
45
|
+
const signRequest = (xml, signingKey) => {
|
46
|
+
if (!xml) {
|
47
|
+
throw new Error('Please specify xml');
|
48
|
+
}
|
49
|
+
if (!signingKey) {
|
50
|
+
throw new Error('Please specify signingKey');
|
51
|
+
}
|
52
|
+
const sig = new xml_crypto_1.default.SignedXml();
|
53
|
+
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
54
|
+
sig.signingKey = signingKey;
|
55
|
+
sig.addReference(authnXPath, [
|
56
|
+
'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
|
57
|
+
'http://www.w3.org/2001/10/xml-exc-c14n#',
|
58
|
+
], 'http://www.w3.org/2001/04/xmlenc#sha256');
|
59
|
+
sig.computeSignature(xml, {
|
60
|
+
location: { reference: authnXPath + issuerXPath, action: 'after' },
|
61
|
+
});
|
62
|
+
return sig.getSignedXml();
|
63
|
+
};
|
64
|
+
const request = ({ ssoUrl, entityID, callbackUrl, isPassive = false, forceAuthn = false, identifierFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', providerName = 'BoxyHQ', signingKey, }) => {
|
65
|
+
const id = idPrefix + crypto_1.default.randomBytes(10).toString('hex');
|
66
|
+
const date = new Date().toISOString();
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
68
|
+
const samlReq = {
|
69
|
+
'samlp:AuthnRequest': {
|
70
|
+
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
71
|
+
'@ID': id,
|
72
|
+
'@Version': '2.0',
|
73
|
+
'@IssueInstant': date,
|
74
|
+
'@ProtocolBinding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
|
75
|
+
'@Destination': ssoUrl,
|
76
|
+
'saml:Issuer': {
|
77
|
+
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
78
|
+
'#text': entityID,
|
79
|
+
},
|
80
|
+
},
|
81
|
+
};
|
82
|
+
if (isPassive)
|
83
|
+
samlReq['samlp:AuthnRequest']['@IsPassive'] = true;
|
84
|
+
if (forceAuthn) {
|
85
|
+
samlReq['samlp:AuthnRequest']['@ForceAuthn'] = true;
|
86
|
+
}
|
87
|
+
samlReq['samlp:AuthnRequest']['@AssertionConsumerServiceURL'] = callbackUrl;
|
88
|
+
samlReq['samlp:AuthnRequest']['samlp:NameIDPolicy'] = {
|
89
|
+
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
90
|
+
'@Format': identifierFormat,
|
91
|
+
'@AllowCreate': 'true',
|
92
|
+
};
|
93
|
+
if (providerName != null) {
|
94
|
+
samlReq['samlp:AuthnRequest']['@ProviderName'] = providerName;
|
95
|
+
}
|
96
|
+
let xml = xmlbuilder_1.default.create(samlReq).end({});
|
97
|
+
if (signingKey) {
|
98
|
+
xml = signRequest(xml, signingKey);
|
99
|
+
}
|
100
|
+
return {
|
101
|
+
id,
|
102
|
+
request: xml,
|
103
|
+
};
|
104
|
+
};
|
105
|
+
const parseAsync = (rawAssertion) => __awaiter(void 0, void 0, void 0, function* () {
|
106
|
+
return new Promise((resolve, reject) => {
|
107
|
+
saml20_1.default.parse(rawAssertion, function onParseAsync(err, profile) {
|
108
|
+
if (err) {
|
109
|
+
reject(err);
|
110
|
+
return;
|
111
|
+
}
|
112
|
+
resolve(profile);
|
113
|
+
});
|
114
|
+
});
|
115
|
+
});
|
116
|
+
const validateAsync = (rawAssertion, options) => __awaiter(void 0, void 0, void 0, function* () {
|
117
|
+
return new Promise((resolve, reject) => {
|
118
|
+
saml20_1.default.validate(rawAssertion, options, function onValidateAsync(err, profile) {
|
119
|
+
if (err) {
|
120
|
+
reject(err);
|
121
|
+
return;
|
122
|
+
}
|
123
|
+
if (profile && profile.claims) {
|
124
|
+
// we map claims to our attributes id, email, firstName, lastName where possible. We also map original claims to raw
|
125
|
+
profile.claims = claims_1.default.map(profile.claims);
|
126
|
+
// some providers don't return the id in the assertion, we set it to a sha256 hash of the email
|
127
|
+
if (!profile.claims.id) {
|
128
|
+
profile.claims.id = crypto_1.default
|
129
|
+
.createHash('sha256')
|
130
|
+
.update(profile.claims.email)
|
131
|
+
.digest('hex');
|
132
|
+
}
|
133
|
+
}
|
134
|
+
resolve(profile);
|
135
|
+
});
|
136
|
+
});
|
137
|
+
});
|
138
|
+
const parseMetadataAsync = (idpMeta) => __awaiter(void 0, void 0, void 0, function* () {
|
139
|
+
return new Promise((resolve, reject) => {
|
140
|
+
xml2js_1.default.parseString(idpMeta, { tagNameProcessors: [xml2js_1.default.processors.stripPrefix] }, (err, res) => {
|
141
|
+
if (err) {
|
142
|
+
reject(err);
|
143
|
+
return;
|
144
|
+
}
|
145
|
+
const entityID = rambda.path('EntityDescriptor.$.entityID', res);
|
146
|
+
let X509Certificate = null;
|
147
|
+
let ssoPostUrl = null;
|
148
|
+
let ssoRedirectUrl = null;
|
149
|
+
let loginType = 'idp';
|
150
|
+
let ssoDes = rambda.pathOr(null, 'EntityDescriptor.IDPSSODescriptor', res);
|
151
|
+
if (!ssoDes) {
|
152
|
+
ssoDes = rambda.pathOr([], 'EntityDescriptor.SPSSODescriptor', res);
|
153
|
+
if (!ssoDes) {
|
154
|
+
loginType = 'sp';
|
155
|
+
}
|
156
|
+
}
|
157
|
+
for (const ssoDesRec of ssoDes) {
|
158
|
+
const keyDes = ssoDesRec['KeyDescriptor'];
|
159
|
+
for (const keyDesRec of keyDes) {
|
160
|
+
if (keyDesRec['$'] && keyDesRec['$'].use === 'signing') {
|
161
|
+
const ki = keyDesRec['KeyInfo'][0];
|
162
|
+
const cd = ki['X509Data'][0];
|
163
|
+
X509Certificate = cd['X509Certificate'][0];
|
164
|
+
}
|
165
|
+
}
|
166
|
+
const ssoSvc = ssoDesRec['SingleSignOnService'] ||
|
167
|
+
ssoDesRec['AssertionConsumerService'] ||
|
168
|
+
[];
|
169
|
+
for (const ssoSvcRec of ssoSvc) {
|
170
|
+
if (rambda.pathOr('', '$.Binding', ssoSvcRec).endsWith('HTTP-POST')) {
|
171
|
+
ssoPostUrl = rambda.path('$.Location', ssoSvcRec);
|
172
|
+
}
|
173
|
+
else if (rambda
|
174
|
+
.pathOr('', '$.Binding', ssoSvcRec)
|
175
|
+
.endsWith('HTTP-Redirect')) {
|
176
|
+
ssoRedirectUrl = rambda.path('$.Location', ssoSvcRec);
|
177
|
+
}
|
178
|
+
}
|
179
|
+
}
|
180
|
+
const ret = {
|
181
|
+
sso: {},
|
182
|
+
};
|
183
|
+
if (entityID) {
|
184
|
+
ret.entityID = entityID;
|
185
|
+
}
|
186
|
+
if (X509Certificate) {
|
187
|
+
ret.thumbprint = thumbprint_1.default.calculate(X509Certificate);
|
188
|
+
}
|
189
|
+
if (ssoPostUrl) {
|
190
|
+
ret.sso.postUrl = ssoPostUrl;
|
191
|
+
}
|
192
|
+
if (ssoRedirectUrl) {
|
193
|
+
ret.sso.redirectUrl = ssoRedirectUrl;
|
194
|
+
}
|
195
|
+
ret.loginType = loginType;
|
196
|
+
resolve(ret);
|
197
|
+
});
|
198
|
+
});
|
199
|
+
});
|
200
|
+
exports.default = { request, parseAsync, validateAsync, parseMetadataAsync };
|
@@ -0,0 +1,69 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
5
|
+
}) : (function(o, m, k, k2) {
|
6
|
+
if (k2 === undefined) k2 = k;
|
7
|
+
o[k2] = m[k];
|
8
|
+
}));
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
11
|
+
}) : function(o, v) {
|
12
|
+
o["default"] = v;
|
13
|
+
});
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
15
|
+
if (mod && mod.__esModule) return mod;
|
16
|
+
var result = {};
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
18
|
+
__setModuleDefault(result, mod);
|
19
|
+
return result;
|
20
|
+
};
|
21
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
22
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
23
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
24
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
25
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
26
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
27
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
28
|
+
});
|
29
|
+
};
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
31
|
+
const x509 = __importStar(require("@peculiar/x509"));
|
32
|
+
const webcrypto_1 = require("@peculiar/webcrypto");
|
33
|
+
const crypto = new webcrypto_1.Crypto();
|
34
|
+
x509.cryptoProvider.set(crypto);
|
35
|
+
const alg = {
|
36
|
+
name: 'RSASSA-PKCS1-v1_5',
|
37
|
+
hash: 'SHA-256',
|
38
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
39
|
+
modulusLength: 2048,
|
40
|
+
};
|
41
|
+
const generate = () => __awaiter(void 0, void 0, void 0, function* () {
|
42
|
+
const keys = yield crypto.subtle.generateKey(alg, true, ['sign', 'verify']);
|
43
|
+
const extensions = [
|
44
|
+
new x509.BasicConstraintsExtension(false, undefined, true),
|
45
|
+
];
|
46
|
+
extensions.push(new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true));
|
47
|
+
if (keys.publicKey) {
|
48
|
+
extensions.push(yield x509.SubjectKeyIdentifierExtension.create(keys.publicKey));
|
49
|
+
}
|
50
|
+
const cert = yield x509.X509CertificateGenerator.createSelfSigned({
|
51
|
+
serialNumber: '01',
|
52
|
+
name: 'CN=BoxyHQ Jackson',
|
53
|
+
notBefore: new Date(),
|
54
|
+
notAfter: new Date('3021/01/01'),
|
55
|
+
signingAlgorithm: alg,
|
56
|
+
keys: keys,
|
57
|
+
extensions,
|
58
|
+
});
|
59
|
+
if (keys.privateKey) {
|
60
|
+
const pkcs8 = yield crypto.subtle.exportKey('pkcs8', keys.privateKey);
|
61
|
+
return {
|
62
|
+
publicKey: cert.toString('pem'),
|
63
|
+
privateKey: x509.PemConverter.encode(pkcs8, 'private key'),
|
64
|
+
};
|
65
|
+
}
|
66
|
+
});
|
67
|
+
exports.default = {
|
68
|
+
generate,
|
69
|
+
};
|
@@ -0,0 +1,137 @@
|
|
1
|
+
export declare type IdPConfig = {
|
2
|
+
defaultRedirectUrl: string;
|
3
|
+
redirectUrl: string;
|
4
|
+
tenant: string;
|
5
|
+
product: string;
|
6
|
+
rawMetadata: string;
|
7
|
+
};
|
8
|
+
export interface OAuth {
|
9
|
+
client_id: string;
|
10
|
+
client_secret: string;
|
11
|
+
provider: string;
|
12
|
+
}
|
13
|
+
export interface ISAMLConfig {
|
14
|
+
config(body: IdPConfig): Promise<OAuth>;
|
15
|
+
getConfig(body: {
|
16
|
+
clientID: string;
|
17
|
+
tenant: string;
|
18
|
+
product: string;
|
19
|
+
}): Promise<Partial<OAuth>>;
|
20
|
+
deleteConfig(body: {
|
21
|
+
clientID: string;
|
22
|
+
clientSecret: string;
|
23
|
+
tenant: string;
|
24
|
+
product: string;
|
25
|
+
}): Promise<void>;
|
26
|
+
create(body: IdPConfig): Promise<OAuth>;
|
27
|
+
get(body: {
|
28
|
+
clientID: string;
|
29
|
+
tenant: string;
|
30
|
+
product: string;
|
31
|
+
}): Promise<Partial<OAuth>>;
|
32
|
+
delete(body: {
|
33
|
+
clientID: string;
|
34
|
+
clientSecret: string;
|
35
|
+
tenant: string;
|
36
|
+
product: string;
|
37
|
+
}): Promise<void>;
|
38
|
+
}
|
39
|
+
export interface IOAuthController {
|
40
|
+
authorize(body: OAuthReqBody): Promise<{
|
41
|
+
redirect_url: string;
|
42
|
+
}>;
|
43
|
+
samlResponse(body: SAMLResponsePayload): Promise<{
|
44
|
+
redirect_url: string;
|
45
|
+
}>;
|
46
|
+
token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
|
47
|
+
userInfo(token: string): Promise<Profile>;
|
48
|
+
}
|
49
|
+
export interface OAuthReqBody {
|
50
|
+
response_type: 'code';
|
51
|
+
client_id: string;
|
52
|
+
redirect_uri: string;
|
53
|
+
state: string;
|
54
|
+
tenant: string;
|
55
|
+
product: string;
|
56
|
+
code_challenge: string;
|
57
|
+
code_challenge_method: 'plain' | 'S256' | '';
|
58
|
+
provider: 'saml';
|
59
|
+
}
|
60
|
+
export interface SAMLResponsePayload {
|
61
|
+
SAMLResponse: string;
|
62
|
+
RelayState: string;
|
63
|
+
}
|
64
|
+
export interface OAuthTokenReq {
|
65
|
+
client_id: string;
|
66
|
+
client_secret: string;
|
67
|
+
code_verifier: string;
|
68
|
+
code: string;
|
69
|
+
grant_type: 'authorization_code';
|
70
|
+
}
|
71
|
+
export interface OAuthTokenRes {
|
72
|
+
access_token: string;
|
73
|
+
token_type: 'bearer';
|
74
|
+
expires_in: number;
|
75
|
+
}
|
76
|
+
export interface Profile {
|
77
|
+
id: string;
|
78
|
+
email: string;
|
79
|
+
firstName: string;
|
80
|
+
lastName: string;
|
81
|
+
}
|
82
|
+
export interface Index {
|
83
|
+
name: string;
|
84
|
+
value: string;
|
85
|
+
}
|
86
|
+
export interface DatabaseDriver {
|
87
|
+
get(namespace: string, key: string): Promise<any>;
|
88
|
+
put(namespace: string, key: string, val: any, ttl: number, ...indexes: Index[]): Promise<any>;
|
89
|
+
delete(namespace: string, key: string): Promise<any>;
|
90
|
+
getByIndex(namespace: string, idx: Index): Promise<any>;
|
91
|
+
}
|
92
|
+
export interface Storable {
|
93
|
+
get(key: string): Promise<any>;
|
94
|
+
put(key: string, val: any, ...indexes: Index[]): Promise<any>;
|
95
|
+
delete(key: string): Promise<any>;
|
96
|
+
getByIndex(idx: Index): Promise<any>;
|
97
|
+
}
|
98
|
+
export interface Encrypted {
|
99
|
+
iv?: string;
|
100
|
+
tag?: string;
|
101
|
+
value: string;
|
102
|
+
}
|
103
|
+
export declare type EncryptionKey = any;
|
104
|
+
export declare type DatabaseEngine = 'redis' | 'sql' | 'mongo' | 'mem';
|
105
|
+
export declare type DatabaseType = 'postgres' | 'mysql' | 'mariadb';
|
106
|
+
export interface DatabaseOption {
|
107
|
+
engine: DatabaseEngine;
|
108
|
+
url: string;
|
109
|
+
type: DatabaseType;
|
110
|
+
ttl: number;
|
111
|
+
cleanupLimit: number;
|
112
|
+
encryptionKey: string;
|
113
|
+
}
|
114
|
+
export interface SAMLReq {
|
115
|
+
ssoUrl?: string;
|
116
|
+
entityID: string;
|
117
|
+
callbackUrl: string;
|
118
|
+
isPassive?: boolean;
|
119
|
+
forceAuthn?: boolean;
|
120
|
+
identifierFormat?: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress';
|
121
|
+
providerName?: 'BoxyHQ';
|
122
|
+
signingKey: string;
|
123
|
+
}
|
124
|
+
export interface SAMLProfile {
|
125
|
+
audience: string;
|
126
|
+
claims: Record<string, any>;
|
127
|
+
issuer: string;
|
128
|
+
sessionIndex: string;
|
129
|
+
}
|
130
|
+
export interface JacksonOption {
|
131
|
+
externalUrl: string;
|
132
|
+
samlPath: string;
|
133
|
+
samlAudience: string;
|
134
|
+
preLoadedConfig?: string;
|
135
|
+
idpEnabled?: boolean;
|
136
|
+
db: DatabaseOption;
|
137
|
+
}
|
package/dist/typings.js
ADDED
package/package.json
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.3.0-beta.247",
|
4
4
|
"license": "Apache 2.0",
|
5
5
|
"description": "SAML 2.0 service",
|
6
|
-
"main": "
|
6
|
+
"main": "dist/index.js",
|
7
|
+
"types": "dist/index.d.ts",
|
7
8
|
"engines": {
|
8
9
|
"node": ">=14.18.1"
|
9
10
|
},
|
@@ -15,14 +16,18 @@
|
|
15
16
|
"SAML 2.0"
|
16
17
|
],
|
17
18
|
"scripts": {
|
18
|
-
"
|
19
|
-
"
|
20
|
-
"
|
21
|
-
"
|
22
|
-
"
|
23
|
-
"
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
20
|
+
"prepublishOnly": "npm run build",
|
21
|
+
"start": "cross-env IDP_ENABLED=true node dist/jackson.js",
|
22
|
+
"dev": "cross-env IDP_ENABLED=true nodemon --config nodemon.json src/jackson.ts",
|
23
|
+
"mongo": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson nodemon --config nodemon.json src/jackson.ts",
|
24
|
+
"sql": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=sql DB_TYPE=postgres DB_URL=postgres://postgres:postgres@localhost:5432/jackson nodemon --config nodemon.json src/jackson.ts",
|
25
|
+
"pre-loaded": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mem PRE_LOADED_CONFIG='./_config' nodemon --config nodemon.json src/jackson.ts",
|
26
|
+
"pre-loaded-db": "cross-env JACKSON_API_KEYS=secret PRE_LOADED_CONFIG='./_config' nodemon --config nodemon.json src/jackson.ts",
|
27
|
+
"test": "tap --ts --timeout=100 --coverage src/**/*.test.ts ",
|
24
28
|
"dev-dbs": "docker-compose -f ./_dev/docker-compose.yml up -d",
|
25
|
-
"dev-dbs-destroy": "docker-compose -f ./_dev/docker-compose.yml down --volumes --remove-orphans"
|
29
|
+
"dev-dbs-destroy": "docker-compose -f ./_dev/docker-compose.yml down --volumes --remove-orphans",
|
30
|
+
"db:migration:generate": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n Initial"
|
26
31
|
},
|
27
32
|
"tap": {
|
28
33
|
"coverage-map": "map.js",
|
@@ -33,15 +38,15 @@
|
|
33
38
|
},
|
34
39
|
"dependencies": {
|
35
40
|
"@boxyhq/saml20": "0.2.0",
|
36
|
-
"@peculiar/webcrypto": "1.2.
|
37
|
-
"@peculiar/x509": "1.6.
|
41
|
+
"@peculiar/webcrypto": "1.2.3",
|
42
|
+
"@peculiar/x509": "1.6.1",
|
38
43
|
"cors": "2.8.5",
|
39
|
-
"express": "4.17.
|
40
|
-
"mongodb": "4.2.
|
44
|
+
"express": "4.17.2",
|
45
|
+
"mongodb": "4.2.2",
|
41
46
|
"mysql2": "2.3.3",
|
42
47
|
"pg": "8.7.1",
|
43
48
|
"rambda": "6.9.0",
|
44
|
-
"redis": "4.0.
|
49
|
+
"redis": "4.0.1",
|
45
50
|
"reflect-metadata": "0.1.13",
|
46
51
|
"ripemd160": "2.0.2",
|
47
52
|
"thumbprint": "0.0.1",
|
@@ -51,17 +56,32 @@
|
|
51
56
|
"xmlbuilder": "15.1.1"
|
52
57
|
},
|
53
58
|
"devDependencies": {
|
59
|
+
"@types/express": "^4.17.13",
|
60
|
+
"@types/node": "^16.11.17",
|
61
|
+
"@types/redis": "4.0.11",
|
62
|
+
"@types/sinon": "10.0.6",
|
63
|
+
"@types/tap": "15.0.5",
|
64
|
+
"@typescript-eslint/eslint-plugin": "^5.8.1",
|
65
|
+
"@typescript-eslint/parser": "^5.8.1",
|
54
66
|
"cross-env": "7.0.3",
|
55
|
-
"eslint": "8.
|
67
|
+
"eslint": "^8.5.0",
|
68
|
+
"eslint-config-prettier": "^8.3.0",
|
56
69
|
"husky": "7.0.4",
|
57
|
-
"lint-staged": "12.1.
|
70
|
+
"lint-staged": "12.1.4",
|
58
71
|
"nodemon": "2.0.15",
|
59
72
|
"prettier": "2.5.1",
|
60
73
|
"sinon": "12.0.1",
|
61
|
-
"tap": "15.1.5"
|
74
|
+
"tap": "15.1.5",
|
75
|
+
"ts-node": "10.4.0",
|
76
|
+
"tsconfig-paths": "3.12.0",
|
77
|
+
"typescript": "4.5.4"
|
62
78
|
},
|
63
79
|
"lint-staged": {
|
64
|
-
"*.js": "eslint --cache --fix",
|
65
|
-
"*.{js,css,md}": "prettier --write"
|
66
|
-
}
|
67
|
-
|
80
|
+
"*.{js,ts}": "eslint --cache --fix",
|
81
|
+
"*.{js,ts,css,md}": "prettier --write"
|
82
|
+
},
|
83
|
+
"files": [
|
84
|
+
"dist",
|
85
|
+
"Dockerfile"
|
86
|
+
]
|
87
|
+
}
|
package/.dockerignore
DELETED
package/.eslintrc.js
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
---
|
2
|
-
name: Bug report
|
3
|
-
about: Report any issues with the platform
|
4
|
-
title: ""
|
5
|
-
labels: bug
|
6
|
-
assignees: ""
|
7
|
-
---
|
8
|
-
|
9
|
-
Found a bug? Please fill out the sections below. 👍
|
10
|
-
|
11
|
-
### Issue Summary
|
12
|
-
|
13
|
-
A summary of the issue. This needs to be a clear detailed-rich summary.
|
14
|
-
|
15
|
-
### Steps to Reproduce
|
16
|
-
|
17
|
-
1. (for example) Went to ...
|
18
|
-
2. Clicked on...
|
19
|
-
3. ...
|
20
|
-
|
21
|
-
Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?
|
22
|
-
|
23
|
-
### Technical details
|
24
|
-
|
25
|
-
- Browser version: You can use https://www.whatsmybrowser.org/ to find this out.
|
26
|
-
- Node.js version
|
27
|
-
- Anything else that you think could be an issue.
|