@boxyhq/saml-jackson 0.3.1 → 0.3.2-beta.258
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/controller/oauth.js +4 -10
- package/dist/db/db.d.ts +1 -1
- package/dist/db/db.js +3 -4
- package/dist/index.js +1 -2
- package/dist/saml/saml.js +4 -14
- package/dist/saml/x509.js +1 -3
- package/package.json +8 -18
- package/Dockerfile +0 -31
- package/README.md +0 -440
- package/dist/env.d.ts +0 -22
- package/dist/env.js +0 -35
- package/dist/jackson.d.ts +0 -1
- package/dist/jackson.js +0 -153
package/dist/controller/oauth.js
CHANGED
@@ -89,10 +89,7 @@ class OAuthController {
|
|
89
89
|
// TODO: Support multiple matches
|
90
90
|
samlConfig = samlConfigs[0];
|
91
91
|
}
|
92
|
-
else if (client_id &&
|
93
|
-
client_id !== '' &&
|
94
|
-
client_id !== 'undefined' &&
|
95
|
-
client_id !== 'null') {
|
92
|
+
else if (client_id && client_id !== '' && client_id !== 'undefined' && client_id !== 'null') {
|
96
93
|
// if tenant and product are encoded in the client_id then we parse it and check for the relevant config(s)
|
97
94
|
const sp = getEncodedClientId(client_id);
|
98
95
|
if (sp === null || sp === void 0 ? void 0 : sp.tenant) {
|
@@ -189,9 +186,7 @@ class OAuthController {
|
|
189
186
|
codeVal.session = session;
|
190
187
|
}
|
191
188
|
yield this.codeStore.put(code, codeVal);
|
192
|
-
if (session &&
|
193
|
-
session.redirect_uri &&
|
194
|
-
!allowed.redirect(session.redirect_uri, samlConfig.redirectUrl)) {
|
189
|
+
if (session && session.redirect_uri && !allowed.redirect(session.redirect_uri, samlConfig.redirectUrl)) {
|
195
190
|
throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
|
196
191
|
}
|
197
192
|
const params = {
|
@@ -206,7 +201,7 @@ class OAuthController {
|
|
206
201
|
}
|
207
202
|
token(body) {
|
208
203
|
return __awaiter(this, void 0, void 0, function* () {
|
209
|
-
const { client_id, client_secret, code_verifier, code, grant_type = 'authorization_code'
|
204
|
+
const { client_id, client_secret, code_verifier, code, grant_type = 'authorization_code' } = body;
|
210
205
|
if (grant_type !== 'authorization_code') {
|
211
206
|
throw new error_1.JacksonError('Unsupported grant_type', 400);
|
212
207
|
}
|
@@ -223,8 +218,7 @@ class OAuthController {
|
|
223
218
|
const sp = getEncodedClientId(client_id);
|
224
219
|
if (!sp) {
|
225
220
|
// OAuth flow
|
226
|
-
if (client_id !== codeVal.clientID ||
|
227
|
-
client_secret !== codeVal.clientSecret) {
|
221
|
+
if (client_id !== codeVal.clientID || client_secret !== codeVal.clientSecret) {
|
228
222
|
throw new error_1.JacksonError('Invalid client_id or client_secret', 401);
|
229
223
|
}
|
230
224
|
}
|
package/dist/db/db.d.ts
CHANGED
package/dist/db/db.js
CHANGED
@@ -30,6 +30,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
30
30
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
31
31
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
32
32
|
};
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
33
34
|
const encrypter = __importStar(require("./encrypter"));
|
34
35
|
const mem_1 = __importDefault(require("./mem"));
|
35
36
|
const mongo_1 = __importDefault(require("./mongo"));
|
@@ -86,11 +87,9 @@ class DB {
|
|
86
87
|
return store_1.default.new(namespace, this, ttl);
|
87
88
|
}
|
88
89
|
}
|
89
|
-
|
90
|
+
exports.default = {
|
90
91
|
new: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
91
|
-
const encryptionKey = options.encryptionKey
|
92
|
-
? Buffer.from(options.encryptionKey, 'latin1')
|
93
|
-
: null;
|
92
|
+
const encryptionKey = options.encryptionKey ? Buffer.from(options.encryptionKey, 'latin1') : null;
|
94
93
|
switch (options.engine) {
|
95
94
|
case 'redis':
|
96
95
|
return new DB(yield redis_1.default.new(options), encryptionKey);
|
package/dist/index.js
CHANGED
@@ -40,8 +40,7 @@ const defaultOpts = (opts) => {
|
|
40
40
|
newOpts.idpEnabled = newOpts.idpEnabled === true;
|
41
41
|
newOpts.db = newOpts.db || {};
|
42
42
|
newOpts.db.engine = newOpts.db.engine || 'sql';
|
43
|
-
newOpts.db.url =
|
44
|
-
newOpts.db.url || 'postgresql://postgres:postgres@localhost:5432/postgres';
|
43
|
+
newOpts.db.url = newOpts.db.url || 'postgresql://postgres:postgres@localhost:5432/postgres';
|
45
44
|
newOpts.db.type = newOpts.db.type || 'postgres'; // Only needed if DB_ENGINE is sql.
|
46
45
|
newOpts.db.ttl = (newOpts.db.ttl || 300) * 1; // TTL for the code, session and token stores (in seconds)
|
47
46
|
newOpts.db.cleanupLimit = (newOpts.db.cleanupLimit || 1000) * 1; // Limit cleanup of TTL entries to this many items at a time
|
package/dist/saml/saml.js
CHANGED
@@ -52,10 +52,7 @@ const signRequest = (xml, signingKey) => {
|
|
52
52
|
const sig = new xml_crypto_1.default.SignedXml();
|
53
53
|
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
54
54
|
sig.signingKey = signingKey;
|
55
|
-
sig.addReference(authnXPath, [
|
56
|
-
'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
|
57
|
-
'http://www.w3.org/2001/10/xml-exc-c14n#',
|
58
|
-
], 'http://www.w3.org/2001/04/xmlenc#sha256');
|
55
|
+
sig.addReference(authnXPath, ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'], 'http://www.w3.org/2001/04/xmlenc#sha256');
|
59
56
|
sig.computeSignature(xml, {
|
60
57
|
location: { reference: authnXPath + issuerXPath, action: 'after' },
|
61
58
|
});
|
@@ -125,10 +122,7 @@ const validateAsync = (rawAssertion, options) => __awaiter(void 0, void 0, void
|
|
125
122
|
profile.claims = claims_1.default.map(profile.claims);
|
126
123
|
// some providers don't return the id in the assertion, we set it to a sha256 hash of the email
|
127
124
|
if (!profile.claims.id) {
|
128
|
-
profile.claims.id = crypto_1.default
|
129
|
-
.createHash('sha256')
|
130
|
-
.update(profile.claims.email)
|
131
|
-
.digest('hex');
|
125
|
+
profile.claims.id = crypto_1.default.createHash('sha256').update(profile.claims.email).digest('hex');
|
132
126
|
}
|
133
127
|
}
|
134
128
|
resolve(profile);
|
@@ -163,16 +157,12 @@ const parseMetadataAsync = (idpMeta) => __awaiter(void 0, void 0, void 0, functi
|
|
163
157
|
X509Certificate = cd['X509Certificate'][0];
|
164
158
|
}
|
165
159
|
}
|
166
|
-
const ssoSvc = ssoDesRec['SingleSignOnService'] ||
|
167
|
-
ssoDesRec['AssertionConsumerService'] ||
|
168
|
-
[];
|
160
|
+
const ssoSvc = ssoDesRec['SingleSignOnService'] || ssoDesRec['AssertionConsumerService'] || [];
|
169
161
|
for (const ssoSvcRec of ssoSvc) {
|
170
162
|
if (rambda.pathOr('', '$.Binding', ssoSvcRec).endsWith('HTTP-POST')) {
|
171
163
|
ssoPostUrl = rambda.path('$.Location', ssoSvcRec);
|
172
164
|
}
|
173
|
-
else if (rambda
|
174
|
-
.pathOr('', '$.Binding', ssoSvcRec)
|
175
|
-
.endsWith('HTTP-Redirect')) {
|
165
|
+
else if (rambda.pathOr('', '$.Binding', ssoSvcRec).endsWith('HTTP-Redirect')) {
|
176
166
|
ssoRedirectUrl = rambda.path('$.Location', ssoSvcRec);
|
177
167
|
}
|
178
168
|
}
|
package/dist/saml/x509.js
CHANGED
@@ -40,9 +40,7 @@ const alg = {
|
|
40
40
|
};
|
41
41
|
const generate = () => __awaiter(void 0, void 0, void 0, function* () {
|
42
42
|
const keys = yield crypto.subtle.generateKey(alg, true, ['sign', 'verify']);
|
43
|
-
const extensions = [
|
44
|
-
new x509.BasicConstraintsExtension(false, undefined, true),
|
45
|
-
];
|
43
|
+
const extensions = [new x509.BasicConstraintsExtension(false, undefined, true)];
|
46
44
|
extensions.push(new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true));
|
47
45
|
if (keys.publicKey) {
|
48
46
|
extensions.push(yield x509.SubjectKeyIdentifierExtension.create(keys.publicKey));
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "0.3.
|
3
|
+
"version": "0.3.2-beta.258",
|
4
4
|
"license": "Apache 2.0",
|
5
5
|
"description": "SAML 2.0 service",
|
6
6
|
"main": "dist/index.js",
|
@@ -18,15 +18,7 @@
|
|
18
18
|
"scripts": {
|
19
19
|
"build": "tsc -p tsconfig.build.json",
|
20
20
|
"prepublishOnly": "npm run build",
|
21
|
-
"
|
22
|
-
"dev": "cross-env IDP_ENABLED=true nodemon --config nodemon.json src/jackson.ts",
|
23
|
-
"mongo": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson nodemon --config nodemon.json src/jackson.ts",
|
24
|
-
"sql": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=sql DB_TYPE=postgres DB_URL=postgres://postgres:postgres@localhost:5432/jackson nodemon --config nodemon.json src/jackson.ts",
|
25
|
-
"pre-loaded": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mem PRE_LOADED_CONFIG='./_config' nodemon --config nodemon.json src/jackson.ts",
|
26
|
-
"pre-loaded-db": "cross-env JACKSON_API_KEYS=secret PRE_LOADED_CONFIG='./_config' nodemon --config nodemon.json src/jackson.ts",
|
27
|
-
"test": "tap --ts --timeout=100 --coverage src/**/*.test.ts ",
|
28
|
-
"dev-dbs": "docker-compose -f ./_dev/docker-compose.yml up -d",
|
29
|
-
"dev-dbs-destroy": "docker-compose -f ./_dev/docker-compose.yml down --volumes --remove-orphans",
|
21
|
+
"test": "tap --ts --timeout=100 --coverage test/**/*.test.ts ",
|
30
22
|
"db:migration:generate": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:generate --config ormconfig.js -n Initial"
|
31
23
|
},
|
32
24
|
"tap": {
|
@@ -56,19 +48,17 @@
|
|
56
48
|
"xmlbuilder": "15.1.1"
|
57
49
|
},
|
58
50
|
"devDependencies": {
|
59
|
-
"@types/express": "
|
60
|
-
"@types/node": "
|
51
|
+
"@types/express": "4.17.13",
|
52
|
+
"@types/node": "16.11.17",
|
61
53
|
"@types/redis": "4.0.11",
|
62
54
|
"@types/sinon": "10.0.6",
|
63
55
|
"@types/tap": "15.0.5",
|
64
|
-
"@typescript-eslint/eslint-plugin": "
|
65
|
-
"@typescript-eslint/parser": "
|
66
|
-
"
|
67
|
-
"eslint": "
|
68
|
-
"eslint-config-prettier": "^8.3.0",
|
56
|
+
"@typescript-eslint/eslint-plugin": "5.8.1",
|
57
|
+
"@typescript-eslint/parser": "5.8.1",
|
58
|
+
"eslint": "8.5.0",
|
59
|
+
"eslint-config-prettier": "8.3.0",
|
69
60
|
"husky": "7.0.4",
|
70
61
|
"lint-staged": "12.1.4",
|
71
|
-
"nodemon": "2.0.15",
|
72
62
|
"prettier": "2.5.1",
|
73
63
|
"sinon": "12.0.1",
|
74
64
|
"tap": "15.1.5",
|
package/Dockerfile
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
# Install dependencies only when needed
|
2
|
-
FROM node:16.13.1-alpine3.14 AS build
|
3
|
-
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
4
|
-
RUN apk add --no-cache libc6-compat
|
5
|
-
WORKDIR /app
|
6
|
-
COPY src/ src/
|
7
|
-
COPY package.json package-lock.json tsconfig*.json ./
|
8
|
-
RUN npm install
|
9
|
-
RUN npm run build
|
10
|
-
|
11
|
-
# Production image, copy all the files and run next
|
12
|
-
FROM node:16.13.1-alpine3.14 AS runner
|
13
|
-
WORKDIR /app
|
14
|
-
|
15
|
-
ENV NODE_OPTIONS="--max-http-header-size=81920"
|
16
|
-
ENV NODE_ENV production
|
17
|
-
|
18
|
-
RUN addgroup -g 1001 -S nodejs
|
19
|
-
RUN adduser -S nodejs -u 1001
|
20
|
-
|
21
|
-
COPY --from=build /app/dist ./dist
|
22
|
-
COPY --from=build /app/package.json ./package.json
|
23
|
-
COPY --from=build /app/package-lock.json ./package-lock.json
|
24
|
-
RUN npm ci --only=production
|
25
|
-
|
26
|
-
USER nodejs
|
27
|
-
|
28
|
-
EXPOSE 5000
|
29
|
-
EXPOSE 6000
|
30
|
-
|
31
|
-
CMD [ "node", "dist/jackson.js" ]
|
package/README.md
DELETED
@@ -1,440 +0,0 @@
|
|
1
|
-
# SAML Jackson (not fiction anymore)
|
2
|
-
|
3
|
-
SAML service [SAML in a box from BoxyHQ]
|
4
|
-
|
5
|
-
You need someone like Jules Winnfield to save you from the vagaries of SAML login.
|
6
|
-
|
7
|
-
## Source code visualizer
|
8
|
-
|
9
|
-
[CodeSee codebase visualizer](https://app.codesee.io/maps/public/53e91640-23b5-11ec-a724-79d7dd589517)
|
10
|
-
|
11
|
-
## Getting Started
|
12
|
-
|
13
|
-
There are two ways to use this repo.
|
14
|
-
|
15
|
-
- As an npm library
|
16
|
-
- As a separate service
|
17
|
-
|
18
|
-
## Install as an npm library
|
19
|
-
|
20
|
-
Jackson is available as an [npm package](https://www.npmjs.com/package/@boxyhq/saml-jackson) that can be integrated into any web application framework (like Express.js for example). Please file an issue or submit a PR if you encounter any issues with your choice of framework.
|
21
|
-
|
22
|
-
```bash
|
23
|
-
npm i @boxyhq/saml-jackson
|
24
|
-
```
|
25
|
-
|
26
|
-
### Add Express Routes
|
27
|
-
|
28
|
-
```javascript
|
29
|
-
// express
|
30
|
-
const express = require('express');
|
31
|
-
const router = express.Router();
|
32
|
-
const cors = require('cors'); // needed if you are calling the token userinfo endpoints from the frontend
|
33
|
-
|
34
|
-
// Set the required options. Refer to https://github.com/boxyhq/jackson#configuration for the full list
|
35
|
-
const opts = {
|
36
|
-
externalUrl: 'https://my-cool-app.com',
|
37
|
-
samlAudience: 'https://my-cool-app.com',
|
38
|
-
samlPath: '/sso/oauth/saml',
|
39
|
-
db: {
|
40
|
-
engine: 'mongo',
|
41
|
-
url: 'mongodb://localhost:27017/my-cool-app',
|
42
|
-
}
|
43
|
-
};
|
44
|
-
|
45
|
-
|
46
|
-
let apiController;
|
47
|
-
let oauthController;
|
48
|
-
// Please note that the initialization of @boxyhq/saml-jackson is async, you cannot run it at the top level
|
49
|
-
// Run this in a function where you initialise the express server.
|
50
|
-
async function init() {
|
51
|
-
const ret = await require('@boxyhq/saml-jackson')(opts);
|
52
|
-
apiController = ret.apiController;
|
53
|
-
oauthController = ret.oauthController;
|
54
|
-
}
|
55
|
-
|
56
|
-
// express.js middlewares needed to parse json and x-www-form-urlencoded
|
57
|
-
router.use(express.json());
|
58
|
-
router.use(express.urlencoded({ extended: true }));
|
59
|
-
|
60
|
-
// SAML config API. You should pass this route through your authentication checks, do not expose this on the public interface without proper authentication in place.
|
61
|
-
router.post('/api/v1/saml/config', async (req, res) => {
|
62
|
-
try {
|
63
|
-
// apply your authentication flow (or ensure this route has passed through your auth middleware)
|
64
|
-
...
|
65
|
-
|
66
|
-
// only when properly authenticated, call the config function
|
67
|
-
res.json(await apiController.config(req.body));
|
68
|
-
} catch (err) {
|
69
|
-
res.status(500).json({
|
70
|
-
error: err.message,
|
71
|
-
});
|
72
|
-
}
|
73
|
-
});
|
74
|
-
// fetch config
|
75
|
-
router.get('/api/v1/saml/config', async (req, res) => {
|
76
|
-
try {
|
77
|
-
// apply your authentication flow (or ensure this route has passed through your auth middleware)
|
78
|
-
...
|
79
|
-
|
80
|
-
// only when properly authenticated, call the config function
|
81
|
-
res.json(await apiController.config(req.query));
|
82
|
-
} catch (err) {
|
83
|
-
res.status(500).json({
|
84
|
-
error: err.message,
|
85
|
-
});
|
86
|
-
}
|
87
|
-
});
|
88
|
-
// delete config
|
89
|
-
router.delete('/api/v1/saml/config', async (req, res) => {
|
90
|
-
try {
|
91
|
-
// apply your authentication flow (or ensure this route has passed through your auth middleware)
|
92
|
-
...
|
93
|
-
|
94
|
-
// only when properly authenticated, call the config function
|
95
|
-
await apiController.deleteConfig(req.body);
|
96
|
-
res.status(200).end();
|
97
|
-
} catch (err) {
|
98
|
-
res.status(500).json({
|
99
|
-
error: err.message,
|
100
|
-
});
|
101
|
-
}
|
102
|
-
});
|
103
|
-
// OAuth 2.0 flow
|
104
|
-
router.get('/oauth/authorize', async (req, res) => {
|
105
|
-
try {
|
106
|
-
const { redirect_url } = await oauthController.authorize(req.query);
|
107
|
-
|
108
|
-
res.redirect(redirect_url);
|
109
|
-
} catch (err) {
|
110
|
-
const { message, statusCode = 500 } = err;
|
111
|
-
|
112
|
-
res.status(statusCode).send(message);
|
113
|
-
}
|
114
|
-
});
|
115
|
-
|
116
|
-
router.post('/oauth/saml', async (req, res) => {
|
117
|
-
try {
|
118
|
-
const { redirect_url } = await oauthController.samlResponse(req.body);
|
119
|
-
|
120
|
-
res.redirect(redirect_url);
|
121
|
-
} catch (err) {
|
122
|
-
const { message, statusCode = 500 } = err;
|
123
|
-
|
124
|
-
res.status(statusCode).send(message);
|
125
|
-
}
|
126
|
-
});
|
127
|
-
|
128
|
-
router.post('/oauth/token', cors(), async (req, res) => {
|
129
|
-
try {
|
130
|
-
const result = await oauthController.token(req.body);
|
131
|
-
|
132
|
-
res.json(result);
|
133
|
-
} catch (err) {
|
134
|
-
const { message, statusCode = 500 } = err;
|
135
|
-
|
136
|
-
res.status(statusCode).send(message);
|
137
|
-
}
|
138
|
-
});
|
139
|
-
|
140
|
-
router.get('/oauth/userinfo', async (req, res) => {
|
141
|
-
try {
|
142
|
-
let token = extractAuthToken(req);
|
143
|
-
|
144
|
-
// check for query param
|
145
|
-
if (!token) {
|
146
|
-
token = req.query.access_token;
|
147
|
-
}
|
148
|
-
|
149
|
-
if (!token) {
|
150
|
-
res.status(401).json({ message: 'Unauthorized' });
|
151
|
-
}
|
152
|
-
|
153
|
-
const profile = await oauthController.userInfo(token);
|
154
|
-
|
155
|
-
res.json(profile);
|
156
|
-
} catch (err) {
|
157
|
-
const { message, statusCode = 500 } = err;
|
158
|
-
|
159
|
-
res.status(statusCode).json({ message });
|
160
|
-
}
|
161
|
-
});
|
162
|
-
|
163
|
-
// set the router
|
164
|
-
app.use('/sso', router);
|
165
|
-
|
166
|
-
```
|
167
|
-
|
168
|
-
## Deployment as a service: Docker
|
169
|
-
|
170
|
-
The docker container can be found at [boxyhq/jackson](https://hub.docker.com/r/boxyhq/jackson/tags). It is preferable to use a specific version instead of the `latest` tag. Jackson uses two ports (configurable if needed, see below) 5000 and 6000. 6000 is the internal port and ideally should not be exposed to a public network.
|
171
|
-
|
172
|
-
```bash
|
173
|
-
docker run -p 5000:5000 -p 6000:6000 boxyhq/jackson:78e9099d
|
174
|
-
```
|
175
|
-
|
176
|
-
Refer to <https://github.com/boxyhq/jackson#configuration> for the full configuration.
|
177
|
-
|
178
|
-
Kubernetes and docker-compose deployment files will be coming soon.
|
179
|
-
|
180
|
-
## Usage
|
181
|
-
|
182
|
-
### 1. Setting up SAML with your customer's Identity Provider
|
183
|
-
|
184
|
-
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.
|
185
|
-
|
186
|
-
### 1.1 SAML profile/claims/attributes mapping
|
187
|
-
|
188
|
-
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.
|
189
|
-
|
190
|
-
| SAML Attribute | Jackson mapping |
|
191
|
-
| ---------------------------------------------------------------------- | --------------- |
|
192
|
-
| <http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier> | id |
|
193
|
-
| <http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress> | email |
|
194
|
-
| <http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname> | firstName |
|
195
|
-
| <http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname> | lastName |
|
196
|
-
|
197
|
-
### 2. SAML config API
|
198
|
-
|
199
|
-
Once your customer has set up the SAML app on their Identity Provider, the Identity Provider will generate an IdP or SP metadata file. Some Identity Providers only generate an IdP metadata file but it usually works for the SP login flow as well. It is an XML file that contains various attributes Jackson needs to validate incoming SAML login requests. This step is the equivalent of setting an OAuth 2.0 app and generating a client ID and client secret that will be used in the login flow.
|
200
|
-
|
201
|
-
You will need to provide a place in the UI for your customers (The account settings page is usually a good place for this) to configure this and then call the API below.
|
202
|
-
|
203
|
-
The following API call sets up the configuration in Jackson:
|
204
|
-
|
205
|
-
```bash
|
206
|
-
curl --location --request POST 'http://localhost:6000/api/v1/saml/config' \
|
207
|
-
--header 'Authorization: Api-Key <Jackson API Key>' \
|
208
|
-
--header 'Content-Type: application/x-www-form-urlencoded' \
|
209
|
-
--data-urlencode 'rawMetadata=<IdP/SP metadata XML>' \
|
210
|
-
--data-urlencode 'defaultRedirectUrl=http://localhost:3000/login/saml' \
|
211
|
-
--data-urlencode 'redirectUrl=["http://localhost:3000/*"]' \
|
212
|
-
--data-urlencode 'tenant=boxyhq.com' \
|
213
|
-
--data-urlencode 'product=demo'
|
214
|
-
```
|
215
|
-
|
216
|
-
- rawMetadata: The XML metadata file your customer gets from their Identity Provider
|
217
|
-
- defaultRedirectUrl: The redirect URL to use in the IdP login flow. Jackson will call this URL after completing an IdP login flow
|
218
|
-
- redirectUrl: JSON encoded array containing a list of allowed redirect URLs. Jackson will disallow any redirects not on this list (or not the default URL above)
|
219
|
-
- 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
|
220
|
-
- 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
|
221
|
-
|
222
|
-
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.
|
223
|
-
|
224
|
-
#### 2.1 SAML get config API
|
225
|
-
|
226
|
-
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` or `tenant` and `product` combination.
|
227
|
-
|
228
|
-
```bash
|
229
|
-
curl -G --location 'http://localhost:6000/api/v1/saml/config' \
|
230
|
-
--header 'Authorization: Api-Key <Jackson API Key>' \
|
231
|
-
--header 'Content-Type: application/x-www-form-urlencoded' \
|
232
|
-
--data-urlencode 'tenant=boxyhq.com' \
|
233
|
-
--data-urlencode 'product=demo'
|
234
|
-
```
|
235
|
-
|
236
|
-
```bash
|
237
|
-
curl -G --location 'http://localhost:6000/api/v1/saml/config' \
|
238
|
-
--header 'Authorization: Api-Key <Jackson API Key>' \
|
239
|
-
--header 'Content-Type: application/x-www-form-urlencoded' \
|
240
|
-
--data-urlencode 'clientID=<Client ID>'
|
241
|
-
```
|
242
|
-
|
243
|
-
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.
|
244
|
-
|
245
|
-
#### 2.2 SAML delete config API
|
246
|
-
|
247
|
-
This endpoint can be used to delete an existing IdP metadata.
|
248
|
-
|
249
|
-
```bash
|
250
|
-
curl -X "DELETE" --location 'http://localhost:6000/api/v1/saml/config' \
|
251
|
-
--header 'Authorization: Api-Key <Jackson API Key>' \
|
252
|
-
--header 'Content-Type: application/x-www-form-urlencoded' \
|
253
|
-
--data-urlencode 'tenant=boxyhq.com' \
|
254
|
-
--data-urlencode 'product=demo'
|
255
|
-
```
|
256
|
-
|
257
|
-
```bash
|
258
|
-
curl -X "DELETE" --location 'http://localhost:6000/api/v1/saml/config' \
|
259
|
-
--header 'Authorization: Api-Key <Jackson API Key>' \
|
260
|
-
--header 'Content-Type: application/x-www-form-urlencoded' \
|
261
|
-
--data-urlencode 'clientID=<Client ID>'
|
262
|
-
--data-urlencode 'clientSecret=<Client Secret>'
|
263
|
-
```
|
264
|
-
|
265
|
-
### 3. OAuth 2.0 Flow
|
266
|
-
|
267
|
-
Jackson has been designed to abstract the SAML login flow as a pure OAuth 2.0 flow. This means it's compatible with any standard OAuth 2.0 library out there, both client-side and server-side. It is important to remember that SAML is configured per customer unlike OAuth 2.0 where you can have a single OAuth app supporting logins for all customers.
|
268
|
-
|
269
|
-
Jackson also supports the PKCE authorization flow (<https://oauth.net/2/pkce/>), so you can protect your SPAs.
|
270
|
-
|
271
|
-
If for any reason you need to implement the flow on your own, the steps are outlined below:
|
272
|
-
|
273
|
-
### 4. Authorize
|
274
|
-
|
275
|
-
The OAuth flow begins with redirecting your user to the `authorize` URL:
|
276
|
-
|
277
|
-
```bash
|
278
|
-
https://localhost:5000/oauth/authorize
|
279
|
-
?response_type=code&provider=saml
|
280
|
-
&client_id=<clientID or tenant and product query params as described in the SAML config API section above>
|
281
|
-
&redirect_uri=<redirect URL>
|
282
|
-
&state=<randomly generated state id>
|
283
|
-
```
|
284
|
-
|
285
|
-
- response_type=code: This is the only supported type for now but maybe extended in the future
|
286
|
-
- 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`.
|
287
|
-
- tenant: Optionally you can provide a dummy `client_id` and specify the `tenant` and `product` custom attributes (if your OAuth 2.0 library allows it).
|
288
|
-
- product: Should be specified if specifying `tenant` above
|
289
|
-
- redirect_uri: This is where the user will be taken back once the authorization flow is complete
|
290
|
-
- 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
|
291
|
-
|
292
|
-
### 5. Code Exchange
|
293
|
-
|
294
|
-
After successful authorization, the user is redirected back to the `redirect_uri`. The query parameters will include the `code` and `state` parameters. You should validate that the state matches the one you sent in the `authorize` request.
|
295
|
-
|
296
|
-
The code can then be exchanged for a token by making the following request:
|
297
|
-
|
298
|
-
```bash
|
299
|
-
curl --request POST \
|
300
|
-
--url 'http://localhost:5000/oauth/token' \
|
301
|
-
--header 'content-type: application/x-www-form-urlencoded' \
|
302
|
-
--data 'grant_type=authorization_code' \
|
303
|
-
--data 'client_id=<clientID or tenant and product query params as described in the SAML config API section above>' \
|
304
|
-
--data 'client_secret=<clientSecret or any arbitrary value if using the tenant and product in the clientID>' \
|
305
|
-
--data 'redirect_uri=<redirect URL>' \
|
306
|
-
--data 'code=<code from the query parameter above>'
|
307
|
-
```
|
308
|
-
|
309
|
-
- grant_type=authorization_code: This is the only supported flow, for now. We might extend this in the future
|
310
|
-
- 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`.
|
311
|
-
- 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
|
312
|
-
- 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
|
313
|
-
|
314
|
-
If everything goes well you should receive a JSON response that includes the access token. This token is needed for the next step where we fetch the user profile.
|
315
|
-
|
316
|
-
```json
|
317
|
-
{
|
318
|
-
"access_token": <access token>,
|
319
|
-
"token_type": "bearer",
|
320
|
-
"expires_in": 300
|
321
|
-
}
|
322
|
-
```
|
323
|
-
|
324
|
-
### 6. Profile Request
|
325
|
-
|
326
|
-
The short-lived access token can now be used to request the user's profile. You'll need to make the following request:
|
327
|
-
|
328
|
-
```bash
|
329
|
-
curl --request GET \
|
330
|
-
--url https://localhost:5000/oauth/userinfo \
|
331
|
-
--header 'authorization: Bearer <access token>' \
|
332
|
-
--header 'content-type: application/json'
|
333
|
-
```
|
334
|
-
|
335
|
-
If everything goes well you should receive a JSON response with the user's profile:
|
336
|
-
|
337
|
-
```json
|
338
|
-
{
|
339
|
-
"id": <id from the Identity Provider>,
|
340
|
-
"email": "sjackson@coolstartup.com",
|
341
|
-
"firstName": "SAML"
|
342
|
-
"lastName": "Jackson"
|
343
|
-
}
|
344
|
-
```
|
345
|
-
|
346
|
-
- id: The id of the user as provided by the Identity Provider
|
347
|
-
- email: The email address of the user as provided by the Identity Provider
|
348
|
-
- firstName: The first name of the user as provided by the Identity Provider
|
349
|
-
- lastName: The last name of the user as provided by the Identity Provider
|
350
|
-
|
351
|
-
## Examples
|
352
|
-
|
353
|
-
To Do
|
354
|
-
|
355
|
-
## Database Support
|
356
|
-
|
357
|
-
Jackson currently supports the following databases.
|
358
|
-
|
359
|
-
- Postgres
|
360
|
-
- MySQL
|
361
|
-
- MariaDB
|
362
|
-
- MongoDB
|
363
|
-
- Redis
|
364
|
-
|
365
|
-
## Configuration
|
366
|
-
|
367
|
-
Configuration is done via env vars (and in the case of the npm library via an options object).
|
368
|
-
|
369
|
-
The following options are supported and will have to be configured during deployment.
|
370
|
-
|
371
|
-
| Key | Description | Default |
|
372
|
-
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
|
373
|
-
| HOST_URL | The URL to bind to | `localhost` |
|
374
|
-
| HOST_PORT | The port to bind to | `5000` |
|
375
|
-
| EXTERNAL_URL (npm: externalUrl) | The public URL to reach this service, used internally for documenting the SAML configuration instructions. | `http://{HOST_URL}:{HOST_PORT}` |
|
376
|
-
| INTERNAL_HOST_URL | The URL to bind to expose the internal APIs. Do not configure this to a public network. | `localhost` |
|
377
|
-
| INTERNAL_HOST_PORT | The port to bind to for the internal APIs. | `6000` |
|
378
|
-
| JACKSON_API_KEYS | A comma separated list of API keys that will be validated when serving the Config API requests | |
|
379
|
-
| 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` |
|
380
|
-
| 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` |
|
381
|
-
| DB_ENGINE (npm: db.engine) | Supported values are `redis`, `sql`, `mongo`, `mem`. | `sql` |
|
382
|
-
| DB_URL (npm: db.url) | The database URL to connect to. For example `postgres://postgres:postgres@localhost:5450/jackson` | |
|
383
|
-
| DB_TYPE (npm: db.type) | Only needed when DB_ENGINE is `sql`. Supported values are `postgres`, `mysql`, `mariadb`. | `postgres` |
|
384
|
-
| DB_TTL (npm: db.ttl) | TTL for the code, session and token stores (in seconds). | 300 |
|
385
|
-
| DB_CLEANUP_LIMIT (npm: db.cleanupLimit) | Limit cleanup of TTL entries to this number. | 1000 |
|
386
|
-
| DB_ENCRYPTION_KEY (npm: db.encryptionKey) | To encrypt data at rest specify a 32 character key. | |
|
387
|
-
| PRE_LOADED_CONFIG | If you only need a single tenant or a handful of pre-configured tenants then this config will help you read and load SAML configs. It works well with the mem DB engine so you don't have to configure any external databases for this to work (though it works with those as well). This is a path (absolute or relative) to a directory that contains files organized in the format described in the next section. | |
|
388
|
-
|
389
|
-
## Pre-loaded SAML Configuration
|
390
|
-
|
391
|
-
If PRE_LOADED_CONFIG is set then it should point to a directory with the following structure (example below):-
|
392
|
-
|
393
|
-
```bash
|
394
|
-
boxyhq.js
|
395
|
-
boxyhq.xml
|
396
|
-
anothertenant.js
|
397
|
-
anothertenant.xml
|
398
|
-
```
|
399
|
-
|
400
|
-
The JS file has the following structure:-
|
401
|
-
|
402
|
-
```javascript
|
403
|
-
module.exports = {
|
404
|
-
defaultRedirectUrl: 'http://localhost:3000/login/saml',
|
405
|
-
redirectUrl: '["http://localhost:3000/*", "http://localhost:5000/*"]',
|
406
|
-
tenant: 'boxyhq.com',
|
407
|
-
product: 'demo',
|
408
|
-
};
|
409
|
-
```
|
410
|
-
|
411
|
-
The XML file (should share the name with the .js file) is the raw XML metadata file you receive from your Identity Provider. Please ensure it is saved in the `utf-8` encoding.
|
412
|
-
|
413
|
-
The config and XML above correspond to the `SAML API config` (see below).
|
414
|
-
|
415
|
-
## SAML Login flows
|
416
|
-
|
417
|
-
There are two kinds of SAML login flows - SP-initiated and IdP-initiated. We highly recommend sticking to the SP-initiated flow since it is more secure but Jackson also supports the IdP-initiated flow if you enable it. For an in-depth understanding of SAML and the two flows please refer to Okta's comprehensive guide - <https://developer.okta.com/docs/concepts/saml/>.
|
418
|
-
|
419
|
-
## Contributing
|
420
|
-
|
421
|
-
Thanks for taking the time to contribute! Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are appreciated.
|
422
|
-
|
423
|
-
Please try to create bug reports that are:
|
424
|
-
|
425
|
-
- _Reproducible._ Include steps to reproduce the problem.
|
426
|
-
- _Specific._ Include as much detail as possible: which version, what environment, etc.
|
427
|
-
- _Unique._ Do not duplicate existing opened issues.
|
428
|
-
- _Scoped to a Single Bug._ One bug per report.
|
429
|
-
|
430
|
-
## Support
|
431
|
-
|
432
|
-
Reach out to the maintainer at one of the following places:
|
433
|
-
|
434
|
-
- [GitHub Discussions](https://github.com/boxyhq/jackson/discussions)
|
435
|
-
- [GitHub Issues](https://github.com/boxyhq/jackson/issues)
|
436
|
-
- The email which is located [in GitHub profile](https://github.com/deepakprabhakara)
|
437
|
-
|
438
|
-
## License
|
439
|
-
|
440
|
-
[Apache 2.0 License](https://github.com/boxyhq/jackson/blob/main/LICENSE)
|
package/dist/env.d.ts
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
declare const env: {
|
2
|
-
hostUrl: string;
|
3
|
-
hostPort: number;
|
4
|
-
externalUrl: string;
|
5
|
-
samlPath: string;
|
6
|
-
samlAudience: string;
|
7
|
-
preLoadedConfig: string | undefined;
|
8
|
-
internalHostUrl: string;
|
9
|
-
internalHostPort: number;
|
10
|
-
apiKeys: string[];
|
11
|
-
idpEnabled: string | undefined;
|
12
|
-
db: {
|
13
|
-
engine: string | undefined;
|
14
|
-
url: string | undefined;
|
15
|
-
type: string | undefined;
|
16
|
-
ttl: string | undefined;
|
17
|
-
encryptionKey: string | undefined;
|
18
|
-
cleanupLimit: string | undefined;
|
19
|
-
};
|
20
|
-
useInternalServer: boolean;
|
21
|
-
};
|
22
|
-
export default env;
|
package/dist/env.js
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
const hostUrl = process.env.HOST_URL || 'localhost';
|
4
|
-
const hostPort = +(process.env.HOST_PORT || '5000');
|
5
|
-
const externalUrl = process.env.EXTERNAL_URL || 'http://' + hostUrl + ':' + hostPort;
|
6
|
-
const samlPath = process.env.SAML_PATH || '/oauth/saml';
|
7
|
-
const internalHostUrl = process.env.INTERNAL_HOST_URL || 'localhost';
|
8
|
-
const internalHostPort = +(process.env.INTERNAL_HOST_PORT || '6000');
|
9
|
-
const apiKeys = (process.env.JACKSON_API_KEYS || '').split(',');
|
10
|
-
const samlAudience = process.env.SAML_AUDIENCE || 'https://saml.boxyhq.com';
|
11
|
-
const preLoadedConfig = process.env.PRE_LOADED_CONFIG;
|
12
|
-
const idpEnabled = process.env.IDP_ENABLED;
|
13
|
-
const db = {
|
14
|
-
engine: process.env.DB_ENGINE,
|
15
|
-
url: process.env.DB_URL,
|
16
|
-
type: process.env.DB_TYPE,
|
17
|
-
ttl: process.env.DB_TTL,
|
18
|
-
encryptionKey: process.env.DB_ENCRYPTION_KEY,
|
19
|
-
cleanupLimit: process.env.DB_CLEANUP_LIMIT,
|
20
|
-
};
|
21
|
-
const env = {
|
22
|
-
hostUrl,
|
23
|
-
hostPort,
|
24
|
-
externalUrl,
|
25
|
-
samlPath,
|
26
|
-
samlAudience,
|
27
|
-
preLoadedConfig,
|
28
|
-
internalHostUrl,
|
29
|
-
internalHostPort,
|
30
|
-
apiKeys,
|
31
|
-
idpEnabled,
|
32
|
-
db,
|
33
|
-
useInternalServer: !(hostUrl === internalHostUrl && hostPort === internalHostPort),
|
34
|
-
};
|
35
|
-
exports.default = env;
|
package/dist/jackson.d.ts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
export {};
|
package/dist/jackson.js
DELETED
@@ -1,153 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
-
});
|
10
|
-
};
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
-
};
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
-
const cors_1 = __importDefault(require("cors"));
|
16
|
-
const express_1 = __importDefault(require("express"));
|
17
|
-
const index_1 = __importDefault(require("./index"));
|
18
|
-
const utils_1 = require("./controller/utils");
|
19
|
-
const env_1 = __importDefault(require("./env"));
|
20
|
-
let apiController;
|
21
|
-
let oauthController;
|
22
|
-
const oauthPath = '/oauth';
|
23
|
-
const apiPath = '/api/v1/saml';
|
24
|
-
const app = (0, express_1.default)();
|
25
|
-
app.use(express_1.default.json());
|
26
|
-
app.use(express_1.default.urlencoded({ extended: true }));
|
27
|
-
app.get(oauthPath + '/authorize', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
28
|
-
try {
|
29
|
-
// @ts-ignore
|
30
|
-
const { redirect_url } = yield oauthController.authorize(req.query);
|
31
|
-
res.redirect(redirect_url);
|
32
|
-
}
|
33
|
-
catch (err) {
|
34
|
-
const { message, statusCode = 500 } = err;
|
35
|
-
res.status(statusCode).send(message);
|
36
|
-
}
|
37
|
-
}));
|
38
|
-
app.post(env_1.default.samlPath, (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
39
|
-
try {
|
40
|
-
const { redirect_url } = yield oauthController.samlResponse(req.body);
|
41
|
-
res.redirect(redirect_url);
|
42
|
-
}
|
43
|
-
catch (err) {
|
44
|
-
const { message, statusCode = 500 } = err;
|
45
|
-
res.status(statusCode).send(message);
|
46
|
-
}
|
47
|
-
}));
|
48
|
-
app.post(oauthPath + '/token', (0, cors_1.default)(), (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
49
|
-
try {
|
50
|
-
const result = yield oauthController.token(req.body);
|
51
|
-
res.json(result);
|
52
|
-
}
|
53
|
-
catch (err) {
|
54
|
-
const { message, statusCode = 500 } = err;
|
55
|
-
res.status(statusCode).send(message);
|
56
|
-
}
|
57
|
-
}));
|
58
|
-
app.get(oauthPath + '/userinfo', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
59
|
-
try {
|
60
|
-
let token = (0, utils_1.extractAuthToken)(req);
|
61
|
-
// check for query param
|
62
|
-
if (!token) {
|
63
|
-
// @ts-ignore
|
64
|
-
token = req.query.access_token;
|
65
|
-
}
|
66
|
-
if (!token) {
|
67
|
-
return res.status(401).json({ message: 'Unauthorized' });
|
68
|
-
}
|
69
|
-
const profile = yield oauthController.userInfo(token);
|
70
|
-
res.json(profile);
|
71
|
-
}
|
72
|
-
catch (err) {
|
73
|
-
const { message, statusCode = 500 } = err;
|
74
|
-
res.status(statusCode).json({ message });
|
75
|
-
}
|
76
|
-
}));
|
77
|
-
const server = app.listen(env_1.default.hostPort, () => __awaiter(void 0, void 0, void 0, function* () {
|
78
|
-
console.log(`🚀 The path of the righteous server: http://${env_1.default.hostUrl}:${env_1.default.hostPort}`);
|
79
|
-
// @ts-ignore
|
80
|
-
const ctrlrModule = yield (0, index_1.default)(env_1.default);
|
81
|
-
apiController = ctrlrModule.apiController;
|
82
|
-
oauthController = ctrlrModule.oauthController;
|
83
|
-
}));
|
84
|
-
// Internal routes, recommended not to expose this to the public interface though it would be guarded by API key(s)
|
85
|
-
let internalApp = app;
|
86
|
-
if (env_1.default.useInternalServer) {
|
87
|
-
internalApp = (0, express_1.default)();
|
88
|
-
internalApp.use(express_1.default.json());
|
89
|
-
internalApp.use(express_1.default.urlencoded({ extended: true }));
|
90
|
-
}
|
91
|
-
const validateApiKey = (token) => {
|
92
|
-
return env_1.default.apiKeys.includes(token);
|
93
|
-
};
|
94
|
-
internalApp.post(apiPath + '/config', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
95
|
-
try {
|
96
|
-
const apiKey = (0, utils_1.extractAuthToken)(req);
|
97
|
-
if (!validateApiKey(apiKey)) {
|
98
|
-
res.status(401).send('Unauthorized');
|
99
|
-
return;
|
100
|
-
}
|
101
|
-
res.json(yield apiController.config(req.body));
|
102
|
-
}
|
103
|
-
catch (err) {
|
104
|
-
const { message } = err;
|
105
|
-
res.status(500).json({
|
106
|
-
error: message,
|
107
|
-
});
|
108
|
-
}
|
109
|
-
}));
|
110
|
-
internalApp.get(apiPath + '/config', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
111
|
-
try {
|
112
|
-
const apiKey = (0, utils_1.extractAuthToken)(req);
|
113
|
-
if (!validateApiKey(apiKey)) {
|
114
|
-
res.status(401).send('Unauthorized');
|
115
|
-
return;
|
116
|
-
}
|
117
|
-
// @ts-ignore
|
118
|
-
res.json(yield apiController.getConfig(req.query));
|
119
|
-
}
|
120
|
-
catch (err) {
|
121
|
-
const { message } = err;
|
122
|
-
res.status(500).json({
|
123
|
-
error: message,
|
124
|
-
});
|
125
|
-
}
|
126
|
-
}));
|
127
|
-
internalApp.delete(apiPath + '/config', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
128
|
-
try {
|
129
|
-
const apiKey = (0, utils_1.extractAuthToken)(req);
|
130
|
-
if (!validateApiKey(apiKey)) {
|
131
|
-
res.status(401).send('Unauthorized');
|
132
|
-
return;
|
133
|
-
}
|
134
|
-
yield apiController.deleteConfig(req.body);
|
135
|
-
res.status(200).end();
|
136
|
-
}
|
137
|
-
catch (err) {
|
138
|
-
const { message } = err;
|
139
|
-
res.status(500).json({
|
140
|
-
error: message,
|
141
|
-
});
|
142
|
-
}
|
143
|
-
}));
|
144
|
-
let internalServer = server;
|
145
|
-
if (env_1.default.useInternalServer) {
|
146
|
-
internalServer = internalApp.listen(env_1.default.internalHostPort, () => __awaiter(void 0, void 0, void 0, function* () {
|
147
|
-
console.log(`🚀 The path of the righteous internal server: http://${env_1.default.internalHostUrl}:${env_1.default.internalHostPort}`);
|
148
|
-
}));
|
149
|
-
}
|
150
|
-
module.exports = {
|
151
|
-
server,
|
152
|
-
internalServer,
|
153
|
-
};
|