@boxyhq/saml-jackson 0.2.3-beta.177 → 0.2.3-beta.210

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.
Files changed (45) hide show
  1. package/ nodemon.json +12 -0
  2. package/.nyc_output/36a3e9e1-42eb-468d-a9ec-8d206fedcd3e.json +1 -0
  3. package/.nyc_output/8c0af85a-b807-45bb-8331-20c3aabe15df.json +1 -0
  4. package/.nyc_output/ad148b90-7401-4df2-959f-3fdcf81a06ec.json +1 -0
  5. package/.nyc_output/processinfo/36a3e9e1-42eb-468d-a9ec-8d206fedcd3e.json +1 -0
  6. package/.nyc_output/processinfo/8c0af85a-b807-45bb-8331-20c3aabe15df.json +1 -0
  7. package/.nyc_output/processinfo/ad148b90-7401-4df2-959f-3fdcf81a06ec.json +1 -0
  8. package/.nyc_output/processinfo/index.json +1 -0
  9. package/package.json +23 -15
  10. package/.eslintrc.js +0 -13
  11. package/prettier.config.js +0 -4
  12. package/src/controller/api.js +0 -167
  13. package/src/controller/error.js +0 -12
  14. package/src/controller/oauth/allowed.js +0 -19
  15. package/src/controller/oauth/code-verifier.js +0 -16
  16. package/src/controller/oauth/redirect.js +0 -18
  17. package/src/controller/oauth.js +0 -321
  18. package/src/controller/utils.js +0 -19
  19. package/src/db/db.js +0 -81
  20. package/src/db/db.test.js +0 -302
  21. package/src/db/encrypter.js +0 -36
  22. package/src/db/mem.js +0 -111
  23. package/src/db/mongo.js +0 -89
  24. package/src/db/redis.js +0 -88
  25. package/src/db/sql/entity/JacksonIndex.js +0 -42
  26. package/src/db/sql/entity/JacksonStore.js +0 -42
  27. package/src/db/sql/entity/JacksonTTL.js +0 -23
  28. package/src/db/sql/model/JacksonIndex.js +0 -9
  29. package/src/db/sql/model/JacksonStore.js +0 -10
  30. package/src/db/sql/model/JacksonTTL.js +0 -8
  31. package/src/db/sql/sql.js +0 -153
  32. package/src/db/store.js +0 -42
  33. package/src/db/utils.js +0 -30
  34. package/src/env.js +0 -39
  35. package/src/index.js +0 -67
  36. package/src/jackson.js +0 -161
  37. package/src/read-config.js +0 -24
  38. package/src/saml/claims.js +0 -40
  39. package/src/saml/saml.js +0 -223
  40. package/src/saml/x509.js +0 -48
  41. package/src/test/api.test.js +0 -186
  42. package/src/test/data/metadata/boxyhq.js +0 -6
  43. package/src/test/data/metadata/boxyhq.xml +0 -30
  44. package/src/test/data/saml_response +0 -1
  45. package/src/test/oauth.test.js +0 -342
@@ -0,0 +1,12 @@
1
+ {
2
+ "restartable": "rs",
3
+ "ignore": [".git", "node_modules/", "dist/", "coverage/"],
4
+ "watch": ["src/"],
5
+ "execMap": {
6
+ "ts": "node -r ts-node/register"
7
+ },
8
+ "env": {
9
+ "NODE_ENV": "development"
10
+ },
11
+ "ext": "js,json,ts"
12
+ }
@@ -0,0 +1 @@
1
+ {"parent":"ad148b90-7401-4df2-959f-3fdcf81a06ec","pid":3634,"argv":["/opt/hostedtoolcache/node/16.13.1/x64/bin/node","/home/runner/work/jackson/jackson/src/test/oauth.test.ts"],"execArgv":["-r","/home/runner/work/jackson/jackson/node_modules/ts-node/register/index.js"],"cwd":"/home/runner/work/jackson/jackson","time":1640721639279,"ppid":3619,"coverageFilename":"/home/runner/work/jackson/jackson/.nyc_output/36a3e9e1-42eb-468d-a9ec-8d206fedcd3e.json","externalId":"src/test/oauth.test.ts","uuid":"36a3e9e1-42eb-468d-a9ec-8d206fedcd3e","files":[]}
@@ -0,0 +1 @@
1
+ {"parent":"ad148b90-7401-4df2-959f-3fdcf81a06ec","pid":3630,"argv":["/opt/hostedtoolcache/node/16.13.1/x64/bin/node","/home/runner/work/jackson/jackson/src/test/api.test.ts"],"execArgv":["-r","/home/runner/work/jackson/jackson/node_modules/ts-node/register/index.js"],"cwd":"/home/runner/work/jackson/jackson","time":1640721639268,"ppid":3619,"coverageFilename":"/home/runner/work/jackson/jackson/.nyc_output/8c0af85a-b807-45bb-8331-20c3aabe15df.json","externalId":"src/test/api.test.ts","uuid":"8c0af85a-b807-45bb-8331-20c3aabe15df","files":[]}
@@ -0,0 +1 @@
1
+ {"parent":null,"pid":3619,"argv":["/opt/hostedtoolcache/node/16.13.1/x64/bin/node","/home/runner/work/jackson/jackson/node_modules/.bin/tap","--ts","--timeout=100","src/test/api.test.ts","src/test/oauth.test.ts"],"execArgv":[],"cwd":"/home/runner/work/jackson/jackson","time":1640721638827,"ppid":3608,"coverageFilename":"/home/runner/work/jackson/jackson/.nyc_output/ad148b90-7401-4df2-959f-3fdcf81a06ec.json","externalId":"","uuid":"ad148b90-7401-4df2-959f-3fdcf81a06ec","files":[]}
@@ -0,0 +1 @@
1
+ {"processes":{"36a3e9e1-42eb-468d-a9ec-8d206fedcd3e":{"parent":"ad148b90-7401-4df2-959f-3fdcf81a06ec","externalId":"src/test/oauth.test.ts","children":[]},"8c0af85a-b807-45bb-8331-20c3aabe15df":{"parent":"ad148b90-7401-4df2-959f-3fdcf81a06ec","externalId":"src/test/api.test.ts","children":[]},"ad148b90-7401-4df2-959f-3fdcf81a06ec":{"parent":null,"children":["36a3e9e1-42eb-468d-a9ec-8d206fedcd3e","8c0af85a-b807-45bb-8331-20c3aabe15df"]}},"files":{},"externalIds":{"src/test/oauth.test.ts":{"root":"36a3e9e1-42eb-468d-a9ec-8d206fedcd3e","children":[]},"src/test/api.test.ts":{"root":"8c0af85a-b807-45bb-8331-20c3aabe15df","children":[]}}}
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@boxyhq/saml-jackson",
3
- "version": "0.2.3-beta.177",
3
+ "version": "0.2.3-beta.210",
4
4
  "license": "Apache 2.0",
5
5
  "description": "SAML 2.0 service",
6
- "main": "src/index.js",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
7
8
  "engines": {
8
9
  "node": ">=14.18.1"
9
10
  },
@@ -15,12 +16,14 @@
15
16
  "SAML 2.0"
16
17
  ],
17
18
  "scripts": {
19
+ "build": "tsc",
18
20
  "start": "cross-env IDP_ENABLED=true node src/jackson.js",
19
21
  "dev": "cross-env IDP_ENABLED=true nodemon src/jackson.js",
20
- "mongo": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson nodemon src/jackson.js",
22
+ "mongo": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson DB_ENCRYPTION_KEY=RiVoTxDoLUUoIUOp224abMxK6PGGfFuF nodemon --config nodemon.json src/jackson.ts",
23
+ "sql": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=sql DB_TYPE=postgres DB_URL=postgres://postgres:postgres@localhost:5432/jackson DB_ENCRYPTION_KEY=RiVoTxDoLUUoIUOp224abMxK6PGGfFuF nodemon src/jackson.js",
21
24
  "pre-loaded": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mem PRE_LOADED_CONFIG='./_config' nodemon src/jackson.js",
22
25
  "pre-loaded-db": "cross-env JACKSON_API_KEYS=secret PRE_LOADED_CONFIG='./_config' nodemon src/jackson.js",
23
- "test": "tap --timeout=100 src/**/*.test.js",
26
+ "test": "tap --ts --timeout=100 src/**/*.test.ts",
24
27
  "dev-dbs": "docker-compose -f ./_dev/docker-compose.yml up -d",
25
28
  "dev-dbs-destroy": "docker-compose -f ./_dev/docker-compose.yml down --volumes --remove-orphans"
26
29
  },
@@ -33,15 +36,15 @@
33
36
  },
34
37
  "dependencies": {
35
38
  "@boxyhq/saml20": "0.2.0",
36
- "@peculiar/webcrypto": "1.2.2",
37
- "@peculiar/x509": "1.6.0",
39
+ "@peculiar/webcrypto": "1.2.3",
40
+ "@peculiar/x509": "1.6.1",
38
41
  "cors": "2.8.5",
39
- "express": "4.17.1",
40
- "mongodb": "4.2.1",
42
+ "express": "4.17.2",
43
+ "mongodb": "4.2.2",
41
44
  "mysql2": "2.3.3",
42
45
  "pg": "8.7.1",
43
46
  "rambda": "6.9.0",
44
- "redis": "4.0.0-rc.4",
47
+ "redis": "4.0.1",
45
48
  "reflect-metadata": "0.1.13",
46
49
  "ripemd160": "2.0.2",
47
50
  "thumbprint": "0.0.1",
@@ -51,17 +54,22 @@
51
54
  "xmlbuilder": "15.1.1"
52
55
  },
53
56
  "devDependencies": {
57
+ "@types/redis": "4.0.11",
58
+ "@types/sinon": "10.0.6",
59
+ "@types/tap": "15.0.5",
54
60
  "cross-env": "7.0.3",
55
- "eslint": "8.4.0",
61
+ "eslint": "8.5.0",
56
62
  "husky": "7.0.4",
57
- "lint-staged": "12.1.2",
63
+ "lint-staged": "12.1.4",
58
64
  "nodemon": "2.0.15",
59
65
  "prettier": "2.5.1",
60
66
  "sinon": "12.0.1",
61
- "tap": "15.1.5"
67
+ "tap": "15.1.5",
68
+ "ts-node": "10.4.0",
69
+ "typescript": "4.5.4"
62
70
  },
63
71
  "lint-staged": {
64
- "*.js": "eslint --cache --fix",
65
- "*.{js,css,md}": "prettier --write"
72
+ "*.{js,ts}": "eslint --cache --fix",
73
+ "*.{js,ts,css,md}": "prettier --write"
66
74
  }
67
- }
75
+ }
package/.eslintrc.js DELETED
@@ -1,13 +0,0 @@
1
- module.exports = {
2
- "env": {
3
- "es2021": true,
4
- "node": true
5
- },
6
- "extends": "eslint:recommended",
7
- "parserOptions": {
8
- "ecmaVersion": 13,
9
- "sourceType": "module"
10
- },
11
- "rules": {
12
- }
13
- };
@@ -1,4 +0,0 @@
1
- module.exports = {
2
- singleQuote: true,
3
- trailingComma: 'es5',
4
- };
@@ -1,167 +0,0 @@
1
- const saml = require('../saml/saml.js');
2
- const x509 = require('../saml/x509.js');
3
- const dbutils = require('../db/utils.js');
4
- const { indexNames } = require('./utils.js');
5
- const { JacksonError } = require('./error.js');
6
-
7
- const crypto = require('crypto');
8
-
9
- let configStore;
10
-
11
- const extractHostName = (url) => {
12
- try {
13
- const pUrl = new URL(url);
14
- if (pUrl.hostname.startsWith('www.')) {
15
- return pUrl.hostname.substring(4);
16
- }
17
- return pUrl.hostname;
18
- } catch (err) {
19
- return null;
20
- }
21
- };
22
-
23
- const config = async (body) => {
24
- const { rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } =
25
- body;
26
-
27
- if (!rawMetadata) {
28
- throw new JacksonError('Please provide rawMetadata', 400);
29
- }
30
-
31
- if (!defaultRedirectUrl) {
32
- throw new JacksonError('Please provide a defaultRedirectUrl', 400);
33
- }
34
-
35
- if (!redirectUrl) {
36
- throw new JacksonError('Please provide redirectUrl', 400);
37
- }
38
-
39
- if (!tenant) {
40
- throw new JacksonError('Please provide tenant', 400);
41
- }
42
-
43
- if (!product) {
44
- throw new JacksonError('Please provide product', 400);
45
- }
46
-
47
- const idpMetadata = await saml.parseMetadataAsync(rawMetadata);
48
-
49
- // extract provider
50
- let providerName = extractHostName(idpMetadata.entityID);
51
- if (!providerName) {
52
- providerName = extractHostName(
53
- idpMetadata.sso.redirectUrl || idpMetadata.sso.postUrl
54
- );
55
- }
56
-
57
- idpMetadata.provider = providerName ? providerName : 'Unknown';
58
-
59
- let clientID = dbutils.keyDigest(
60
- dbutils.keyFromParts(tenant, product, idpMetadata.entityID)
61
- );
62
- let clientSecret;
63
-
64
- let exists = await configStore.get(clientID);
65
- if (exists) {
66
- clientSecret = exists.clientSecret;
67
- } else {
68
- clientSecret = crypto.randomBytes(24).toString('hex');
69
- }
70
-
71
- const certs = await x509.generate();
72
- if (!certs) {
73
- throw new Error('Error generating x59 certs');
74
- }
75
-
76
- await configStore.put(
77
- clientID,
78
- {
79
- idpMetadata,
80
- defaultRedirectUrl,
81
- redirectUrl: JSON.parse(redirectUrl), // redirectUrl is a stringified array
82
- tenant,
83
- product,
84
- clientID,
85
- clientSecret,
86
- certs,
87
- },
88
- {
89
- // secondary index on entityID
90
- name: indexNames.entityID,
91
- value: idpMetadata.entityID,
92
- },
93
- {
94
- // secondary index on tenant + product
95
- name: indexNames.tenantProduct,
96
- value: dbutils.keyFromParts(tenant, product),
97
- }
98
- );
99
-
100
- return {
101
- client_id: clientID,
102
- client_secret: clientSecret,
103
- provider: idpMetadata.provider,
104
- };
105
- };
106
-
107
- const getConfig = async (body) => {
108
- const { clientID, tenant, product } = body;
109
-
110
- if (clientID) {
111
- const samlConfig = await configStore.get(clientID);
112
- if (!samlConfig) {
113
- return {};
114
- }
115
-
116
- return { provider: samlConfig.idpMetadata.provider };
117
- } else {
118
- const samlConfigs = await configStore.getByIndex({
119
- name: indexNames.tenantProduct,
120
- value: dbutils.keyFromParts(tenant, product),
121
- });
122
- if (!samlConfigs || !samlConfigs.length) {
123
- return {};
124
- }
125
-
126
- return { provider: samlConfigs[0].idpMetadata.provider };
127
- }
128
- };
129
-
130
- const deleteConfig = async (body) => {
131
- const { clientID, clientSecret, tenant, product } = body;
132
-
133
- if (clientID) {
134
- if (!clientSecret) {
135
- throw new JacksonError('Please provide clientSecret', 400);
136
- }
137
- const samlConfig = await configStore.get(clientID);
138
- if (!samlConfig) {
139
- return;
140
- }
141
- if (samlConfig.clientSecret === clientSecret) {
142
- await configStore.delete(clientID);
143
- } else {
144
- throw new JacksonError('clientSecret mismatch', 400);
145
- }
146
- } else {
147
- const samlConfigs = await configStore.getByIndex({
148
- name: indexNames.tenantProduct,
149
- value: dbutils.keyFromParts(tenant, product),
150
- });
151
- if (!samlConfigs || !samlConfigs.length) {
152
- return;
153
- }
154
- for (const conf of samlConfigs) {
155
- await configStore.delete(conf.clientID);
156
- }
157
- }
158
- };
159
-
160
- module.exports = (opts) => {
161
- configStore = opts.configStore;
162
- return {
163
- config,
164
- getConfig,
165
- deleteConfig,
166
- };
167
- };
@@ -1,12 +0,0 @@
1
- class JacksonError extends Error {
2
- constructor(message, statusCode = 500) {
3
- super(message);
4
-
5
- this.name = this.constructor.name;
6
- this.statusCode = statusCode;
7
-
8
- Error.captureStackTrace(this, this.constructor);
9
- }
10
- }
11
-
12
- module.exports = { JacksonError };
@@ -1,19 +0,0 @@
1
- module.exports = {
2
- redirect: (redirectUrl, redirectUrls) => {
3
- const url = new URL(redirectUrl);
4
-
5
- for (const idx in redirectUrls) {
6
- const rUrl = new URL(redirectUrls[idx]);
7
- // TODO: Check pathname, for now pathname is ignored
8
- if (
9
- rUrl.protocol === url.protocol &&
10
- rUrl.hostname === url.hostname &&
11
- rUrl.port === url.port
12
- ) {
13
- return true;
14
- }
15
- }
16
-
17
- return false;
18
- },
19
- };
@@ -1,16 +0,0 @@
1
- const crypto = require('crypto');
2
-
3
- const transformBase64 = (input) => {
4
- return input.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
5
- };
6
-
7
- const encode = (code_challenge) => {
8
- return transformBase64(
9
- crypto.createHash('sha256').update(code_challenge).digest('base64')
10
- );
11
- };
12
-
13
- module.exports = {
14
- encode,
15
- transformBase64,
16
- };
@@ -1,18 +0,0 @@
1
- module.exports = {
2
- error: (res, redirectUrl, err) => {
3
- var url = new URL(redirectUrl);
4
- url.searchParams.set('error', err);
5
-
6
- res.redirect(url);
7
- },
8
-
9
- success: (redirectUrl, params) => {
10
- const url = new URL(redirectUrl);
11
-
12
- for (const [key, value] of Object.entries(params)) {
13
- url.searchParams.set(key, value);
14
- }
15
-
16
- return url.href;
17
- },
18
- };
@@ -1,321 +0,0 @@
1
- const crypto = require('crypto');
2
-
3
- const saml = require('../saml/saml.js');
4
- const codeVerifier = require('./oauth/code-verifier.js');
5
- const { indexNames } = require('./utils.js');
6
- const dbutils = require('../db/utils.js');
7
- const redirect = require('./oauth/redirect.js');
8
- const allowed = require('./oauth/allowed.js');
9
- const { JacksonError } = require('./error.js');
10
-
11
- let configStore;
12
- let sessionStore;
13
- let codeStore;
14
- let tokenStore;
15
- let options;
16
-
17
- const relayStatePrefix = 'boxyhq_jackson_';
18
-
19
- function getEncodedClientId(client_id) {
20
- try {
21
- const sp = new URLSearchParams(client_id);
22
- const tenant = sp.get('tenant');
23
- const product = sp.get('product');
24
- if (tenant && product) {
25
- return {
26
- tenant: sp.get('tenant'),
27
- product: sp.get('product'),
28
- };
29
- }
30
-
31
- return null;
32
- } catch (err) {
33
- return null;
34
- }
35
- }
36
-
37
- const authorize = async (body) => {
38
- const {
39
- response_type = 'code',
40
- client_id,
41
- redirect_uri,
42
- state,
43
- tenant,
44
- product,
45
- code_challenge,
46
- code_challenge_method = '',
47
- // eslint-disable-next-line no-unused-vars
48
- provider = 'saml',
49
- } = body;
50
-
51
- if (!redirect_uri) {
52
- throw new JacksonError('Please specify a redirect URL.', 400);
53
- }
54
-
55
- if (!state) {
56
- throw new JacksonError(
57
- 'Please specify a state to safeguard against XSRF attacks.',
58
- 400
59
- );
60
- }
61
-
62
- let samlConfig;
63
-
64
- if (tenant && product) {
65
- const samlConfigs = await configStore.getByIndex({
66
- name: indexNames.tenantProduct,
67
- value: dbutils.keyFromParts(tenant, product),
68
- });
69
-
70
- if (!samlConfigs || samlConfigs.length === 0) {
71
- throw new JacksonError('SAML configuration not found.', 403);
72
- }
73
-
74
- // TODO: Support multiple matches
75
- samlConfig = samlConfigs[0];
76
- } else if (
77
- client_id &&
78
- client_id !== '' &&
79
- client_id !== 'undefined' &&
80
- client_id !== 'null'
81
- ) {
82
- // if tenant and product are encoded in the client_id then we parse it and check for the relevant config(s)
83
- const sp = getEncodedClientId(client_id);
84
- if (sp) {
85
- const samlConfigs = await configStore.getByIndex({
86
- name: indexNames.tenantProduct,
87
- value: dbutils.keyFromParts(sp.tenant, sp.product),
88
- });
89
-
90
- if (!samlConfigs || samlConfigs.length === 0) {
91
- throw new JacksonError('SAML configuration not found.', 403);
92
- }
93
-
94
- // TODO: Support multiple matches
95
- samlConfig = samlConfigs[0];
96
- } else {
97
- samlConfig = await configStore.get(client_id);
98
- }
99
- } else {
100
- throw new JacksonError(
101
- 'You need to specify client_id or tenant & product',
102
- 403
103
- );
104
- }
105
-
106
- if (!samlConfig) {
107
- throw new JacksonError('SAML configuration not found.', 403);
108
- }
109
-
110
- if (!allowed.redirect(redirect_uri, samlConfig.redirectUrl)) {
111
- throw new JacksonError('Redirect URL is not allowed.', 403);
112
- }
113
-
114
- const samlReq = saml.request({
115
- entityID: options.samlAudience,
116
- callbackUrl: options.externalUrl + options.samlPath,
117
- signingKey: samlConfig.certs.privateKey,
118
- });
119
-
120
- const sessionId = crypto.randomBytes(16).toString('hex');
121
-
122
- await sessionStore.put(sessionId, {
123
- id: samlReq.id,
124
- redirect_uri,
125
- response_type,
126
- state,
127
- code_challenge,
128
- code_challenge_method,
129
- });
130
-
131
- const redirectUrl = redirect.success(samlConfig.idpMetadata.sso.redirectUrl, {
132
- RelayState: relayStatePrefix + sessionId,
133
- SAMLRequest: Buffer.from(samlReq.request).toString('base64'),
134
- });
135
-
136
- return { redirect_url: redirectUrl };
137
- };
138
-
139
- const samlResponse = async (body) => {
140
- const { SAMLResponse } = body; // RelayState will contain the sessionId from earlier quasi-oauth flow
141
-
142
- let RelayState = body.RelayState || '';
143
-
144
- if (!options.idpEnabled && !RelayState.startsWith(relayStatePrefix)) {
145
- // IDP is disabled so block the request
146
-
147
- throw new JacksonError(
148
- 'IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.',
149
- 403
150
- );
151
- }
152
-
153
- if (!RelayState.startsWith(relayStatePrefix)) {
154
- RelayState = '';
155
- }
156
-
157
- RelayState = RelayState.replace(relayStatePrefix, '');
158
-
159
- const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
160
-
161
- const parsedResp = await saml.parseAsync(rawResponse);
162
-
163
- const samlConfigs = await configStore.getByIndex({
164
- name: indexNames.entityID,
165
- value: parsedResp.issuer,
166
- });
167
-
168
- if (!samlConfigs || samlConfigs.length === 0) {
169
- throw new JacksonError('SAML configuration not found.', 403);
170
- }
171
-
172
- // TODO: Support multiple matches
173
- const samlConfig = samlConfigs[0];
174
-
175
- let session;
176
-
177
- if (RelayState !== '') {
178
- session = await sessionStore.get(RelayState);
179
- if (!session) {
180
- throw new JacksonError(
181
- 'Unable to validate state from the origin request.',
182
- 403
183
- );
184
- }
185
- }
186
-
187
- let validateOpts = {
188
- thumbprint: samlConfig.idpMetadata.thumbprint,
189
- audience: options.samlAudience,
190
- };
191
-
192
- if (session && session.id) {
193
- validateOpts.inResponseTo = session.id;
194
- }
195
-
196
- const profile = await saml.validateAsync(rawResponse, validateOpts);
197
-
198
- // store details against a code
199
- const code = crypto.randomBytes(20).toString('hex');
200
-
201
- let codeVal = {
202
- profile,
203
- clientID: samlConfig.clientID,
204
- clientSecret: samlConfig.clientSecret,
205
- };
206
-
207
- if (session) {
208
- codeVal.session = session;
209
- }
210
-
211
- await codeStore.put(code, codeVal);
212
-
213
- if (
214
- session &&
215
- session.redirect_uri &&
216
- !allowed.redirect(session.redirect_uri, samlConfig.redirectUrl)
217
- ) {
218
- throw new JacksonError('Redirect URL is not allowed.', 403);
219
- }
220
-
221
- let params = {
222
- code,
223
- };
224
-
225
- if (session && session.state) {
226
- params.state = session.state;
227
- }
228
-
229
- const redirectUrl = redirect.success(
230
- (session && session.redirect_uri) || samlConfig.defaultRedirectUrl,
231
- params
232
- );
233
-
234
- return { redirect_url: redirectUrl };
235
- };
236
-
237
- const token = async (body) => {
238
- const {
239
- client_id,
240
- client_secret,
241
- code_verifier,
242
- code,
243
- grant_type = 'authorization_code',
244
- } = body;
245
-
246
- if (grant_type !== 'authorization_code') {
247
- throw new JacksonError('Unsupported grant_type', 400);
248
- }
249
-
250
- if (!code) {
251
- throw new JacksonError('Please specify code', 400);
252
- }
253
-
254
- const codeVal = await codeStore.get(code);
255
- if (!codeVal || !codeVal.profile) {
256
- throw new JacksonError('Invalid code', 403);
257
- }
258
-
259
- if (client_id && client_secret) {
260
- // check if we have an encoded client_id
261
- if (client_id !== 'dummy' && client_secret !== 'dummy') {
262
- const sp = getEncodedClientId(client_id);
263
- if (!sp) {
264
- // OAuth flow
265
- if (
266
- client_id !== codeVal.clientID ||
267
- client_secret !== codeVal.clientSecret
268
- ) {
269
- throw new JacksonError('Invalid client_id or client_secret', 401);
270
- }
271
- }
272
- }
273
- } else if (code_verifier) {
274
- // PKCE flow
275
- let cv = code_verifier;
276
- if (codeVal.session.code_challenge_method.toLowerCase() === 's256') {
277
- cv = codeVerifier.encode(code_verifier);
278
- }
279
-
280
- if (codeVal.session.code_challenge !== cv) {
281
- throw new JacksonError('Invalid code_verifier', 401);
282
- }
283
- } else if (codeVal && codeVal.session) {
284
- throw new JacksonError(
285
- 'Please specify client_secret or code_verifier',
286
- 401
287
- );
288
- }
289
-
290
- // store details against a token
291
- const token = crypto.randomBytes(20).toString('hex');
292
-
293
- await tokenStore.put(token, codeVal.profile);
294
-
295
- return {
296
- access_token: token,
297
- token_type: 'bearer',
298
- expires_in: options.db.ttl,
299
- };
300
- };
301
-
302
- const userInfo = async (token) => {
303
- const { claims } = await tokenStore.get(token);
304
-
305
- return claims;
306
- };
307
-
308
- module.exports = (opts) => {
309
- configStore = opts.configStore;
310
- sessionStore = opts.sessionStore;
311
- codeStore = opts.codeStore;
312
- tokenStore = opts.tokenStore;
313
- options = opts.opts;
314
-
315
- return {
316
- authorize,
317
- samlResponse,
318
- token,
319
- userInfo,
320
- };
321
- };