@boxyhq/saml-jackson 0.1.5-beta.123 → 0.1.5-beta.136

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 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
- 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
132
 
134
- | SAML Attribute | Jackson mapping |
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|
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
+
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' \
@@ -162,7 +164,29 @@ curl --location --request POST 'http://localhost:6000/api/v1/saml/config' \
162
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
163
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
164
166
 
165
- 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.
166
190
 
167
191
  ### 3. OAuth 2.0 Flow
168
192
 
@@ -199,11 +223,11 @@ The code can then be exchanged for a token by making the following request:
199
223
  curl --request POST \
200
224
  --url 'http://localhost:5000/oauth/token' \
201
225
  --header 'content-type: application/x-www-form-urlencoded' \
202
- --data grant_type=authorization_code \
226
+ --data 'grant_type=authorization_code' \
203
227
  --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> \
228
+ --data 'client_secret=<clientSecret or any arbitrary value if using the tenant and product in the clientID>' \
205
229
  --data 'redirect_uri=<redirect URL>' \
206
- --data code=<code from the query parameter above>
230
+ --data 'code=<code from the query parameter above>'
207
231
  ```
208
232
 
209
233
  - grant_type=authorization_code: This is the only supported flow, for now. We might extend this in the future
@@ -236,15 +260,15 @@ If everything goes well you should receive a JSON response with the user's profi
236
260
 
237
261
  ```
238
262
  {
263
+ "id": <id from the Identity Provider>,
239
264
  "email": "sjackson@coolstartup.com",
240
265
  "firstName": "SAML"
241
- "id": <id from the Identity Provider>,
242
- "lastName": "Jackson",
266
+ "lastName": "Jackson"
243
267
  }
244
268
  ```
245
269
 
246
- - email: The email address of the user as provided by the Identity Provider
247
270
  - id: The id of the user as provided by the Identity Provider
271
+ - email: The email address of the user as provided by the Identity Provider
248
272
  - firstName: The first name of the user as provided by the Identity Provider
249
273
  - lastName: The last name of the user as provided by the Identity Provider
250
274
 
@@ -276,6 +300,7 @@ The following options are supported and will have to be configured during deploy
276
300
  | 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
301
  | INTERNAL_HOST_URL | The URL to bind to expose the internal APIs. Do not configure this to a public network. | `localhost` |
278
302
  | INTERNAL_HOST_PORT | The port to bind to for the internal APIs. | `6000` |
303
+ | JACKSON_API_KEYS | A comma separated list of API keys that will be validated when serving the Config API requests | |
279
304
  | 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
305
  | 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
306
  | 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.123",
3
+ "version": "0.1.5-beta.136",
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",
@@ -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 () => {