@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/README.md +1 -1
- package/dist/controller/api.d.ts +1 -0
- package/dist/controller/api.js +37 -5
- package/dist/controller/health-check.d.ts +11 -0
- package/dist/controller/health-check.js +53 -0
- package/dist/controller/logout.d.ts +18 -0
- package/dist/controller/logout.js +199 -0
- package/dist/controller/oauth/redirect.d.ts +1 -1
- package/dist/controller/oauth/redirect.js +6 -1
- package/dist/controller/oauth.d.ts +5 -3
- package/dist/controller/oauth.js +156 -27
- package/dist/controller/utils.d.ts +2 -1
- package/dist/controller/utils.js +11 -24
- package/dist/db/mem.js +31 -12
- package/dist/db/mongo.js +3 -10
- package/dist/db/redis.js +17 -6
- package/dist/db/sql/sql.d.ts +1 -1
- package/dist/db/sql/sql.js +10 -9
- package/dist/db/utils.d.ts +2 -0
- package/dist/db/utils.js +3 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +14 -2
- package/dist/typings.d.ts +50 -21
- package/package.json +22 -25
- package/dist/saml/saml.d.ts +0 -12
- package/dist/saml/saml.js +0 -211
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
|
|
package/dist/controller/api.d.ts
CHANGED
package/dist/controller/api.js
CHANGED
@@ -50,7 +50,7 @@ exports.APIController = void 0;
|
|
50
50
|
const crypto_1 = __importDefault(require("crypto"));
|
51
51
|
const dbutils = __importStar(require("../db/utils"));
|
52
52
|
const metrics = __importStar(require("../opentelemetry/metrics"));
|
53
|
-
const
|
53
|
+
const saml20_1 = __importDefault(require("@boxyhq/saml20"));
|
54
54
|
const x509_1 = __importDefault(require("../saml/x509"));
|
55
55
|
const error_1 = require("./error");
|
56
56
|
const utils_1 = require("./utils");
|
@@ -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,11 +181,13 @@ 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();
|
174
189
|
}
|
175
|
-
const idpMetadata = yield
|
190
|
+
const idpMetadata = yield saml20_1.default.parseMetadataAsync(metaData);
|
176
191
|
// extract provider
|
177
192
|
let providerName = extractHostName(idpMetadata.entityID);
|
178
193
|
if (!providerName) {
|
@@ -195,7 +210,7 @@ class APIController {
|
|
195
210
|
const record = {
|
196
211
|
idpMetadata,
|
197
212
|
defaultRedirectUrl,
|
198
|
-
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);
|
@@ -306,7 +323,7 @@ class APIController {
|
|
306
323
|
}
|
307
324
|
let newMetadata;
|
308
325
|
if (metaData) {
|
309
|
-
newMetadata = yield
|
326
|
+
newMetadata = yield saml20_1.default.parseMetadataAsync(metaData);
|
310
327
|
// extract provider
|
311
328
|
let providerName = extractHostName(newMetadata.entityID);
|
312
329
|
if (!providerName) {
|
@@ -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:
|
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;
|
@@ -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,199 @@
|
|
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 crypto_1 = __importDefault(require("crypto"));
|
40
|
+
const util_1 = require("util");
|
41
|
+
const xml2js_1 = __importDefault(require("xml2js"));
|
42
|
+
const xmlbuilder_1 = __importDefault(require("xmlbuilder"));
|
43
|
+
const zlib_1 = require("zlib");
|
44
|
+
const dbutils = __importStar(require("../db/utils"));
|
45
|
+
const saml20_1 = __importDefault(require("@boxyhq/saml20"));
|
46
|
+
const error_1 = require("./error");
|
47
|
+
const redirect = __importStar(require("./oauth/redirect"));
|
48
|
+
const utils_1 = require("./utils");
|
49
|
+
const deflateRawAsync = (0, util_1.promisify)(zlib_1.deflateRaw);
|
50
|
+
const relayStatePrefix = 'boxyhq_jackson_';
|
51
|
+
const logoutXPath = "/*[local-name(.)='LogoutRequest']";
|
52
|
+
class LogoutController {
|
53
|
+
constructor({ configStore, sessionStore, opts }) {
|
54
|
+
this.opts = opts;
|
55
|
+
this.configStore = configStore;
|
56
|
+
this.sessionStore = sessionStore;
|
57
|
+
}
|
58
|
+
// Create SLO Request
|
59
|
+
createRequest({ nameId, tenant, product, redirectUrl }) {
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
61
|
+
let samlConfig = null;
|
62
|
+
if (tenant && product) {
|
63
|
+
const samlConfigs = yield this.configStore.getByIndex({
|
64
|
+
name: utils_1.IndexNames.TenantProduct,
|
65
|
+
value: dbutils.keyFromParts(tenant, product),
|
66
|
+
});
|
67
|
+
if (!samlConfigs || samlConfigs.length === 0) {
|
68
|
+
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
69
|
+
}
|
70
|
+
samlConfig = samlConfigs[0];
|
71
|
+
}
|
72
|
+
if (!samlConfig) {
|
73
|
+
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
74
|
+
}
|
75
|
+
const { idpMetadata: { slo, provider }, certs: { privateKey, publicKey }, } = samlConfig;
|
76
|
+
if ('redirectUrl' in slo === false && 'postUrl' in slo === false) {
|
77
|
+
throw new error_1.JacksonError(`${provider} doesn't support SLO or disabled by IdP.`, 400);
|
78
|
+
}
|
79
|
+
const { id, xml } = buildRequestXML(nameId, this.opts.samlAudience, slo.redirectUrl);
|
80
|
+
const sessionId = crypto_1.default.randomBytes(16).toString('hex');
|
81
|
+
let logoutUrl = null;
|
82
|
+
let logoutForm = null;
|
83
|
+
const relayState = relayStatePrefix + sessionId;
|
84
|
+
const signedXML = yield signXML(xml, privateKey, publicKey);
|
85
|
+
yield this.sessionStore.put(sessionId, {
|
86
|
+
id,
|
87
|
+
redirectUrl,
|
88
|
+
});
|
89
|
+
// HTTP-Redirect binding
|
90
|
+
if ('redirectUrl' in slo) {
|
91
|
+
logoutUrl = redirect.success(slo.redirectUrl, {
|
92
|
+
SAMLRequest: Buffer.from(yield deflateRawAsync(signedXML)).toString('base64'),
|
93
|
+
RelayState: relayState,
|
94
|
+
});
|
95
|
+
}
|
96
|
+
// HTTP-POST binding
|
97
|
+
if ('postUrl' in slo) {
|
98
|
+
logoutForm = saml20_1.default.createPostForm(slo.postUrl, [
|
99
|
+
{
|
100
|
+
name: 'RelayState',
|
101
|
+
value: relayState,
|
102
|
+
},
|
103
|
+
{
|
104
|
+
name: 'SAMLRequest',
|
105
|
+
value: Buffer.from(signedXML).toString('base64'),
|
106
|
+
},
|
107
|
+
]);
|
108
|
+
}
|
109
|
+
return { logoutUrl, logoutForm };
|
110
|
+
});
|
111
|
+
}
|
112
|
+
// Handle SLO Response
|
113
|
+
handleResponse({ SAMLResponse, RelayState }) {
|
114
|
+
var _a;
|
115
|
+
return __awaiter(this, void 0, void 0, function* () {
|
116
|
+
const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
|
117
|
+
const sessionId = RelayState.replace(relayStatePrefix, '');
|
118
|
+
const session = yield this.sessionStore.get(sessionId);
|
119
|
+
if (!session) {
|
120
|
+
throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
|
121
|
+
}
|
122
|
+
const parsedResponse = yield parseSAMLResponse(rawResponse);
|
123
|
+
if (parsedResponse.status !== 'urn:oasis:names:tc:SAML:2.0:status:Success') {
|
124
|
+
throw new error_1.JacksonError(`SLO failed with status ${parsedResponse.status}.`, 400);
|
125
|
+
}
|
126
|
+
if (parsedResponse.inResponseTo !== session.id) {
|
127
|
+
throw new error_1.JacksonError(`SLO failed with mismatched request ID.`, 400);
|
128
|
+
}
|
129
|
+
const samlConfigs = yield this.configStore.getByIndex({
|
130
|
+
name: utils_1.IndexNames.EntityID,
|
131
|
+
value: parsedResponse.issuer,
|
132
|
+
});
|
133
|
+
if (!samlConfigs || samlConfigs.length === 0) {
|
134
|
+
throw new error_1.JacksonError('SAML configuration not found.', 403);
|
135
|
+
}
|
136
|
+
const { idpMetadata, defaultRedirectUrl } = samlConfigs[0];
|
137
|
+
if (!(yield saml20_1.default.validateSignature(rawResponse, null, idpMetadata.thumbprint))) {
|
138
|
+
throw new error_1.JacksonError('Invalid signature.', 403);
|
139
|
+
}
|
140
|
+
try {
|
141
|
+
yield this.sessionStore.delete(sessionId);
|
142
|
+
}
|
143
|
+
catch (_err) {
|
144
|
+
// Ignore
|
145
|
+
}
|
146
|
+
return {
|
147
|
+
redirectUrl: (_a = session.redirectUrl) !== null && _a !== void 0 ? _a : defaultRedirectUrl,
|
148
|
+
};
|
149
|
+
});
|
150
|
+
}
|
151
|
+
}
|
152
|
+
exports.LogoutController = LogoutController;
|
153
|
+
// Create the XML for the SLO Request
|
154
|
+
const buildRequestXML = (nameId, providerName, sloUrl) => {
|
155
|
+
const id = '_' + crypto_1.default.randomBytes(10).toString('hex');
|
156
|
+
const xml = {
|
157
|
+
'samlp:LogoutRequest': {
|
158
|
+
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
159
|
+
'@xmlns:saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
|
160
|
+
'@ID': id,
|
161
|
+
'@Version': '2.0',
|
162
|
+
'@IssueInstant': new Date().toISOString(),
|
163
|
+
'@Destination': sloUrl,
|
164
|
+
'saml:Issuer': {
|
165
|
+
'#text': providerName,
|
166
|
+
},
|
167
|
+
'saml:NameID': {
|
168
|
+
'@Format': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
|
169
|
+
'#text': nameId,
|
170
|
+
},
|
171
|
+
},
|
172
|
+
};
|
173
|
+
return {
|
174
|
+
id,
|
175
|
+
xml: xmlbuilder_1.default.create(xml).end({}),
|
176
|
+
};
|
177
|
+
};
|
178
|
+
// Parse SAMLResponse
|
179
|
+
const parseSAMLResponse = (rawResponse) => __awaiter(void 0, void 0, void 0, function* () {
|
180
|
+
return new Promise((resolve, reject) => {
|
181
|
+
xml2js_1.default.parseString(rawResponse, { tagNameProcessors: [xml2js_1.default.processors.stripPrefix] }, (err, { LogoutResponse }) => {
|
182
|
+
if (err) {
|
183
|
+
reject(err);
|
184
|
+
return;
|
185
|
+
}
|
186
|
+
resolve({
|
187
|
+
issuer: LogoutResponse.Issuer[0]._,
|
188
|
+
id: LogoutResponse.$.ID,
|
189
|
+
status: LogoutResponse.Status[0].StatusCode[0].$.Value,
|
190
|
+
destination: LogoutResponse.$.Destination,
|
191
|
+
inResponseTo: LogoutResponse.$.InResponseTo,
|
192
|
+
});
|
193
|
+
});
|
194
|
+
});
|
195
|
+
});
|
196
|
+
// Sign the XML
|
197
|
+
const signXML = (xml, signingKey, publicKey) => __awaiter(void 0, void 0, void 0, function* () {
|
198
|
+
return yield saml20_1.default.sign(xml, signingKey, publicKey, logoutXPath);
|
199
|
+
});
|
@@ -1 +1 @@
|
|
1
|
-
export declare const success: (redirectUrl: string, params: Record<string, string>) => string;
|
1
|
+
export declare const success: (redirectUrl: string, params: Record<string, string | string[] | undefined>) => string;
|
@@ -4,7 +4,12 @@ exports.success = void 0;
|
|
4
4
|
const success = (redirectUrl, params) => {
|
5
5
|
const url = new URL(redirectUrl);
|
6
6
|
for (const [key, value] of Object.entries(params)) {
|
7
|
-
|
7
|
+
if (Array.isArray(value)) {
|
8
|
+
value.forEach((v) => url.searchParams.append(key, v));
|
9
|
+
}
|
10
|
+
else if (value !== undefined) {
|
11
|
+
url.searchParams.set(key, value);
|
12
|
+
}
|
8
13
|
}
|
9
14
|
return url.href;
|
10
15
|
};
|
@@ -12,12 +12,14 @@ export declare class OAuthController implements IOAuthController {
|
|
12
12
|
tokenStore: any;
|
13
13
|
opts: any;
|
14
14
|
});
|
15
|
+
private resolveMultipleConfigMatches;
|
15
16
|
authorize(body: OAuthReqBody): Promise<{
|
16
|
-
redirect_url
|
17
|
-
authorize_form
|
17
|
+
redirect_url?: string;
|
18
|
+
authorize_form?: string;
|
18
19
|
}>;
|
19
20
|
samlResponse(body: SAMLResponsePayload): Promise<{
|
20
|
-
redirect_url
|
21
|
+
redirect_url?: string;
|
22
|
+
app_select_form?: string;
|
21
23
|
}>;
|
22
24
|
/**
|
23
25
|
* @swagger
|