feat/security #1

Merged
julius merged 7 commits from feat/security into main 2025-02-18 07:06:29 +00:00
5 changed files with 121 additions and 19 deletions
Showing only changes of commit 15c9a64de2 - Show all commits

View File

@ -1,6 +1,6 @@
from datetime import timedelta, timezone, datetime
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi import Depends, HTTPException, Response, status
from pydantic import BaseModel
import jwt
from jwt.exceptions import InvalidTokenError
@ -102,7 +102,7 @@ async def get_current_active_user(
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
form_data: Annotated[OAuth2PasswordRequestForm, Depends()], response: Response
) -> Token:
user = authenticate_user(form_data.username, form_data.password)
if not user:
@ -115,6 +115,9 @@ async def login_for_access_token(
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
response.set_cookie(
"Authorization", value=f"Bearer {access_token}", httponly=True, samesite="none"
)
return Token(access_token=access_token, token_type="bearer")

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { baseUrl } from "./api";
import { apiAuth, baseUrl, token } from "./api";
import useAuthContext from "./AuthContext";
//const debounce = <T extends (...args: any[]) => void>(
// func: T,
@ -61,18 +62,14 @@ export default function Analysis() {
// Function to generate and fetch the graph image
async function loadImage() {
setLoading(true);
await fetch(`${baseUrl}api/analysis/image`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params)
})
.then((resp) => resp.json())
await apiAuth("analysis/image", params, "POST")
.then((data) => {
setImage(data.image);
setLoading(false);
});
}).catch((e) => {
const { checkAuth } = useAuthContext();
checkAuth();
})
}
useEffect(() => {
@ -92,6 +89,9 @@ export default function Analysis() {
}
}
const { user } = useAuthContext()!
console.log(`logged in as ${user.username}`);
return (
<div className="stack column dropdown">
<button onClick={() => setShowControlPanel(!showControlPanel)}>

View File

@ -12,16 +12,17 @@ body {
height: 100%;
}
footer {
font-size: x-small;
}
#root {
max-width: 1280px;
margin: 0 auto;
padding: 8px;
}
footer {
font-size: x-small;
}
.grey {
color: #444;
}
@ -37,6 +38,11 @@ footer {
z-index: -1;
}
input {
padding: 0.2em 16px;
margin-bottom: 0.5em;
}
h1,
h2,
h3 {
@ -120,7 +126,8 @@ h3 {
margin: auto;
}
button {
button,
.button {
font-weight: bold;
font-size: large;
color: aliceblue;

View File

@ -1,9 +1,10 @@
import Analysis from "./Analysis";
import "./App.css";
import { AuthProvider } from "./AuthContext";
import Footer from "./Footer";
import Header from "./Header";
import Rankings from "./Rankings";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { BrowserRouter, Routes, Route } from "react-router";
function App() {
//const [data, setData] = useState({ nodes: [], links: [] } as SociogramData);
@ -17,7 +18,11 @@ function App() {
<Header />
<Routes>
<Route index element={<Rankings />} />
<Route path="/analysis" element={<Analysis />} />
<Route path="/analysis" element={
<AuthProvider>
<Analysis />
</AuthProvider>
} />
</Routes>
<Footer />
</BrowserRouter>

View File

@ -1,4 +1,10 @@
import { useContext } from "react";
import useAuthContext from "./AuthContext";
import { createCookie } from "react-router";
export const baseUrl = import.meta.env.VITE_BASE_URL as string;
export const token = () => localStorage.getItem("access_token") as string;
export default async function api(path: string, data: any): Promise<any> {
const request = new Request(`${baseUrl}${path}/`, {
method: "POST",
@ -15,3 +21,84 @@ export default async function api(path: string, data: any): Promise<any> {
}
return response;
}
export async function apiAuth(path: string, data: any, method: string = "GET"): Promise<any> {
const req = new Request(`${baseUrl}api/${path}`, {
method: method, headers: {
"Authorization": `Bearer ${token()} `,
'Content-Type': 'application/json'
},
body: JSON.stringify(data),
});
let resp: Response;
try {
resp = await fetch(req);
} catch (e) {
throw new Error(`request failed: ${e}`);
}
if (!resp.ok) {
if (resp.status === 401) {
logout()
throw new Error('Unauthorized');
}
}
return resp.json()
}
export type User = {
username: string;
fullName: string;
}
export async function currentUser(): Promise<User> {
if (!token()) throw new Error("you have no access token")
const req = new Request(`${baseUrl}api/users/me/`, {
method: "GET", headers: {
"Authorization": `Bearer ${token()} `,
'Content-Type': 'application/json'
}
});
let resp: Response;
try {
resp = await fetch(req);
} catch (e) {
throw new Error(`request failed: ${e}`);
}
if (!resp.ok) {
if (resp.status === 401) {
logout()
throw new Error('Unauthorized');
}
}
return resp.json() as Promise<User>;
}
export type LoginRequest = {
username: string;
password: string;
};
export type Token = {
access_token: string;
token_type: string;
};
export const login = (req: LoginRequest) => {
fetch(`${baseUrl}api/token`, {
method: "POST", headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}, body: new URLSearchParams(req).toString()
}).then(resp => resp.json() as unknown as Token).then(token => token ? localStorage.setItem("access_token", token.access_token) : console.log("token not acquired")).catch((e) => console.log("catch error " + e + " in login"));
}
export const cookielogin = (req: LoginRequest) => {
fetch(`${baseUrl}api/token`, {
method: "POST", headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}, body: new URLSearchParams(req).toString()
}).then(resp => { createCookie(resp.headers.getSetCookie()) }).catch((e) => console.log("catch error " + e + " in login"));
}
export const logout = () => localStorage.removeItem("access_token");