Skip to content

Commit d0e4015

Browse files
committed
Changed endpoints, added some TS
-/api/get-repos and /api/get-orgs is now /orgs, and /orgs/repos - Added some type script stuff - Moved middleware to separate file
1 parent 01c67ec commit d0e4015

11 files changed

+301
-65
lines changed

.gitignore

+38
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,41 @@ public/
103103
.dynamodb/
104104

105105
# End of https://www.gitignore.io/api/node
106+
107+
# compiled output
108+
/dist
109+
/node_modules
110+
111+
# Logs
112+
logs
113+
*.log
114+
npm-debug.log*
115+
yarn-debug.log*
116+
yarn-error.log*
117+
lerna-debug.log*
118+
119+
# OS
120+
.DS_Store
121+
122+
# Tests
123+
/coverage
124+
/.nyc_output
125+
126+
# IDEs and editors
127+
/.idea
128+
.project
129+
.classpath
130+
.c9/
131+
*.launch
132+
.settings/
133+
*.sublime-workspace
134+
135+
# IDE - VSCode
136+
.vscode/*
137+
!.vscode/settings.json
138+
!.vscode/tasks.json
139+
!.vscode/launch.json
140+
!.vscode/extensions.json
141+
142+
# Environment Files
143+
*.env

.prettierrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all"
4+
}

app.js

+27-47
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,39 @@
1-
"use strict";
1+
'use strict';
22

3-
const express = require("express");
4-
const jwt = require("express-jwt");
5-
const jwksRsa = require("jwks-rsa");
6-
const getUserOrgs = require("./library/githubUser/getUserOrgs");
7-
const getUser = require("./library/githubUser/getUser");
8-
const getRepos = require("./library/githubUser/getRepos");
9-
const managementApiToken = require("./library/auth0/getManagementApiToken");
10-
const session = require("express-session");
11-
const redis = require("redis");
12-
let RedisStore = require("connect-redis")(session);
3+
const express = require('express');
4+
const getUserOrgs = require('./library/githubUser/getUserOrgs');
5+
const getUser = require('./library/githubUser/getUser');
6+
const getRepos = require('./library/githubUser/getRepos');
7+
const managementApiToken = require('./library/auth0/getManagementApiToken');
8+
const session = require('express-session');
9+
const redis = require('redis');
10+
let RedisStore = require('connect-redis')(session);
1311
let redisClient = redis.createClient();
14-
require("dotenv").config();
12+
require('dotenv').config();
1513

1614
// Create a new Express app
1715
const app = express();
1816

1917
// Logging
20-
const pino = require("pino");
21-
const expressPino = require("express-pino-logger");
22-
const logger = pino({ level: process.env.LOG_LEVEL || "info" });
18+
const pino = require('pino');
19+
const expressPino = require('express-pino-logger');
20+
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
2321
const expressLogger = expressPino({ logger });
2422
app.use(expressLogger);
2523

26-
// Set up Auth0 configuration
27-
const authConfig = {
28-
domain: "devopslibrary.auth0.com",
29-
audience: "kondo-backend"
30-
};
31-
3224
// Define middleware that validates incoming bearer tokens
33-
// using JWKS from devopslibrary.auth0.com
34-
const checkJwt = jwt({
35-
secret: jwksRsa.expressJwtSecret({
36-
cache: true,
37-
rateLimit: true,
38-
jwksRequestsPerMinute: 5,
39-
jwksUri: `https://${authConfig.domain}/.well-known/jwks.json`
40-
}),
41-
42-
audience: authConfig.audience,
43-
issuer: `https://${authConfig.domain}/`,
44-
algorithm: ["RS256"]
45-
});
25+
const checkJwt = require('./src/middleware/authGuard');
4626

4727
// Use session https://github.com/expressjs/session
4828
var sess = {
4929
store: new RedisStore({ client: redisClient }),
50-
secret: "I like Santa",
30+
secret: 'I like Santa',
5131
cookie: {},
5232
resave: false,
53-
saveUninitialized: false
33+
saveUninitialized: false,
5434
};
55-
if (app.get("env") === "production") {
56-
app.set("trust proxy", 1); // trust first proxy
35+
if (app.get('env') === 'production') {
36+
app.set('trust proxy', 1); // trust first proxy
5737
sess.cookie.secure = true; // serve secure cookies
5838
}
5939
app.use(session(sess));
@@ -62,36 +42,36 @@ app.use(session(sess));
6242
// Get management token from Auth0
6343
let authToken;
6444
app.use(express.json());
65-
app.listen(3001, async function() {
66-
logger.info("App is ready");
45+
app.listen(3000, async function() {
46+
logger.info('App is ready');
6747
await managementApiToken.token.then(function(token) {
6848
authToken = token;
6949
});
7050
});
7151

7252
// Retrieve Org Information
73-
app.get("/api/get-orgs", checkJwt, async (req, res) => {
53+
app.get('/orgs', checkJwt, async (req, res) => {
7454
if (!req.session.orgs) {
75-
const userId = req.user.sub.split("|")[1];
55+
const userId = req.user.sub.split('|')[1];
7656
const githubUser = await getUser(authToken, userId);
7757
const githubToken = githubUser.identities[0].access_token;
7858
const orgs = await getUserOrgs(githubToken);
7959
req.session.orgs = orgs;
8060
}
81-
req.log.info("Returning cached Github Orgs");
61+
req.log.info('Returning cached Github Orgs');
8262
res.send(req.session.orgs);
8363
});
8464

8565
// Receive Repo Information
86-
app.get("/api/get-repos", checkJwt, async (req, res) => {
87-
const userId = req.user.sub.split("|")[1];
66+
app.get('/orgs/repos', checkJwt, async (req, res) => {
67+
const userId = req.user.sub.split('|')[1];
8868
const githubUser = await getUser(authToken, userId);
8969
const githubToken = githubUser.identities[0].access_token;
9070
const repos = await getRepos(req.query.org, githubToken);
9171
res.send(repos);
9272
});
9373

94-
process.on("SIGINT", function() {
74+
process.on('SIGINT', function() {
9575
redisClient.quit();
96-
console.log("redis client quit");
76+
console.log('redis client quit');
9777
});

package.json

+20-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"author": "DevOps Library",
1212
"license": "ISC",
1313
"dependencies": {
14+
"@hapi/joi": "^16.1.7",
15+
"@types/jest": "^24.0.23",
1416
"async-redis": "^1.1.7",
1517
"backpack-core": "^0.8.4",
1618
"connect-redis": "^4.0.3",
@@ -35,22 +37,36 @@
3537
"jest": {
3638
"collectCoverage": true,
3739
"collectCoverageFrom": [
38-
"**/*.js"
40+
"**/*.js",
41+
"**/*.ts"
42+
],
43+
"moduleFileExtensions": [
44+
"js",
45+
"json",
46+
"ts"
3947
],
40-
"testEnvironment": "node",
4148
"testResultsProcessor": "jest-sonar-reporter",
4249
"coveragePathIgnorePatterns": [
4350
"/node_modules/",
4451
"/coverage/",
4552
"backpack.config.js",
4653
"/build/"
47-
]
54+
],
55+
"rootDir": "src",
56+
"testRegex": ".spec.ts$",
57+
"transform": {
58+
"^.+\\.(t|j)s$": "ts-jest"
59+
},
60+
"coverageDirectory": "./coverage",
61+
"testEnvironment": "node"
4862
},
4963
"devDependencies": {
5064
"eslint": "^6.6.0",
5165
"eslint-config-prettier": "^6.5.0",
5266
"jest": "^24.9.0",
5367
"jest-sonar-reporter": "^2.0.0",
54-
"supertest": "^4.0.2"
68+
"supertest": "^4.0.2",
69+
"ts-jest": "^24.1.0",
70+
"typescript": "^3.7.2"
5571
}
5672
}

src/config/config.service.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ConfigService } from './config.service';
2+
3+
describe('Config', () => {
4+
it('should be defined', () => {
5+
expect(new ConfigService('development.env')).toBeDefined();
6+
});
7+
});

src/config/config.service.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as dotenv from 'dotenv';
2+
const Joi = require('@hapi/joi');
3+
import * as fs from 'fs';
4+
5+
export type EnvConfig = Record<string, string>;
6+
7+
export class ConfigService {
8+
private readonly envConfig: EnvConfig;
9+
10+
constructor(filePath: string) {
11+
const config = dotenv.parse(fs.readFileSync(filePath));
12+
this.envConfig = this.validateInput(config);
13+
}
14+
15+
/**
16+
* Ensures all needed variables are set, and returns the validated JavaScript object
17+
* including the applied default values.
18+
*/
19+
private validateInput(envConfig: EnvConfig): EnvConfig {
20+
const envVarsSchema = Joi.object({
21+
NODE_ENV: Joi.string()
22+
.valid('development', 'production', 'test', 'provision')
23+
.default('development'),
24+
PORT: Joi.number().default(3000),
25+
AUTH0_CLIENT_ID: Joi.string().required(),
26+
AUTH0_CLIENT_SECRET: Joi.string().required(),
27+
AUTH0_AUDIENCE: Joi.string().required(),
28+
AUTH0_DOMAIN: Joi.string().required(),
29+
});
30+
31+
const { error, value: validatedEnvConfig } = envVarsSchema.validate(
32+
envConfig,
33+
);
34+
if (error) {
35+
throw new Error(`Config validation error: ${error.message}`);
36+
}
37+
return validatedEnvConfig;
38+
}
39+
40+
get(key: string): string {
41+
return this.envConfig[key];
42+
}
43+
}

src/middleware/authGuard.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import jwt from 'express-jwt';
2+
import jwksRsa from 'jwks-rsa';
3+
import { ConfigService } from '../config/config.service';
4+
5+
// Set up Auth0 configuration
6+
let config: ConfigService = new ConfigService(
7+
`${process.env.NODE_ENV || 'development'}.env`,
8+
);
9+
10+
const checkJwt = jwt({
11+
secret: jwksRsa.expressJwtSecret({
12+
cache: true,
13+
rateLimit: true,
14+
jwksRequestsPerMinute: 5,
15+
jwksUri: `https://${config.get('AUTH0_DOMAIN')}/.well-known/jwks.json`,
16+
}),
17+
18+
audience: config.get('AUTH0_AUDIENCE'),
19+
issuer: `https://${config.get('AUTH0_DOMAIN')}/`,
20+
algorithm: ['RS256'],
21+
});

tsconfig.build.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4+
}

tsconfig.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"module": "commonjs",
4+
"declaration": true,
5+
"removeComments": true,
6+
"emitDecoratorMetadata": true,
7+
"experimentalDecorators": true,
8+
"target": "es2017",
9+
"sourceMap": true,
10+
"outDir": "./dist",
11+
"baseUrl": "./",
12+
"incremental": true
13+
},
14+
"exclude": ["node_modules", "dist"]
15+
}

tslint.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"defaultSeverity": "error",
3+
"extends": ["tslint:recommended"],
4+
"jsRules": {
5+
"no-unused-expression": true
6+
},
7+
"rules": {
8+
"quotemark": [true, "single"],
9+
"member-access": [false],
10+
"ordered-imports": [false],
11+
"max-line-length": [true, 150],
12+
"member-ordering": [false],
13+
"interface-name": [false],
14+
"arrow-parens": false,
15+
"object-literal-sort-keys": false
16+
},
17+
"rulesDirectory": []
18+
}

0 commit comments

Comments
 (0)