@boxyhq/saml-jackson 0.1.5-beta.116 → 0.1.5-beta.121

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.
@@ -98,7 +98,7 @@ jobs:
98
98
 
99
99
  - name: Get short SHA
100
100
  id: slug
101
- run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
101
+ run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-7)"
102
102
 
103
103
  - name: Set up Docker Buildx
104
104
  id: buildx
package/README.md CHANGED
@@ -152,7 +152,7 @@ curl --location --request POST 'http://localhost:6000/api/v1/saml/config' \
152
152
  - 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
153
153
  - 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
154
154
 
155
- 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=tentant=<tenantID>&product=<productID>` and any arbitrary value for `client_secret` when setting up the OAuth 2.0 flow.
155
+ 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.
156
156
 
157
157
  ### 3. OAuth 2.0 Flow
158
158
 
@@ -175,7 +175,7 @@ https://localhost:5000/oauth/authorize
175
175
  ```
176
176
 
177
177
  - response_type=code: This is the only supported type for now but maybe extended in the future
178
- - client_id: Use the client_id returned by the SAML config API or use `tentant=<tenantID>&product=<productID>` to use the tenant and product IDs instead
178
+ - client_id: Use the client_id returned by the SAML config API or use `tenant=<tenantID>&product=<productID>` to use the tenant and product IDs instead. **Note:** Please don't forget to URL encode the query parameters including `client_id`.
179
179
  - redirect_uri: This is where the user will be taken back once the authorization flow is complete
180
180
  - state: Use a randomly generated string as the state, this will be echoed back as a query parameter when taking the user back to the `redirect_uri` above. You should validate the state to prevent XSRF attacks
181
181
 
@@ -197,7 +197,7 @@ curl --request POST \
197
197
  ```
198
198
 
199
199
  - grant_type=authorization_code: This is the only supported flow, for now. We might extend this in the future
200
- - client_id: Use the client_id returned by the SAML config API or use `tentant=<tenantID>&product=<productID>` to use the tenant and product IDs instead
200
+ - client_id: Use the client_id returned by the SAML config API or use `tenant=<tenantID>&product=<productID>` to use the tenant and product IDs instead. **Note:** Please don't forget to URL encode the query parameters including `client_id`.
201
201
  - client_secret: Use the client_secret returned by the SAML config API or any arbitrary value if using the tenant and product in the clientID
202
202
  - redirect_uri: This is where the user will be taken back once the authorization flow is complete. Use the same redirect_uri as the previous request
203
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "0.1.5-beta.116",
3
+ "version": "0.1.5-beta.121",
4
4
  "license": "Apache 2.0",
5
5
  "description": "SAML 2.0 service",
6
6
  "main": "src/index.js",
@@ -2,7 +2,7 @@ const crypto = require('crypto');
2
2
 
3
3
  const saml = require('../saml/saml.js');
4
4
  const codeVerifier = require('./oauth/code-verifier.js');
5
- const { indexNames } = require('./utils.js');
5
+ const { indexNames, extractAuthToken } = require('./utils.js');
6
6
  const dbutils = require('../db/utils.js');
7
7
  const redirect = require('./oauth/redirect.js');
8
8
  const allowed = require('./oauth/allowed.js');
@@ -15,16 +15,6 @@ let options;
15
15
 
16
16
  const relayStatePrefix = 'boxyhq_jackson_';
17
17
 
18
- const extractBearerToken = (req) => {
19
- const authHeader = req.get('authorization');
20
- const parts = (authHeader || '').split(' ');
21
- if (parts.length > 1) {
22
- return parts[1];
23
- }
24
-
25
- return null;
26
- };
27
-
28
18
  function getEncodedClientId(client_id) {
29
19
  try {
30
20
  const sp = new URLSearchParams(client_id);
@@ -196,11 +186,6 @@ const samlResponse = async (req, res) => {
196
186
  }
197
187
 
198
188
  const profile = await saml.validateAsync(rawResponse, validateOpts);
199
-
200
- // some providers don't return the id in the assertion, we set it to a sha256 hash of the email
201
- if (profile && profile.claims && !profile.claims.id) {
202
- profile.claims.id = crypto.createHash('sha256').update(profile.claims.email).digest('hex');
203
- }
204
189
 
205
190
  // store details against a code
206
191
  const code = crypto.randomBytes(20).toString('hex');
@@ -303,7 +288,7 @@ const token = async (req, res) => {
303
288
  };
304
289
 
305
290
  const userInfo = async (req, res) => {
306
- let token = extractBearerToken(req);
291
+ let token = extractAuthToken(req);
307
292
 
308
293
  // check for query param
309
294
  if (!token) {
@@ -3,6 +3,17 @@ const indexNames = {
3
3
  tenantProduct: 'tenantProduct',
4
4
  };
5
5
 
6
+ const extractAuthToken = (req) => {
7
+ const authHeader = req.get('authorization');
8
+ const parts = (authHeader || '').split(' ');
9
+ if (parts.length > 1) {
10
+ return parts[1];
11
+ }
12
+
13
+ return null;
14
+ };
15
+
6
16
  module.exports = {
7
17
  indexNames,
18
+ extractAuthToken,
8
19
  };
package/src/env.js CHANGED
@@ -7,6 +7,8 @@ const samlPath = process.env.SAML_PATH || '/oauth/saml';
7
7
  const internalHostUrl = process.env.INTERNAL_HOST_URL || 'localhost';
8
8
  const internalHostPort = (process.env.INTERNAL_HOST_PORT || '6000') * 1;
9
9
 
10
+ const apiKeys = (process.env.JACKSON_API_KEYS || '').split(',');
11
+
10
12
  const samlAudience = process.env.SAML_AUDIENCE;
11
13
  const preLoadedConfig = process.env.PRE_LOADED_CONFIG;
12
14
 
@@ -27,6 +29,7 @@ module.exports = {
27
29
  preLoadedConfig,
28
30
  internalHostUrl,
29
31
  internalHostPort,
32
+ apiKeys,
30
33
  idpEnabled,
31
34
  db,
32
35
  useInternalServer: !(
package/src/jackson.js CHANGED
@@ -2,6 +2,7 @@ const express = require('express');
2
2
  const cors = require('cors');
3
3
 
4
4
  const env = require('./env.js');
5
+ const { extractAuthToken } = require('./controller/utils.js');
5
6
 
6
7
  let apiController;
7
8
  let oauthController;
@@ -66,8 +67,18 @@ if (env.useInternalServer) {
66
67
  internalApp.use(express.urlencoded({ extended: true }));
67
68
  }
68
69
 
70
+ const validateApiKey = (token) => {
71
+ return env.apiKeys.includes(token);
72
+ };
73
+
69
74
  internalApp.post(apiPath + '/config', async (req, res) => {
70
75
  try {
76
+ const apiKey = extractAuthToken(req);
77
+ if (!validateApiKey(apiKey)) {
78
+ res.status(401).send('Unauthorized');
79
+ return;
80
+ }
81
+
71
82
  res.json(await apiController.config(req.body));
72
83
  } catch (err) {
73
84
  res.status(500).json({
@@ -0,0 +1,40 @@
1
+ const mapping = [
2
+ {
3
+ attribute: 'id',
4
+ schema:
5
+ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier',
6
+ },
7
+ {
8
+ attribute: 'email',
9
+ schema:
10
+ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
11
+ },
12
+ {
13
+ attribute: 'firstName',
14
+ schema: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',
15
+ },
16
+ {
17
+ attribute: 'lastName',
18
+ schema: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',
19
+ },
20
+ ];
21
+
22
+ const map = (claims) => {
23
+ const profile = {
24
+ raw: claims,
25
+ };
26
+
27
+ mapping.forEach((m) => {
28
+ if (claims[m.attribute]) {
29
+ profile[m.attribute] = claims[m.attribute];
30
+ } else if (claims[m.schema]) {
31
+ profile[m.attribute] = claims[m.schema];
32
+ }
33
+ });
34
+
35
+ return profile;
36
+ };
37
+
38
+ module.exports = {
39
+ map,
40
+ };
package/src/saml/saml.js CHANGED
@@ -5,6 +5,7 @@ const thumbprint = require('thumbprint');
5
5
  const xmlbuilder = require('xmlbuilder');
6
6
  const crypto = require('crypto');
7
7
  const xmlcrypto = require('xml-crypto');
8
+ const claims = require('./claims');
8
9
 
9
10
  const idPrefix = '_';
10
11
  const authnXPath =
@@ -120,6 +121,19 @@ module.exports = {
120
121
  return;
121
122
  }
122
123
 
124
+ if (profile && profile.claims) {
125
+ // we map claims to our attributes id, email, firstName, lastName where possible. We also map original claims to raw
126
+ profile.claims = claims.map(profile.claims);
127
+
128
+ // some providers don't return the id in the assertion, we set it to a sha256 hash of the email
129
+ if (!profile.claims.id) {
130
+ profile.claims.id = crypto
131
+ .createHash('sha256')
132
+ .update(profile.claims.email)
133
+ .digest('hex');
134
+ }
135
+ }
136
+
123
137
  resolve(profile);
124
138
  }
125
139
  );