Skip to content

Commit

Permalink
Merge pull request #38 from acmutd/internal/hmac-sha-256-security
Browse files Browse the repository at this point in the history
Internal/cf-jwt-verification
  • Loading branch information
harshasrikara authored Jan 13, 2021
2 parents b824d41 + 18c3c08 commit 9537f92
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 15 deletions.
16 changes: 13 additions & 3 deletions functions/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
"axios": "^0.19.2",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"express": "^4.17.1",
"express-jwt": "6.0.0",
"firebase-admin": "^8.10.0",
"firebase-functions": "^3.6.1",
"firebase-functions": "^3.13.0",
"helmet": "^3.23.1",
"jwks-rsa": "^1.8.1",
"md5": "^2.3.0",
Expand Down
39 changes: 39 additions & 0 deletions functions/src/application/typeform.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as Sentry from "@sentry/node";
// import * as functions from "firebase-functions";
import { firestore } from "../admin/admin";
// import crypto from "crypto";

type definition = {
id: string;
Expand All @@ -13,6 +15,7 @@ type form_response = {
landed_at: string;
submitted_at: string;
definition: definition;
hidden: any;
answers: any; //im lazy, someone plz do this
};
interface typeform {
Expand All @@ -29,10 +32,32 @@ interface qa {

export const typeform_webhook = async (request: any, response: any): Promise<void> => {
const data: typeform = request.body;
//if (!verify_signature(request.header("Typeform-Signature"), request.body)) {
// response.json({
// message: "Unauthorized",
// act: actualSig,
// exp: request.header("Typeform-Signature"),
// });
//}

const qa_responses: qa[] = [];
const questions = data.form_response.definition.fields;
const answers = data.form_response.answers;
const hidden = data.form_response.hidden;

for (const [key, value] of Object.entries(hidden || {})) {
// do not save fields that expose security vulnerabilities
// never pass in a jwt via typeform to acm-core
if (key === "jwt") {
continue;
}
const qa_res = {
question: key as string,
answer: value as string,
type: "hidden_field",
};
qa_responses.push(qa_res);
}

questions.forEach((element: any, index: number) => {
const qa_res = {
Expand Down Expand Up @@ -60,3 +85,17 @@ export const typeform_webhook = async (request: any, response: any): Promise<voi
});
}
};

// const verify_signature = (expectedSig: any, body: any) => {
// const hash = crypto
// .createHmac("sha256", functions.config().typeform.secret)
// .update(JSON.stringify(body))
// .digest("base64");
// const actualSig = `sha256=${hash}`;
// console.log("expected: " + expectedSig);
// console.log("actual: " + actualSig);
// if (actualSig !== expectedSig) {
// return false;
// }
// return true;
// };
10 changes: 6 additions & 4 deletions functions/src/authTypes.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
interface JwtClaims {
iss: string;
email?: string;
iss: number;
sub: string;
aud: string;
iat: string;
nbf?: number;
iat: number;
exp: string;
azp: string;
gty: string;
azp?: string;
gty?: string;
}

declare namespace Express {
Expand Down
2 changes: 1 addition & 1 deletion functions/src/custom/vanity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const build_vanity_link = functions.firestore
let subdomain = "";
let slashtag = "";

const fullname_question = "full name";
const fullname_question = "name";
const email_question = "email";
const destination_question = "vanity link";
const primary_domain_question = "primary domain";
Expand Down
82 changes: 82 additions & 0 deletions functions/src/express_configs/express_cf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Initialize express & setup all middleware here
* Do not handle any routes in this file
*/

import * as functions from "firebase-functions";
import express from "express";
import jwt from "express-jwt";
import jwksRsa from "jwks-rsa";
import * as Sentry from "@sentry/node";
import * as Tracing from "@sentry/tracing";
import cors from "cors";
import * as bodyParser from "body-parser";
import { Response, Request } from "express";

const app = express();

//setup sentry
if (functions.config()?.sentry?.dns) {
Sentry.init({
dsn: functions.config().sentry.dns,
integrations: [
// enable HTTP calls tracing
new Sentry.Integrations.Http({ tracing: true }),
// enable Express.js middleware tracing
new Tracing.Integrations.Express({ app }),
],
tracesSampleRate: 1.0,
});
}

// The request handler must be the first middleware on the app
app.use(Sentry.Handlers.requestHandler());
// TracingHandler creates a trace for every incoming request
app.use(Sentry.Handlers.tracingHandler());

app.use(cors({ origin: true }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// The error handler must be before any other error middleware and after all controllers
app.use(
Sentry.Handlers.errorHandler({
shouldHandleError(error) {
// Capture all errors over 400
if ((error.status as number) >= 400) {
return true;
}
return false;
},
})
);

function errorHandler(error: Error, request: Request, response: Response, next: (err?: Error) => void) {
Sentry.captureException(error);
response.status(500).json({
message: "Error encountered",
error: error,
});
next(error); //just incase we have additional error handlers
}
app.use(errorHandler);

// Automatically send uncaught exception errors to Sentry
process.on("uncaughtException", (err) => Sentry.captureException(err));

const checkJwt = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${functions.config().cloudflare.domain}/cdn-cgi/access/certs`,
}),

audience: functions.config().cloudflare.audience,
issuer: `https://${functions.config().cloudflare.domain}`,
algorithms: ["RS256"],
});
//user must be authenticated on auth0 for the requests to go through
app.use(checkJwt);

export default app;
1 change: 1 addition & 0 deletions functions/src/express_configs/express_open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());

app.use(cors({ origin: true }));
// app.use(bodyParser.raw({ type: "application/json" }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

Expand Down
2 changes: 1 addition & 1 deletion functions/src/express_configs/express_secure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const checkJwt = jwt({
issuer: `https://${functions.config().auth0.domain}/`,
algorithms: ["RS256"],
});
//uåser must be authenticated on auth0 for the requests to go through
//user must be authenticated on auth0 for the requests to go through
app.use(checkJwt);

export default app;
15 changes: 14 additions & 1 deletion functions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Handle express routing in this file
*/
import app_cf from "./express_configs/express_cf";
import app_secure from "./express_configs/express_secure";
import app_open from "./express_configs/express_open";

Expand All @@ -16,7 +17,7 @@ import * as eventFunctions from "./events/events";
import * as vanityFunctions from "./custom/vanity";
import * as hacktoberfestFunctions from "./custom/hacktoberfest";
import * as typeformFunctions from "./application/typeform";
import * as errorFunctions from "./services/errorService";
import * as errorFunctions from "./services/ErrorService";

//this will match every call made to this api.
app_secure.all("/", (request, response, next) => {
Expand Down Expand Up @@ -111,6 +112,18 @@ app_open.get("/debug-sentry", errorFunctions.debug_sentry);
*/
app_open.post("/htf-development", hacktoberfestFunctions.retrieve_record);

/**
* Cloudflare access protected endpoint
*/
app_cf.get("/verify", (req, res) => {
console.log(req.user);
res.json({
message: "Successful execution of jwt verification",
email: req.user.email,
});
});

export const cf = functions.https.onRequest(app_cf);
export const api = functions.https.onRequest(app_secure);
export const challenge = functions.https.onRequest(app_open);

Expand Down
5 changes: 1 addition & 4 deletions functions/src/services/ErrorService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */

import * as Sentry from "@sentry/node";
export default class ErrorService {
export class ErrorService {
static generatePostError<T>(reqObj: any, exampleObj: T) {
const response = { error: { message: "You are missing the " } };
const missing: string[] = [];
Expand Down

0 comments on commit 9537f92

Please sign in to comment.