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

Added API Token Verification to lock restricted calls #182

Merged
merged 4 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
13 changes: 9 additions & 4 deletions backend/src/api/protein.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
from ..db import Database, bytea_to_str, str_to_bytea

from ..api_types import ProteinEntry, UploadBody, UploadError, EditBody, CamelModel
from ..auth import requiresAuthentication
from io import BytesIO
from fastapi import APIRouter
from fastapi.responses import FileResponse, StreamingResponse
from fastapi.requests import Request

router = APIRouter()

Expand Down Expand Up @@ -179,7 +181,9 @@ def get_protein_entry(protein_name: str):

# TODO: add permissions so only the creator can delete not just anyone
@router.delete("/protein/entry/{protein_name:str}", response_model=None)
def delete_protein_entry(protein_name: str):
def delete_protein_entry(protein_name: str, req: Request):
print("DELETING")
requiresAuthentication(req)
# Todo, have a meaningful error if the delete fails
with Database() as db:
# remove protein
Expand Down Expand Up @@ -207,7 +211,8 @@ def upload_protein_png(body: UploadPNGBody):

# None return means success
@router.post("/protein/upload", response_model=UploadError | None)
def upload_protein_entry(body: UploadBody):
def upload_protein_entry(body: UploadBody, req: Request):
requiresAuthentication(req)
# check that the name is not already taken in the DB
if protein_name_found(body.name):
return UploadError.NAME_NOT_UNIQUE
Expand Down Expand Up @@ -262,10 +267,10 @@ def upload_protein_entry(body: UploadBody):

# TODO: add more edits, now only does name and content edits
@router.put("/protein/edit", response_model=UploadError | None)
def edit_protein_entry(body: EditBody):
def edit_protein_entry(body: EditBody, req: Request):
# check that the name is not already taken in the DB
# TODO: check if permission so we don't have people overriding other people's names

requiresAuthentication(req)
try:
if body.new_name != body.old_name:
os.rename(pdb_file_name(body.old_name), pdb_file_name(body.new_name))
Expand Down
23 changes: 20 additions & 3 deletions backend/src/auth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import jwt
from datetime import datetime, timezone, timedelta
from fastapi.requests import Request
from fastapi import HTTPException

# TODO: This method of secret key generation is, obviously, extremely unsafe.
# This needs to be changed.
Expand All @@ -15,13 +17,28 @@ def generateAuthToken(userId, admin):
return jwt.encode(payload, secret_key, algorithm="HS256")


# TODO: Find out how FastAPI handles middleware functions, and turn this into one.
def authenticateToken(token):
# Return the decoded token if it's valid.
try:
decoded = jwt.decode(token, secret_key, algorithms="HS256")
# Valid token is always is in the form "Bearer [token]", so we need to slice off the "Bearer" portion.
sliced_token = token[7:]
print(sliced_token)
decoded = jwt.decode(sliced_token, secret_key, algorithms="HS256")
print("Valid token")
print(decoded)
return decoded

# If the token is invalid, return None.
except Exception:
except Exception as err:
print("Invalid token:", err)
return None


# Use this function with a request if you want.
def requiresAuthentication(req: Request):
userInfo = authenticateToken(req.headers["authorization"])
if not userInfo or not userInfo.get("admin"):
print("Unauthorized User")
raise HTTPException(status_code=403, detail="Unauthorized")
else:
print("User authorized.")
13 changes: 13 additions & 0 deletions frontend/src/lib/backend.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
export * from "./openapi";
export { DefaultService as Backend } from "./openapi";
import { OpenAPI } from "./openapi";
import Cookies from "js-cookie";

export const BACKEND_URL = "http://localhost:8000";
OpenAPI.BASE = BACKEND_URL;

// Sets the token header to the stored authentication token from the cookie
export function setToken() {
console.log("Setting Token")
OpenAPI.TOKEN = Cookies.get("auth")
}

// Sets the token header to nothing.
export function clearToken() {
console.log("Clearing token.")
OpenAPI.TOKEN = undefined
}
4 changes: 3 additions & 1 deletion frontend/src/routes/Edit.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Backend, UploadError, type ProteinEntry } from "../lib/backend";
import { Backend, UploadError, setToken, type ProteinEntry } from "../lib/backend";
import { Button, Input, Label, Helper, Select } from "flowbite-svelte";
import { navigate } from "svelte-routing";
import { onMount } from "svelte";
Expand Down Expand Up @@ -140,6 +140,7 @@
on:click={async () => {
if (entry) {
try {
setToken()
const err = await Backend.editProteinEntry({
newName: name,
oldName: entry.name,
Expand Down Expand Up @@ -179,6 +180,7 @@
<Button
color="red"
on:click={async () => {
setToken()
await Backend.deleteProteinEntry(urlId);
navigate("/");
}}>Delete Protein Entry</Button
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/routes/Login.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Backend, type LoginResponse } from "../lib/backend";
import { Backend, clearToken, type LoginResponse } from "../lib/backend";
import { Button, Label, Input } from "flowbite-svelte";
import Cookies from "js-cookie";
import { onMount } from "svelte";
Expand All @@ -11,6 +11,7 @@
* We want to do this to avoid bugs, and to let the user log out.
*/
onMount(async () => {
clearToken()
Cookies.remove("auth")
$user.loggedIn = false
$user.username = ""
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/routes/Upload.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { Backend, UploadError } from "../lib/backend";
import { Backend, UploadError, setToken } from "../lib/backend";
import {
Fileupload,
Button,
Expand Down Expand Up @@ -108,6 +108,7 @@

const pdbFileStr = await fileToString(file);
try {
setToken()
const err = await Backend.uploadProteinEntry({
name,
description,
Expand Down
Loading