Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal/cf-jwt-verification #38

Merged
merged 7 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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