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