Skip to content

Commit ca40142

Browse files
committed
feature: add settings middlware
1 parent 51016ed commit ca40142

3 files changed

Lines changed: 370 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import express from "express";
2+
import request from "supertest";
3+
import errorHandler from "../../utils/errorhandler";
4+
import jwt from "jsonwebtoken";
5+
import * as mocks from "../../utils/mocks/settings";
6+
import settingsMiddleware from "../settings";
7+
8+
describe("Settings middleware", () => {
9+
let app;
10+
beforeAll(() => {
11+
process.env.API_SECRET = "secret";
12+
app = express();
13+
app.use(settingsMiddleware);
14+
app.get("/", (req, res) => {
15+
res.json({ projectId: req.projectId });
16+
});
17+
app.use((err, req, res, next) => {
18+
errorHandler(err, req, res, next);
19+
});
20+
});
21+
22+
it("should throw an error if no API key is present", async () => {
23+
const res = await request(app).get("/");
24+
expect(res.status).toBe(401);
25+
});
26+
it("should throw an error if invalid API key is present", async () => {
27+
const res = await request(app)
28+
.get("/")
29+
.set("X-MicroAPI-ProjectKey", "crapKey");
30+
expect(res.status).toBe(401);
31+
expect(res.body.error).toBe("Invalid API key found");
32+
});
33+
34+
it("should throw invalid settings error", async () => {
35+
const mockSettings = jest
36+
.spyOn(mocks, "mockSettings")
37+
.mockImplementation(() => {
38+
return mocks.errorMockSettings;
39+
});
40+
41+
const apiKey = jwt.sign(
42+
{ projectId: 123 },
43+
Buffer.from(process.env.API_SECRET, "base64")
44+
);
45+
const res = await request(app)
46+
.get("/")
47+
.set("X-MicroAPI-ProjectKey", apiKey);
48+
expect(mockSettings).toHaveBeenCalled();
49+
expect(res.status).toBe(400);
50+
expect(res.body.error).toBe("Invalid settings found");
51+
52+
mockSettings.mockRestore();
53+
});
54+
55+
it("should return status code 200 and decoded projectId", async () => {
56+
const apiKey = jwt.sign(
57+
{ projectId: 123 },
58+
Buffer.from(process.env.API_SECRET, "base64")
59+
);
60+
const res = await request(app)
61+
.get("/")
62+
.set("X-MicroAPI-ProjectKey", apiKey);
63+
expect(res.status).toBe(200);
64+
expect(res.body.projectId).toBe(123);
65+
});
66+
});

src/middleware/settings.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require("dotenv").config();
2+
import jwt from "jsonwebtoken";
3+
import CustomError from "../utils/customError";
4+
import { parseSettings } from "../utils/settingsParser";
5+
import { mockSettings as fetchSettings } from "../utils/mocks/settings";
6+
const log = require("debug")("log");
7+
8+
export const getProjectId = (apiKey) => {
9+
//verify/decode projectID from API key
10+
const decoded = jwt.verify(
11+
apiKey,
12+
Buffer.from(process.env.API_SECRET, "base64")
13+
);
14+
return decoded.projectId;
15+
};
16+
17+
//get settings from external DB or endpoint
18+
//function might be modified to accomodate both sources
19+
export const getSettings = async (apiKey) => {
20+
//fool linter
21+
log(apiKey);
22+
23+
//mock the request for now with mocksettings
24+
//settings need to come from source
25+
//validate the settings by matching against predefined schema
26+
const settings = parseSettings(fetchSettings(), true);
27+
return settings;
28+
};
29+
30+
const settingsMiddleware = async (req, res, next) => {
31+
/* In multi-tenant app, projectID is retreived from API key in a custom HTTP header
32+
** For now we stick with multi-tenant and we will customize this to cater for **
33+
** single tenancy architecture in time where projectIDs are irrelevant **
34+
** In retrospect, expiry of API keys should be from MicroAPI, **
35+
** so making a request for settings with an invalid API key will be rejected **
36+
*/
37+
try {
38+
// we are calling our custom HTTP header X-MicroAPI-ProjectKey
39+
const apiKey = req.headers["x-microapi-projectkey"];
40+
if (!apiKey) throw new CustomError(401, "No API key found");
41+
42+
//validate apiKey
43+
let projectId;
44+
try {
45+
projectId = getProjectId(apiKey);
46+
} catch (error) {
47+
throw new CustomError(401, "Invalid API key found");
48+
}
49+
50+
// get settings from parent DB/source
51+
const settings = await getSettings(apiKey);
52+
if (settings.errors) {
53+
throw new CustomError(400, "Invalid settings found", settings.errors);
54+
}
55+
56+
//attach setting to request body
57+
req.settings = settings;
58+
req.projectId = projectId;
59+
60+
//pass to next middleware
61+
next();
62+
} catch (error) {
63+
next(error);
64+
}
65+
};
66+
67+
export default settingsMiddleware;

src/utils/mocks/settings.js

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
export const mockSettings = () => [
2+
{
3+
setting_name: "Email Verification Callback",
4+
setting_type: "String",
5+
setting_key: "emailVerifyCallback",
6+
setting_required: true,
7+
setting_value: "/emailCallback",
8+
},
9+
{
10+
setting_name: "Password Reset Callback",
11+
setting_type: "String",
12+
setting_key: "passwordResetCallback",
13+
setting_required: true,
14+
setting_value: "/passwordResetCallback",
15+
},
16+
{
17+
setting_name: "Login Success Callback",
18+
setting_type: "String",
19+
setting_key: "successCallback",
20+
setting_required: true,
21+
setting_value: "successCallback",
22+
},
23+
{
24+
setting_name: "MongoDB URI",
25+
setting_type: "String",
26+
setting_key: "mongoDbUri",
27+
setting_required: true,
28+
setting_value: "dbUriString",
29+
},
30+
{
31+
setting_name: "Facebook Credentials",
32+
setting_type: "Array",
33+
setting_key: "facebookAuthProvider",
34+
setting_required: false,
35+
setting_value: [
36+
{
37+
setting_name: "Facebook Application ID",
38+
setting_type: "String",
39+
setting_key: "appID",
40+
setting_value: null,
41+
setting_required: true,
42+
},
43+
{
44+
setting_name: "Facebook Application Secret",
45+
setting_type: "String",
46+
setting_key: "appSecret",
47+
setting_value: null,
48+
setting_required: true,
49+
},
50+
],
51+
},
52+
{
53+
setting_name: "Twitter Credentials",
54+
setting_type: "Array",
55+
setting_key: "twitterAuthProvider",
56+
setting_required: false,
57+
setting_value: [
58+
{
59+
setting_name: "Twitter Consumer Key",
60+
setting_type: "String",
61+
setting_key: "key",
62+
setting_value: null,
63+
setting_required: true,
64+
},
65+
{
66+
setting_name: "Twitter Consumer Secret",
67+
setting_type: "String",
68+
setting_key: "secret",
69+
setting_value: null,
70+
setting_required: true,
71+
},
72+
],
73+
},
74+
{
75+
setting_name: "Github Credentials",
76+
setting_type: "Array",
77+
setting_key: "githubAuthProvider",
78+
setting_required: false,
79+
setting_value: [
80+
{
81+
setting_name: "Github Client ID",
82+
setting_type: "String",
83+
setting_key: "clientID",
84+
setting_value: null,
85+
setting_required: true,
86+
},
87+
{
88+
setting_name: "Github Client Secret",
89+
setting_type: "String",
90+
setting_key: "clientSecret",
91+
setting_value: null,
92+
setting_required: true,
93+
},
94+
],
95+
},
96+
{
97+
setting_name: "Google Credentials",
98+
setting_type: "Array",
99+
setting_key: "googleAuthProvider",
100+
setting_required: false,
101+
setting_value: [
102+
{
103+
setting_name: "Google Client ID",
104+
setting_type: "String",
105+
setting_key: "clientID",
106+
setting_value: null,
107+
setting_required: true,
108+
},
109+
{
110+
setting_name: "Google Client Secret",
111+
setting_type: "String",
112+
setting_key: "clientSecret",
113+
setting_value: null,
114+
setting_required: true,
115+
},
116+
],
117+
},
118+
];
119+
120+
export const errorMockSettings = [
121+
{
122+
setting_name: "Email Verification Callback",
123+
setting_type: "String",
124+
setting_key: "emailVerifyCallback",
125+
setting_required: true,
126+
setting_value: "/emailCallback",
127+
},
128+
{
129+
setting_name: "Password Reset Callback",
130+
setting_type: "String",
131+
setting_key: "passwordResetCallback",
132+
setting_required: true,
133+
setting_value: "/passwordResetCallback",
134+
},
135+
{
136+
setting_name: "Login Success Callback",
137+
setting_type: "String",
138+
setting_key: "successCallback",
139+
setting_required: true,
140+
setting_value: "successCallback",
141+
},
142+
{
143+
setting_name: "MongoDB URI",
144+
setting_type: "String",
145+
setting_key: "mongoDbUri",
146+
setting_required: true,
147+
setting_value: null,
148+
},
149+
{
150+
setting_name: "Facebook Credentials",
151+
setting_type: "Array",
152+
setting_key: "facebookAuthProvider",
153+
setting_required: false,
154+
setting_value: [
155+
{
156+
setting_name: "Facebook Application ID",
157+
setting_type: "String",
158+
setting_key: "appID",
159+
setting_value: null,
160+
setting_required: true,
161+
},
162+
{
163+
setting_name: "Facebook Application Secret",
164+
setting_type: "String",
165+
setting_key: "appSecret",
166+
setting_value: null,
167+
setting_required: true,
168+
},
169+
],
170+
},
171+
{
172+
setting_name: "Twitter Credentials",
173+
setting_type: "Array",
174+
setting_key: "twitterAuthProvider",
175+
setting_required: false,
176+
setting_value: [
177+
{
178+
setting_name: "Twitter Consumer Key",
179+
setting_type: "String",
180+
setting_key: "key",
181+
setting_value: null,
182+
setting_required: true,
183+
},
184+
{
185+
setting_name: "Twitter Consumer Secret",
186+
setting_type: "String",
187+
setting_key: "secret",
188+
setting_value: null,
189+
setting_required: true,
190+
},
191+
],
192+
},
193+
{
194+
setting_name: "Github Credentials",
195+
setting_type: "Array",
196+
setting_key: "githubAuthProvider",
197+
setting_required: false,
198+
setting_value: [
199+
{
200+
setting_name: "Github Client ID",
201+
setting_type: "String",
202+
setting_key: "clientID",
203+
setting_value: null,
204+
setting_required: true,
205+
},
206+
{
207+
setting_name: "Github Client Secret",
208+
setting_type: "String",
209+
setting_key: "clientSecret",
210+
setting_value: null,
211+
setting_required: true,
212+
},
213+
],
214+
},
215+
{
216+
setting_name: "Google Credentials",
217+
setting_type: "Array",
218+
setting_key: "googleAuthProvider",
219+
setting_required: false,
220+
setting_value: [
221+
{
222+
setting_name: "Google Client ID",
223+
setting_type: "String",
224+
setting_key: "clientID",
225+
setting_value: null,
226+
setting_required: true,
227+
},
228+
{
229+
setting_name: "Google Client Secret",
230+
setting_type: "String",
231+
setting_key: "clientSecret",
232+
setting_value: null,
233+
setting_required: true,
234+
},
235+
],
236+
},
237+
];

0 commit comments

Comments
 (0)