@boxyhq/saml-jackson 0.1.5-beta.125 → 0.1.5-beta.138
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 +24 -2
- package/package.json +3 -2
- package/src/controller/api.js +47 -0
- package/src/jackson.js +16 -0
package/README.md
CHANGED
@@ -130,7 +130,7 @@ Please follow the instructions [here](https://docs.google.com/document/d/1fk---Z
|
|
130
130
|
|
131
131
|
### 1.1 SAML profile/claims/attributes mapping
|
132
132
|
|
133
|
-
As outlined in the guide above we try and support 4 attributes in the SAML claims - `id`, `email`, `firstName`, `lastName`. This is how the common SAML
|
133
|
+
As outlined in the guide above we try and support 4 attributes in the SAML claims - `id`, `email`, `firstName`, `lastName`. This is how the common SAML attributes map over for most providers, but some providers have custom mappings. Please refer to the documentation on Identity Provider to understand the exact mapping.
|
134
134
|
|
135
135
|
| SAML Attribute | Jackson mapping |
|
136
136
|
| -------------------------------------------------------------------- | --------------- |
|
@@ -164,7 +164,29 @@ curl --location --request POST 'http://localhost:6000/api/v1/saml/config' \
|
|
164
164
|
- tenant: Jackson supports a multi-tenant architecture, this is a unique identifier you set from your side that relates back to your customer's tenant. This is normally an email, domain, an account id, or user-id
|
165
165
|
- product: Jackson support multiple products, this is a unique identifier you set from your side that relates back to the product your customer is using
|
166
166
|
|
167
|
-
The response returns a JSON with `client_id` and `client_secret` that can be stored against your tenant and product for a more secure OAuth 2.0 flow. If you do not want to store the `client_id` and `client_secret` you can alternatively use `client_id=tenant=<tenantID>&product=<productID>` and any arbitrary value for `client_secret` when setting up the OAuth 2.0 flow.
|
167
|
+
The response returns a JSON with `client_id` and `client_secret` that can be stored against your tenant and product for a more secure OAuth 2.0 flow. If you do not want to store the `client_id` and `client_secret` you can alternatively use `client_id=tenant=<tenantID>&product=<productID>` and any arbitrary value for `client_secret` when setting up the OAuth 2.0 flow. Additionally a `provider` attribute is also returned which indicates the domain of your Identity Provider.
|
168
|
+
|
169
|
+
#### 2.1 SAML GET config API
|
170
|
+
|
171
|
+
This endpoint can be used to return metadata about an existing SAML config. This can be used to check and display the details to your customers. You can use either `clientID` and `clientSecret` combination or `tenant` and `product` combination.
|
172
|
+
|
173
|
+
```
|
174
|
+
curl --location --request GET 'http://localhost:6000/api/v1/saml/config' \
|
175
|
+
--header 'Authorization: Api-Key <Jackson API Key>' \
|
176
|
+
--header 'Content-Type: application/x-www-form-urlencoded' \
|
177
|
+
--data-urlencode 'tenant=boxyhq.com' \
|
178
|
+
--data-urlencode 'product=demo'
|
179
|
+
```
|
180
|
+
|
181
|
+
```
|
182
|
+
curl --location --request GET 'http://localhost:6000/api/v1/saml/config' \
|
183
|
+
--header 'Authorization: Api-Key <Jackson API Key>' \
|
184
|
+
--header 'Content-Type: application/x-www-form-urlencoded' \
|
185
|
+
--data-urlencode 'clientID=<Client ID>' \
|
186
|
+
--data-urlencode 'clientSecret=<Client Secret>'
|
187
|
+
```
|
188
|
+
|
189
|
+
The response returns a JSON with `provider` indicating the domain of your Identity Provider. If an empty JSON payload is returned then we do not have any configuration stored for the attributes you requested.
|
168
190
|
|
169
191
|
### 3. OAuth 2.0 Flow
|
170
192
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "0.1.5-beta.
|
3
|
+
"version": "0.1.5-beta.138",
|
4
4
|
"license": "Apache 2.0",
|
5
5
|
"description": "SAML 2.0 service",
|
6
6
|
"main": "src/index.js",
|
@@ -20,7 +20,8 @@
|
|
20
20
|
"mongo": "cross-env DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson nodemon src/jackson.js",
|
21
21
|
"pre-loaded": "cross-env DB_ENGINE=mem PRE_LOADED_CONFIG='./_config' nodemon src/jackson.js",
|
22
22
|
"test": "tap --timeout=100 src/**/*.test.js",
|
23
|
-
"dev-dbs": "docker-compose -f ./_dev/docker-compose.yml up -d"
|
23
|
+
"dev-dbs": "docker-compose -f ./_dev/docker-compose.yml up -d",
|
24
|
+
"dev-dbs-destroy": "docker-compose -f ./_dev/docker-compose.yml down --volumes --remove-orphans"
|
24
25
|
},
|
25
26
|
"tap": {
|
26
27
|
"coverage-map": "map.js",
|
package/src/controller/api.js
CHANGED
@@ -7,11 +7,33 @@ const crypto = require('crypto');
|
|
7
7
|
|
8
8
|
let configStore;
|
9
9
|
|
10
|
+
const extractHostName = (url) => {
|
11
|
+
try {
|
12
|
+
const pUrl = new URL(url);
|
13
|
+
if(pUrl.hostname.startsWith('www.')) {
|
14
|
+
return pUrl.hostname.substring(4);
|
15
|
+
}
|
16
|
+
return pUrl.hostname;
|
17
|
+
} catch (err) {
|
18
|
+
return null;
|
19
|
+
}
|
20
|
+
};
|
21
|
+
|
10
22
|
const config = async (body) => {
|
11
23
|
const { rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } =
|
12
24
|
body;
|
13
25
|
const idpMetadata = await saml.parseMetadataAsync(rawMetadata);
|
14
26
|
|
27
|
+
// extract provider
|
28
|
+
let providerName = extractHostName(idpMetadata.entityID);
|
29
|
+
if (!providerName) {
|
30
|
+
providerName = extractHostName(
|
31
|
+
idpMetadata.sso.redirectUrl || idpMetadata.sso.postUrl
|
32
|
+
);
|
33
|
+
}
|
34
|
+
|
35
|
+
idpMetadata.provider = providerName ? providerName : 'Unknown';
|
36
|
+
|
15
37
|
let clientID = dbutils.keyDigest(
|
16
38
|
dbutils.keyFromParts(tenant, product, idpMetadata.entityID)
|
17
39
|
);
|
@@ -56,12 +78,37 @@ const config = async (body) => {
|
|
56
78
|
return {
|
57
79
|
client_id: clientID,
|
58
80
|
client_secret: clientSecret,
|
81
|
+
provider: idpMetadata.provider,
|
59
82
|
};
|
60
83
|
};
|
61
84
|
|
85
|
+
const getConfig = async (body) => {
|
86
|
+
const { clientID, clientSecret, tenant, product } = body;
|
87
|
+
|
88
|
+
if (clientID && clientSecret) {
|
89
|
+
const samlConfig = await configStore.get(clientID);
|
90
|
+
if (!samlConfig) {
|
91
|
+
return {};
|
92
|
+
}
|
93
|
+
|
94
|
+
return { provider: samlConfig.idpMetadata.provider };
|
95
|
+
} else {
|
96
|
+
const samlConfigs = await configStore.getByIndex({
|
97
|
+
name: indexNames.tenantProduct,
|
98
|
+
value: dbutils.keyFromParts(tenant, product),
|
99
|
+
});
|
100
|
+
if (!samlConfigs || !samlConfigs.length) {
|
101
|
+
return {};
|
102
|
+
}
|
103
|
+
|
104
|
+
return { provider: samlConfigs[0].idpMetadata.provider };
|
105
|
+
}
|
106
|
+
};
|
107
|
+
|
62
108
|
module.exports = (opts) => {
|
63
109
|
configStore = opts.configStore;
|
64
110
|
return {
|
65
111
|
config,
|
112
|
+
getConfig,
|
66
113
|
};
|
67
114
|
};
|
package/src/jackson.js
CHANGED
@@ -87,6 +87,22 @@ internalApp.post(apiPath + '/config', async (req, res) => {
|
|
87
87
|
}
|
88
88
|
});
|
89
89
|
|
90
|
+
internalApp.get(apiPath + '/config', async (req, res) => {
|
91
|
+
try {
|
92
|
+
const apiKey = extractAuthToken(req);
|
93
|
+
if (!validateApiKey(apiKey)) {
|
94
|
+
res.status(401).send('Unauthorized');
|
95
|
+
return;
|
96
|
+
}
|
97
|
+
|
98
|
+
res.json(await apiController.getConfig(req.body));
|
99
|
+
} catch (err) {
|
100
|
+
res.status(500).json({
|
101
|
+
error: err.message,
|
102
|
+
});
|
103
|
+
}
|
104
|
+
});
|
105
|
+
|
90
106
|
let internalServer = server;
|
91
107
|
if (env.useInternalServer) {
|
92
108
|
internalServer = internalApp.listen(env.internalHostPort, async () => {
|