Skip to content

Commit

Permalink
Backend auth (#160)
Browse files Browse the repository at this point in the history
* started imlementing universal search
GARBAGE CODE ALERT WIP

* split apart code into 2 components

* added noresults

* Made search page accessible thru search button

* Zoom to mural upon click in search menu

* fixed pr comments

* got rid of unnecessary comment

* fixed search card width

* Close sidebar on mural select

* started working on backend auth

* token example

* changed authentication to be middleware

* added middleware to all the put/post routes

* removed a log

* removed token error handling from controller

* removed token error handling from controller

* added token to frontend calls

* added eb script to download auth key off bucket
  • Loading branch information
JDziewonski98 authored Apr 5, 2021
1 parent 1499391 commit c601365
Show file tree
Hide file tree
Showing 14 changed files with 10,530 additions and 45 deletions.
20 changes: 20 additions & 0 deletions .ebextensions/privatekey.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Resources:
AWSEBAutoScalingGroup:
Metadata:
AWS::CloudFormation::Authentication:
S3Auth:
type: "s3"
buckets: ["elasticbeanstalk-us-east-1-115630815585"]
roleName:
"Fn::GetOptionSetting":
Namespace: "aws:autoscaling:launchconfiguration"
OptionName: "IamInstanceProfile"
DefaultValue: "aws-elasticbeanstalk-ec2-role"
files:
# Private key
"/mu-auth-73f4d05ed60d.json":
mode: "000400"
owner: root
group: root
authentication: "S3Auth"
source: https://elasticbeanstalk-us-east-1-115630815585.s3.amazonaws.com/privatekey/mu-auth-73f4d05ed60d.json
10,455 changes: 10,432 additions & 23 deletions backend/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"firebase-admin": "^9.5.0",
"nodemon": "^2.0.6",
"pg": "^8.4.2",
"pg-hstore": "^2.3.3",
Expand All @@ -31,8 +32,8 @@
"devDependencies": {
"@types/cors": "^2.8.9",
"cross-env": "^7.0.2",
"jest": "^26.6.3",
"supertest": "^6.0.1",
"ts-jest": "^26.4.3",
"jest": "^26.6.3"
"ts-jest": "^26.4.3"
}
}
21 changes: 11 additions & 10 deletions backend/src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BoroughController } from "../controllers/borough.controller";
import { ArtistController } from "../controllers/artist.controller";
import { TourController } from "../controllers/tour.controller";
import { Application } from "express";
import { authMiddleware } from "../auth/AuthMiddleware"

export class Routes {
public muralController: MuralController = new MuralController();
Expand All @@ -16,51 +17,51 @@ export class Routes {
app
.route("/mural")
.get(this.muralController.showAll.bind(this.muralController))
.post(this.muralController.create.bind(this.muralController));
.post(authMiddleware, this.muralController.create.bind(this.muralController));

app
.route("/mural/:id")
.get(this.muralController.show.bind(this.muralController))
.put(this.muralController.update.bind(this.muralController));
.put(authMiddleware, this.muralController.update.bind(this.muralController));

app
.route("/borough")
.get(this.boroughController.showAll.bind(this.boroughController))
.post(this.boroughController.create.bind(this.boroughController));
.post(authMiddleware, this.boroughController.create.bind(this.boroughController));

app
.route("/borough/:id")
.get(this.boroughController.show.bind(this.boroughController))
.put(this.boroughController.update.bind(this.boroughController));
.put(authMiddleware, this.boroughController.update.bind(this.boroughController));

app
.route("/artist")
.get(this.artistController.showAll.bind(this.artistController))
.post(this.artistController.create.bind(this.artistController));
.post(authMiddleware, this.artistController.create.bind(this.artistController));

app
.route("/artist/:id")
.get(this.artistController.show.bind(this.artistController))
.put(this.artistController.update.bind(this.artistController));
.put(authMiddleware, this.artistController.update.bind(this.artistController));

app
.route("/tour")
.get(this.tourController.showAll.bind(this.tourController))
.post(this.tourController.create.bind(this.tourController));
.post(authMiddleware, this.tourController.create.bind(this.tourController));

app
.route("/tour/:id")
.get(this.tourController.show.bind(this.tourController))
.put(this.tourController.update.bind(this.tourController));
.put(authMiddleware, this.tourController.update.bind(this.tourController));

app
.route("/collection")
.get(this.collectionController.showAll.bind(this.collectionController))
.post(this.collectionController.create.bind(this.collectionController));
.post(authMiddleware, this.collectionController.create.bind(this.collectionController));

app
.route("/collection/:id")
.get(this.collectionController.show.bind(this.collectionController))
.put(this.collectionController.update.bind(this.collectionController));
.put(authMiddleware, this.collectionController.update.bind(this.collectionController));
}
}
29 changes: 29 additions & 0 deletions backend/src/auth/AuthMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { FBAuth } from "./FBAuth"
import { TokenError } from "../controllers/customErrors/TokenError";
import { Request, Response } from "express";

/**
* Middleware that can be added at route level to add firebase authentication to a controller
* Expects a valid firebase jwt token in headers.authorization
* @param req HTTP request
* @param res HTTP response
* @param next reference to controller function to be executed if auth passes
*/
export const authMiddleware = async (req: Request, res: Response, next: any) => {
try {if (req.headers.authorization) {
await FBAuth.auth()
.verifyIdToken(req.headers.authorization)
.then((decodedToken) => {
next()
})
.catch((e) => {
console.log(e);
throw new TokenError("Invalid Token!");
});
} else {
throw new TokenError("No Token!");
}}
catch (e) {
res.status(400).json({ error: "Unauthorized!" });
}
};
6 changes: 6 additions & 0 deletions backend/src/auth/FBAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import admin from "firebase-admin";

export const FBAuth = admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "gs://mu-auth.appspot.com",
});
1 change: 0 additions & 1 deletion backend/src/controllers/artist.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ArtistService } from "../services/artist.service";

export class ArtistController {
public artistService: ArtistService = new ArtistService();

/**
* POST /artist
* @param req HTTP request containing ArtistInterface attributes:
Expand Down
6 changes: 6 additions & 0 deletions backend/src/controllers/customErrors/TokenError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class TokenError extends Error {
constructor(message: string) {
super(message);
this.name = "TokenError";
}
}
1 change: 0 additions & 1 deletion backend/src/controllers/tour.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { TourService } from "../services/tour.service";

export class TourController {
public tourService: TourService = new TourService();

/**
* POST /tour to create a new tour associated with murals by id
* @param req HTTP request containing a "tour" attribute containing TourInterface attributes
Expand Down
3 changes: 2 additions & 1 deletion backend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"outDir": "./build",
"baseUrl":"./src",
"esModuleInterop": true,
"strict": true
"strict": true,
"resolveJsonModule": true
},
"include":["src/**/*.ts"],
"exclude":["node_modules"]
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function App() {
const [signingIn, setSigningIn] = useState<boolean>(false);
const [signInError, setSignInError] = useState<string>("");
const [user, setUser] = useState<any>({});
const [JWTtoken, setJWTtoken] = useState<any>();

const [sidebarOpen, setSidebarOpen] = useState<boolean>(false);
const [activeForm, setActiveForm] = useState<FORM>(FORM.MURAL);
Expand All @@ -50,6 +51,7 @@ function App() {
.then(() => {
setSigningIn(false);
setSignInError("");
setJWTtoken(FirebaseAuth.currentUser?.getIdToken(true))
})
.catch((error: any) => {
console.log(error.message);
Expand All @@ -68,6 +70,7 @@ function App() {
*/
const handleSignout = async () => {
await FirebaseAuth.signOut();
setJWTtoken(null);
};

/**
Expand Down Expand Up @@ -201,7 +204,7 @@ function App() {

return (
<div className="App">
<Context.Provider value={{ user: user, getMural }}>
<Context.Provider value={{ user: user, token : JWTtoken, getMural }}>
<SigninForm
signInClick={handleSignin}
cancelClick={() => setSigningIn(false)}
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/CollectionForm/CollectionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function CollectionForm(props: ICollectionFormProps) {
/**
* Enable editable form fields for admin users
*/
const userContext = useContext(Context)
const userContext: any = useContext(Context)
useEffect(() => setIsAdmin(!!(userContext as any).user), [userContext]);

const handleAddMural = (addedMural: any) => {
Expand All @@ -108,6 +108,11 @@ function CollectionForm(props: ICollectionFormProps) {
description: description
},
murals: muralsInCollection.map((mural: any) => mural.id)
},
{
headers: {
authorization: userContext.token.i
}
})
.then(() => {
setPopup(true);
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/TourForm/TourForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ function TourForm(props: ITourFormProps) {
const [isAdmin, setIsAdmin] = useState<boolean>(false);

const styles = useStyles();

/**
* Populate the form when an existing tour is passed as a prop
*/
Expand All @@ -94,7 +93,7 @@ function TourForm(props: ITourFormProps) {
/**
* Enable editable form fields for admin users
*/
const userContext = useContext(Context)
const userContext: any = useContext(Context)
useEffect(() => setIsAdmin(!!(userContext as any).user), [userContext]);

const handleAddMural = (addedMural: any) => {
Expand All @@ -108,14 +107,18 @@ function TourForm(props: ITourFormProps) {

const handleSave = () => {
if (!title.length || !description.length) return;

axios
.post(GET_ALL_TOUR, {
tour: {
name: title,
description: description,
},
murals: muralsInTour.map((mural: any) => mural.id),
}, {
headers: {
authorization: userContext.token.i
}
})
.then(() => {
setPopup(true);
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/muralForm/MuralForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function MuralForm({ mural, handleCancel }: IMuralFormProps) {
/**
* Enable editable form fields for admin users
*/
const userContext = useContext(Context);
const userContext: any = useContext(Context)
useEffect(() => setIsAdmin(!!(userContext as any).user), [userContext]);

/**
Expand Down Expand Up @@ -190,7 +190,10 @@ function MuralForm({ mural, handleCancel }: IMuralFormProps) {
method: existingMural ? 'put' : 'post',
url: existingMural ?
`${CREATE_MURAL_API}/${payload.id}` : CREATE_MURAL_API,
data: payload
data: payload,
headers: {
authorization: userContext.token.i
}
})
.then(
(response) => {
Expand Down

1 comment on commit c601365

@vercel
Copy link

@vercel vercel bot commented on c601365 Apr 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.