@boxyhq/saml-jackson 0.1.5-beta.122 → 0.1.5-beta.135
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 +15 -12
- package/package.json +3 -2
- package/src/controller/api.js +46 -0
- package/src/jackson.js +16 -0
package/README.md
CHANGED
@@ -129,14 +129,15 @@ Kubernetes and docker-compose deployment files will be coming soon.
|
|
129
129
|
Please follow the instructions [here](https://docs.google.com/document/d/1fk---Z9Ln59u-2toGKUkyO3BF6Dh3dscT2u4J2xHANE) to guide your customers in setting up SAML correctly for your product(s). You should create a copy of the doc and modify it with your custom settings, we have used the values that work for our demo apps.
|
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 aattributes map over for most providers, but some providers have custom mappings. Please refer to the documentation on Identity Provider to understand the exact mapping.
|
133
134
|
|
134
|
-
| SAML Attribute
|
135
|
-
|
136
|
-
|http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier|id|
|
137
|
-
|http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress|email|
|
138
|
-
|http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname|firstName|
|
139
|
-
|http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname|lastName|
|
135
|
+
| SAML Attribute | Jackson mapping |
|
136
|
+
| -------------------------------------------------------------------- | --------------- |
|
137
|
+
| http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier | id |
|
138
|
+
| http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress | email |
|
139
|
+
| http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname | firstName |
|
140
|
+
| http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname | lastName |
|
140
141
|
|
141
142
|
### 2. SAML config API
|
142
143
|
|
@@ -148,6 +149,7 @@ The following API call sets up the configuration in Jackson:
|
|
148
149
|
|
149
150
|
```
|
150
151
|
curl --location --request POST 'http://localhost:6000/api/v1/saml/config' \
|
152
|
+
--header 'Authorization: Api-Key <Jackson API Key>' \
|
151
153
|
--header 'Content-Type: application/x-www-form-urlencoded' \
|
152
154
|
--data-urlencode 'rawMetadata=<IdP/SP metadata XML>' \
|
153
155
|
--data-urlencode 'defaultRedirectUrl=http://localhost:3000/login/saml' \
|
@@ -199,11 +201,11 @@ The code can then be exchanged for a token by making the following request:
|
|
199
201
|
curl --request POST \
|
200
202
|
--url 'http://localhost:5000/oauth/token' \
|
201
203
|
--header 'content-type: application/x-www-form-urlencoded' \
|
202
|
-
--data grant_type=authorization_code \
|
204
|
+
--data 'grant_type=authorization_code' \
|
203
205
|
--data 'client_id=<clientID or tenant and product query params as described in the SAML config API section above>' \
|
204
|
-
--data client_secret=<clientSecret or any arbitrary value if using the tenant and product in the clientID> \
|
206
|
+
--data 'client_secret=<clientSecret or any arbitrary value if using the tenant and product in the clientID>' \
|
205
207
|
--data 'redirect_uri=<redirect URL>' \
|
206
|
-
--data code=<code from the query parameter above>
|
208
|
+
--data 'code=<code from the query parameter above>'
|
207
209
|
```
|
208
210
|
|
209
211
|
- grant_type=authorization_code: This is the only supported flow, for now. We might extend this in the future
|
@@ -236,15 +238,15 @@ If everything goes well you should receive a JSON response with the user's profi
|
|
236
238
|
|
237
239
|
```
|
238
240
|
{
|
241
|
+
"id": <id from the Identity Provider>,
|
239
242
|
"email": "sjackson@coolstartup.com",
|
240
243
|
"firstName": "SAML"
|
241
|
-
"
|
242
|
-
"lastName": "Jackson",
|
244
|
+
"lastName": "Jackson"
|
243
245
|
}
|
244
246
|
```
|
245
247
|
|
246
|
-
- email: The email address of the user as provided by the Identity Provider
|
247
248
|
- id: The id of the user as provided by the Identity Provider
|
249
|
+
- email: The email address of the user as provided by the Identity Provider
|
248
250
|
- firstName: The first name of the user as provided by the Identity Provider
|
249
251
|
- lastName: The last name of the user as provided by the Identity Provider
|
250
252
|
|
@@ -276,6 +278,7 @@ The following options are supported and will have to be configured during deploy
|
|
276
278
|
| EXTERNAL_URL (npm: externalUrl) | The public URL to reach this service, used internally for documenting the SAML configuration instructions. | `http://{HOST_URL}:{HOST_PORT}` |
|
277
279
|
| INTERNAL_HOST_URL | The URL to bind to expose the internal APIs. Do not configure this to a public network. | `localhost` |
|
278
280
|
| INTERNAL_HOST_PORT | The port to bind to for the internal APIs. | `6000` |
|
281
|
+
| JACKSON_API_KEYS | A comma separated list of API keys that will be validated when serving the Config API requests | |
|
279
282
|
| SAML_AUDIENCE (npm: samlAudience) | This is just an identifier to validate the SAML audience, this value will also get configured in the SAML apps created by your customers. Once set do not change this value unless you get your customers to reconfigure their SAML again. It is case-sensitive. This does not have to be a real URL. | `https://saml.boxyhq.com` |
|
280
283
|
| IDP_ENABLED (npm: idpEnabled) | Set to `true` to enable IdP initiated login for SAML. SP initiated login is the only recommended flow but you might have to support IdP login at times. | `false` |
|
281
284
|
| DB_ENGINE (npm: db.engine) | Supported values are `redis`, `sql`, `mongo`, `mem`. | `sql` |
|
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.135",
|
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
|
);
|
@@ -59,9 +81,33 @@ const config = async (body) => {
|
|
59
81
|
};
|
60
82
|
};
|
61
83
|
|
84
|
+
const getConfig = async (body) => {
|
85
|
+
const { clientID, clientSecret, tenant, product } = body;
|
86
|
+
|
87
|
+
if (clientID && clientSecret) {
|
88
|
+
const samlConfig = await configStore.get(clientID);
|
89
|
+
if (!samlConfig) {
|
90
|
+
return {};
|
91
|
+
}
|
92
|
+
|
93
|
+
return { provider: samlConfig.idpMetadata.provider };
|
94
|
+
} else {
|
95
|
+
const samlConfigs = await configStore.getByIndex({
|
96
|
+
name: indexNames.tenantProduct,
|
97
|
+
value: dbutils.keyFromParts(tenant, product),
|
98
|
+
});
|
99
|
+
if (!samlConfigs || !samlConfigs.length) {
|
100
|
+
return {};
|
101
|
+
}
|
102
|
+
|
103
|
+
return { provider: samlConfigs[0].idpMetadata.provider };
|
104
|
+
}
|
105
|
+
};
|
106
|
+
|
62
107
|
module.exports = (opts) => {
|
63
108
|
configStore = opts.configStore;
|
64
109
|
return {
|
65
110
|
config,
|
111
|
+
getConfig,
|
66
112
|
};
|
67
113
|
};
|
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 () => {
|