add login / logout logic for user
This commit is contained in:
@@ -10,3 +10,5 @@ KEYCLOAK_REALM=
|
|||||||
KEYCLOAK_CLIENT_ID=
|
KEYCLOAK_CLIENT_ID=
|
||||||
KEYCLOAK_CLIENT_SECRET=
|
KEYCLOAK_CLIENT_SECRET=
|
||||||
KEYCLOAK_REDIRECT_URI=
|
KEYCLOAK_REDIRECT_URI=
|
||||||
|
DEBUG=True
|
||||||
|
MAX_AGE=3600
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
from fastapi import APIRouter, Security, HTTPException, Depends, Request
|
from typing import Annotated
|
||||||
|
from fastapi import APIRouter, Security, HTTPException, Depends, Request, Cookie
|
||||||
from fastapi.responses import RedirectResponse, Response
|
from fastapi.responses import RedirectResponse, Response
|
||||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
import jwt
|
import jwt
|
||||||
from jwt import PyJWKClient
|
from jwt import PyJWKClient
|
||||||
|
|
||||||
from src.settings import AUTH_URL, TOKEN_URL, JWKS_URL, ISSUER, settings
|
from src.settings import AUTH_URL, TOKEN_URL, JWKS_URL, ISSUER, LOGOUT_URL, settings
|
||||||
import src.users.service as service
|
import src.users.service as service
|
||||||
from src.database import get_session
|
from src.database import get_session
|
||||||
from src.models import UserCreate, User, UserPublic
|
from src.models import UserCreate, User, UserPublic
|
||||||
@@ -20,11 +21,45 @@ router = APIRouter(prefix='/auth')
|
|||||||
jwk_client = PyJWKClient(JWKS_URL)
|
jwk_client = PyJWKClient(JWKS_URL)
|
||||||
security = HTTPBearer()
|
security = HTTPBearer()
|
||||||
|
|
||||||
@router.post('/logout')
|
@router.get('/logout')
|
||||||
def logout(response: Response):
|
def logout(
|
||||||
response.delete_cookie('access_token')
|
id_token: Annotated[str | None, Cookie()] = None,
|
||||||
response.delete_cookie('refresh_token')
|
refresh_token: Annotated[str | None, Cookie()] = None,
|
||||||
return {'detail': messages.userloggedout}
|
):
|
||||||
|
if refresh_token:
|
||||||
|
print("invalidate tokens")
|
||||||
|
requests.post(LOGOUT_URL, data={
|
||||||
|
"client_id": settings.keycloak_client_id,
|
||||||
|
"client_secret": settings.keycloak_client_secret,
|
||||||
|
"refresh_token": refresh_token
|
||||||
|
})
|
||||||
|
|
||||||
|
if id_token:
|
||||||
|
print("redirect keycloak")
|
||||||
|
response = RedirectResponse(f'{LOGOUT_URL}?post_logout_redirect_uri={settings.origins}&id_token_hint={id_token}')
|
||||||
|
else:
|
||||||
|
response = RedirectResponse(settings.origins)
|
||||||
|
|
||||||
|
print("clear cookies")
|
||||||
|
response.delete_cookie(
|
||||||
|
key='access_token',
|
||||||
|
path='/',
|
||||||
|
secure=not settings.debug,
|
||||||
|
samesite='lax',
|
||||||
|
)
|
||||||
|
response.delete_cookie(
|
||||||
|
key='refresh_token',
|
||||||
|
path='/',
|
||||||
|
secure=not settings.debug,
|
||||||
|
samesite='lax',
|
||||||
|
)
|
||||||
|
response.delete_cookie(
|
||||||
|
key='id_token',
|
||||||
|
path='/',
|
||||||
|
secure=not settings.debug,
|
||||||
|
samesite='lax',
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.get('/login')
|
@router.get('/login')
|
||||||
@@ -64,7 +99,27 @@ def callback(code: str, session: Session = Depends(get_session)):
|
|||||||
id_token = token_data['id_token']
|
id_token = token_data['id_token']
|
||||||
decoded_token = jwt.decode(id_token, options={'verify_signature': False})
|
decoded_token = jwt.decode(id_token, options={'verify_signature': False})
|
||||||
decoded_access_token = jwt.decode(token_data['access_token'], options={'verify_signature': False})
|
decoded_access_token = jwt.decode(token_data['access_token'], options={'verify_signature': False})
|
||||||
roles = decoded_access_token['resource_access'][settings.keycloak_client_id]
|
resource_access = decoded_access_token.get('resource_access')
|
||||||
|
if not resource_access:
|
||||||
|
data = {
|
||||||
|
'client_id': settings.keycloak_client_id,
|
||||||
|
'client_secret': settings.keycloak_client_secret,
|
||||||
|
'refresh_token': token_data['refresh_token'],
|
||||||
|
}
|
||||||
|
res = requests.post(LOGOUT_URL, data=data)
|
||||||
|
resp = RedirectResponse(settings.origins)
|
||||||
|
return resp
|
||||||
|
resource_access.get(settings.keycloak_client_id)
|
||||||
|
if not roles:
|
||||||
|
data = {
|
||||||
|
'client_id': settings.keycloak_client_id,
|
||||||
|
'client_secret': settings.keycloak_client_secret,
|
||||||
|
'refresh_token': token_data['refresh_token'],
|
||||||
|
}
|
||||||
|
res = requests.post(LOGOUT_URL, data=data)
|
||||||
|
resp = RedirectResponse(settings.origins)
|
||||||
|
return resp
|
||||||
|
|
||||||
user_create = UserCreate(
|
user_create = UserCreate(
|
||||||
email=decoded_token.get('email'),
|
email=decoded_token.get('email'),
|
||||||
name=decoded_token.get('preferred_username'),
|
name=decoded_token.get('preferred_username'),
|
||||||
@@ -76,18 +131,27 @@ def callback(code: str, session: Session = Depends(get_session)):
|
|||||||
key='access_token',
|
key='access_token',
|
||||||
value=token_data['access_token'],
|
value=token_data['access_token'],
|
||||||
httponly=True,
|
httponly=True,
|
||||||
secure=True if settings.debug == False else True,
|
secure=not settings.debug,
|
||||||
samesite='strict',
|
samesite='lax',
|
||||||
max_age=settings.max_age
|
max_age=settings.max_age
|
||||||
)
|
)
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
key='refresh_token',
|
key='refresh_token',
|
||||||
value=token_data['refresh_token'] or '',
|
value=token_data['refresh_token'] or '',
|
||||||
httponly=True,
|
httponly=True,
|
||||||
secure=True if settings.debug == False else True,
|
secure=not settings.debug,
|
||||||
samesite='strict',
|
samesite='lax',
|
||||||
max_age=30 * 24 * settings.max_age
|
max_age=30 * 24 * settings.max_age
|
||||||
)
|
)
|
||||||
|
response.set_cookie(
|
||||||
|
key='id_token',
|
||||||
|
value=token_data['id_token'],
|
||||||
|
httponly=True,
|
||||||
|
secure=not settings.debug,
|
||||||
|
samesite='lax',
|
||||||
|
max_age=settings.max_age
|
||||||
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def verify_token(token: str):
|
def verify_token(token: str):
|
||||||
@@ -109,12 +173,12 @@ def verify_token(token: str):
|
|||||||
|
|
||||||
|
|
||||||
def get_current_user(request: Request, session: Session = Depends(get_session)):
|
def get_current_user(request: Request, session: Session = Depends(get_session)):
|
||||||
access_token = request.cookies.get("access_token")
|
access_token = request.cookies.get('access_token')
|
||||||
if not access_token:
|
if not access_token:
|
||||||
raise HTTPException(status_code=401, detail=messages.notauthenticated)
|
raise HTTPException(status_code=401, detail=messages.notauthenticated)
|
||||||
payload = verify_token(access_token)
|
payload = verify_token(access_token)
|
||||||
if not payload:
|
if not payload:
|
||||||
raise HTTPException(status_code=401, detail="aze")
|
raise HTTPException(status_code=401, detail='aze')
|
||||||
email = payload.get('email')
|
email = payload.get('email')
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
@@ -125,16 +189,55 @@ def get_current_user(request: Request, session: Session = Depends(get_session)):
|
|||||||
raise HTTPException(status_code=401, detail=messages.usernotfound)
|
raise HTTPException(status_code=401, detail=messages.usernotfound)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
@router.post('/refresh')
|
||||||
|
def refresh_token(refresh_token: Annotated[str | None, Cookie()] = None):
|
||||||
|
refresh = refresh_token
|
||||||
|
data = {
|
||||||
|
'grant_type': 'refresh_token',
|
||||||
|
'client_id': settings.keycloak_client_id,
|
||||||
|
'client_secret': settings.keycloak_client_secret,
|
||||||
|
'refresh_token': refresh,
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
result = requests.post(TOKEN_URL, data=data, headers=headers)
|
||||||
|
if result.status_code != 200:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=messages.failtogettoken
|
||||||
|
)
|
||||||
|
|
||||||
|
token_data = result.json()
|
||||||
|
response = Response()
|
||||||
|
response.set_cookie(
|
||||||
|
key='access_token',
|
||||||
|
value=token_data['access_token'],
|
||||||
|
httponly=True,
|
||||||
|
secure=True if settings.debug == False else True,
|
||||||
|
samesite='lax',
|
||||||
|
max_age=settings.max_age
|
||||||
|
)
|
||||||
|
response.set_cookie(
|
||||||
|
key='refresh_token',
|
||||||
|
value=token_data['refresh_token'] or '',
|
||||||
|
httponly=True,
|
||||||
|
secure=True if settings.debug == False else True,
|
||||||
|
samesite='lax',
|
||||||
|
max_age=4
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
@router.get('/user/me')
|
@router.get('/user/me')
|
||||||
def me(user: UserPublic = Depends(get_current_user)):
|
def me(user: UserPublic = Depends(get_current_user)):
|
||||||
if not user:
|
if not user:
|
||||||
return {"logged": False}
|
return {'logged': False}
|
||||||
return {
|
return {
|
||||||
"logged": True,
|
'logged': True,
|
||||||
"user": {
|
'user': {
|
||||||
"name": user.name,
|
'name': user.name,
|
||||||
"email": user.email,
|
'email': user.email,
|
||||||
"id": user.id,
|
'id': user.id,
|
||||||
"roles": [role.name for role in user.roles]
|
'roles': [role.name for role in user.roles]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,6 @@ def create_occasional_dict(contract_products: list[models.ContractProduct]):
|
|||||||
async def create_contract(
|
async def create_contract(
|
||||||
contract: models.ContractCreate,
|
contract: models.ContractCreate,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
user: models.User = Depends(get_current_user)
|
|
||||||
):
|
):
|
||||||
new_contract = service.create_one(session, contract)
|
new_contract = service.create_one(session, contract)
|
||||||
occasional_contract_products = list(filter(lambda contract_product: contract_product.product.type == models.ProductType.OCCASIONAL, new_contract.products))
|
occasional_contract_products = list(filter(lambda contract_product: contract_product.product.type == models.ProductType.OCCASIONAL, new_contract.products))
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ app = FastAPI()
|
|||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=[settings.origins],
|
allow_origins=[
|
||||||
|
settings.origins
|
||||||
|
],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ notauthenticated = "Not authenticated"
|
|||||||
usernotfound = "User not found"
|
usernotfound = "User not found"
|
||||||
userloggedout = "User logged out"
|
userloggedout = "User logged out"
|
||||||
failtogettoken = "Failed to get token"
|
failtogettoken = "Failed to get token"
|
||||||
|
unauthorized = "Unauthorized"
|
||||||
@@ -25,3 +25,4 @@ AUTH_URL = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}/protoco
|
|||||||
TOKEN_URL = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}/protocol/openid-connect/token"
|
TOKEN_URL = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}/protocol/openid-connect/token"
|
||||||
ISSUER = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}"
|
ISSUER = f"{settings.keycloak_server}/realms/{settings.keycloak_realm}"
|
||||||
JWKS_URL = f"{ISSUER}/protocol/openid-connect/certs"
|
JWKS_URL = f"{ISSUER}/protocol/openid-connect/certs"
|
||||||
|
LOGOUT_URL = f'{settings.keycloak_server}/realms/{settings.keycloak_realm}/protocol/openid-connect/logout'
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { NavLink } from "react-router";
|
import { NavLink } from "react-router";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { Button, Group, Loader } from "@mantine/core";
|
import { Group, Loader } from "@mantine/core";
|
||||||
import { Config } from "@/config/config";
|
import { Config } from "@/config/config";
|
||||||
import { useCurrentUser, useLogoutUser } from "@/services/api";
|
import { useAuth } from "@/services/auth/AuthProvider";
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const { data: user, isLoading } = useCurrentUser();
|
const { loggedUser: user, isLoading } = useAuth();
|
||||||
const logout = useLogoutUser();
|
|
||||||
if (!user && isLoading) {
|
if (!user && isLoading) {
|
||||||
return (
|
return (
|
||||||
<Group align="center" justify="center" h="80vh" w="100%">
|
<Group align="center" justify="center" h="80vh" w="100%">
|
||||||
@@ -37,15 +37,14 @@ export function Navbar() {
|
|||||||
{t("login with keycloak", { capfirst: true })}
|
{t("login with keycloak", { capfirst: true })}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<a
|
||||||
|
href={`${Config.backend_uri}/auth/logout`}
|
||||||
className={"navLink"}
|
className={"navLink"}
|
||||||
aria-label={t("logout", { capfirst: true })}
|
aria-label={t("logout", { capfirst: true })}
|
||||||
onClick={() => {
|
|
||||||
logout.mutate();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{t("logout", { capfirst: true })}
|
{t("logout", { capfirst: true })}
|
||||||
</Button>
|
</a>
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function ShipmentModal({
|
|||||||
}, [allForms]);
|
}, [allForms]);
|
||||||
|
|
||||||
const productsSelect = useMemo(() => {
|
const productsSelect = useMemo(() => {
|
||||||
if (!allProducts) return;
|
if (!allProducts || !allProductors) return;
|
||||||
return allProductors?.map((productor) => {
|
return allProductors?.map((productor) => {
|
||||||
return {
|
return {
|
||||||
group: productor.name,
|
group: productor.name,
|
||||||
|
|||||||
@@ -8,15 +8,18 @@ import { Notifications } from "@mantine/notifications";
|
|||||||
import "@mantine/core/styles.css";
|
import "@mantine/core/styles.css";
|
||||||
import "@mantine/dates/styles.css";
|
import "@mantine/dates/styles.css";
|
||||||
import "@mantine/notifications/styles.css";
|
import "@mantine/notifications/styles.css";
|
||||||
|
import { AuthProvider } from "./services/auth/AuthProvider";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
export const queryClient = new QueryClient();
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<MantineProvider>
|
<MantineProvider>
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<RouterProvider router={router} />
|
<AuthProvider>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</AuthProvider>
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Tabs } from "@mantine/core";
|
import { Tabs } from "@mantine/core";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router";
|
import { Link, Outlet, useLocation, useNavigate } from "react-router";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -15,13 +15,13 @@ export default function Dashboard() {
|
|||||||
onChange={(value) => navigate(`/dashboard/${value}`)}
|
onChange={(value) => navigate(`/dashboard/${value}`)}
|
||||||
>
|
>
|
||||||
<Tabs.List mb="md">
|
<Tabs.List mb="md">
|
||||||
<Tabs.Tab value="help">{t("help", { capfirst: true })}</Tabs.Tab>
|
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/help" {...props}></Link>)} value="help">{t("help", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="productors">{t("productors", { capfirst: true })}</Tabs.Tab>
|
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/productors" {...props}></Link>)} value="productors">{t("productors", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="products">{t("products", { capfirst: true })}</Tabs.Tab>
|
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/products" {...props}></Link>)} value="products">{t("products", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="forms">{t("forms", { capfirst: true })}</Tabs.Tab>
|
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/forms" {...props}></Link>)} value="forms">{t("forms", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="shipments">{t("shipments", { capfirst: true })}</Tabs.Tab>
|
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/shipments" {...props}></Link>)} value="shipments">{t("shipments", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="contracts">{t("contracts", { capfirst: true })}</Tabs.Tab>
|
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/contracts" {...props}></Link>)} value="contracts">{t("contracts", { capfirst: true })}</Tabs.Tab>
|
||||||
<Tabs.Tab value="users">{t("users", { capfirst: true })}</Tabs.Tab>
|
<Tabs.Tab renderRoot={(props) => (<Link to="/dashboard/users" {...props}></Link>)} value="users">{t("users", { capfirst: true })}</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { NotFound } from "./pages/NotFound";
|
|||||||
import Contracts from "./pages/Contracts";
|
import Contracts from "./pages/Contracts";
|
||||||
import { Help } from "./pages/Help";
|
import { Help } from "./pages/Help";
|
||||||
import { Login } from "./pages/Login";
|
import { Login } from "./pages/Login";
|
||||||
import { Auth } from "./components/Auth";
|
import { ProtectedRoute } from "./services/auth/ProtectedRoute";
|
||||||
|
|
||||||
export const router = createBrowserRouter([
|
export const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -24,7 +24,7 @@ export const router = createBrowserRouter([
|
|||||||
{ index: true, Component: Home },
|
{ index: true, Component: Home },
|
||||||
{ path: "/forms", Component: Forms },
|
{ path: "/forms", Component: Forms },
|
||||||
{
|
{
|
||||||
element: <Auth />,
|
element: <ProtectedRoute />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/dashboard",
|
path: "/dashboard",
|
||||||
|
|||||||
@@ -25,12 +25,40 @@ import type { Contract, ContractCreate } from "./resources/contracts";
|
|||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { t } from "@/config/i18n";
|
import { t } from "@/config/i18n";
|
||||||
|
|
||||||
|
export async function refreshToken() {
|
||||||
|
return await fetch(`${Config.backend_uri}/auth/refresh`, {method: "POST", credentials: "include"});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchWithAuth(input: RequestInfo, options?: RequestInit) {
|
||||||
|
const res = await fetch(input, {
|
||||||
|
credentials: "include",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 401) {
|
||||||
|
const refresh = await refreshToken();
|
||||||
|
if (refresh.status == 400 || refresh.status == 401) {
|
||||||
|
window.location.href = `${Config.backend_uri}/auth/logout`;
|
||||||
|
const error = new Error("Unauthorized");
|
||||||
|
error.cause = 401
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
const newRes = await fetch(input, {
|
||||||
|
credentials: "include",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
return newRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
export function useGetShipments(filters?: URLSearchParams): UseQueryResult<Shipment[], Error> {
|
export function useGetShipments(filters?: URLSearchParams): UseQueryResult<Shipment[], Error> {
|
||||||
const queryString = filters?.toString();
|
const queryString = filters?.toString();
|
||||||
return useQuery<Shipment[]>({
|
return useQuery<Shipment[]>({
|
||||||
queryKey: ["shipments", queryString],
|
queryKey: ["shipments", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/shipments${filters ? `?${queryString}` : ""}`, {
|
fetchWithAuth(`${Config.backend_uri}/shipments${filters ? `?${queryString}` : ""}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
});
|
});
|
||||||
@@ -43,7 +71,7 @@ export function useGetShipment(
|
|||||||
return useQuery<Shipment>({
|
return useQuery<Shipment>({
|
||||||
queryKey: ["shipment"],
|
queryKey: ["shipment"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/shipments/${id}`, {
|
fetchWithAuth(`${Config.backend_uri}/shipments/${id}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
@@ -56,7 +84,7 @@ export function useCreateShipment() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newShipment: ShipmentCreate) => {
|
mutationFn: (newShipment: ShipmentCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/shipments`, {
|
return fetchWithAuth(`${Config.backend_uri}/shipments`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -89,7 +117,7 @@ export function useEditShipment() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({ shipment, id }: ShipmentEditPayload) => {
|
mutationFn: ({ shipment, id }: ShipmentEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/shipments/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/shipments/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -121,7 +149,7 @@ export function useDeleteShipment() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/shipments/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/shipments/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -153,9 +181,11 @@ export function useGetProductors(filters?: URLSearchParams): UseQueryResult<Prod
|
|||||||
return useQuery<Productor[]>({
|
return useQuery<Productor[]>({
|
||||||
queryKey: ["productors", queryString],
|
queryKey: ["productors", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`, {
|
fetchWithAuth(`${Config.backend_uri}/productors${filters ? `?${queryString}` : ""}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
})
|
||||||
|
.then((res) => res.json()
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +196,7 @@ export function useGetProductor(
|
|||||||
return useQuery<Productor>({
|
return useQuery<Productor>({
|
||||||
queryKey: ["productor"],
|
queryKey: ["productor"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/productors/${id}`, {
|
fetchWithAuth(`${Config.backend_uri}/productors/${id}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
@@ -179,7 +209,7 @@ export function useCreateProductor() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newProductor: ProductorCreate) => {
|
mutationFn: (newProductor: ProductorCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/productors`, {
|
return fetchWithAuth(`${Config.backend_uri}/productors`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -212,7 +242,7 @@ export function useEditProductor() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({ productor, id }: ProductorEditPayload) => {
|
mutationFn: ({ productor, id }: ProductorEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/productors/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/productors/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -244,7 +274,7 @@ export function useDeleteProductor() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/productors/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/productors/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -278,7 +308,7 @@ export function useGetForm(
|
|||||||
return useQuery<Form>({
|
return useQuery<Form>({
|
||||||
queryKey: ["form"],
|
queryKey: ["form"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/forms/${id}`, {
|
fetchWithAuth(`${Config.backend_uri}/forms/${id}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
@@ -291,9 +321,7 @@ export function useGetForms(filters?: URLSearchParams): UseQueryResult<Form[], E
|
|||||||
return useQuery<Form[]>({
|
return useQuery<Form[]>({
|
||||||
queryKey: ["forms", queryString],
|
queryKey: ["forms", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`, {
|
fetch(`${Config.backend_uri}/forms${filters ? `?${queryString}` : ""}`).then((res) => res.json()),
|
||||||
credentials: "include",
|
|
||||||
}).then((res) => res.json()),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +330,7 @@ export function useCreateForm() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newForm: FormCreate) => {
|
mutationFn: (newForm: FormCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/forms`, {
|
return fetchWithAuth(`${Config.backend_uri}/forms`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -321,7 +349,7 @@ export function useDeleteForm() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/forms/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/forms/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -353,7 +381,7 @@ export function useEditForm() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({ id, form }: FormEditPayload) => {
|
mutationFn: ({ id, form }: FormEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/forms/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/forms/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -387,7 +415,7 @@ export function useGetProduct(
|
|||||||
return useQuery<Product>({
|
return useQuery<Product>({
|
||||||
queryKey: ["product"],
|
queryKey: ["product"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/products/${id}`, {
|
fetchWithAuth(`${Config.backend_uri}/products/${id}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
@@ -400,7 +428,7 @@ export function useGetProducts(filters?: URLSearchParams): UseQueryResult<Produc
|
|||||||
return useQuery<Product[]>({
|
return useQuery<Product[]>({
|
||||||
queryKey: ["products", queryString],
|
queryKey: ["products", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/products${filters ? `?${queryString}` : ""}`, {
|
fetchWithAuth(`${Config.backend_uri}/products${filters ? `?${queryString}` : ""}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
});
|
});
|
||||||
@@ -411,7 +439,7 @@ export function useCreateProduct() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newProduct: ProductCreate) => {
|
mutationFn: (newProduct: ProductCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/products`, {
|
return fetchWithAuth(`${Config.backend_uri}/products`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -443,7 +471,7 @@ export function useDeleteProduct() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/products/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/products/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -475,7 +503,7 @@ export function useEditProduct() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({ id, product }: ProductEditPayload) => {
|
mutationFn: ({ id, product }: ProductEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/products/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/products/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -510,7 +538,7 @@ export function useGetUser(
|
|||||||
return useQuery<User>({
|
return useQuery<User>({
|
||||||
queryKey: ["user"],
|
queryKey: ["user"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/users/${id}`, {
|
fetchWithAuth(`${Config.backend_uri}/users/${id}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
@@ -523,7 +551,7 @@ export function useGetUsers(filters?: URLSearchParams): UseQueryResult<User[], E
|
|||||||
return useQuery<User[]>({
|
return useQuery<User[]>({
|
||||||
queryKey: ["users", queryString],
|
queryKey: ["users", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/users${filters ? `?${queryString}` : ""}`, {
|
fetchWithAuth(`${Config.backend_uri}/users${filters ? `?${queryString}` : ""}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
});
|
});
|
||||||
@@ -534,7 +562,7 @@ export function useCreateUser() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (newUser: UserCreate) => {
|
mutationFn: (newUser: UserCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/users`, {
|
return fetchWithAuth(`${Config.backend_uri}/users`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -566,7 +594,7 @@ export function useDeleteUser() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/users/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/users/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -598,7 +626,7 @@ export function useEditUser() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({ id, user }: UserEditPayload) => {
|
mutationFn: ({ id, user }: UserEditPayload) => {
|
||||||
return fetch(`${Config.backend_uri}/users/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/users/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -630,7 +658,7 @@ export function useGetContracts(filters?: URLSearchParams): UseQueryResult<Contr
|
|||||||
return useQuery<Contract[]>({
|
return useQuery<Contract[]>({
|
||||||
queryKey: ["contracts", queryString],
|
queryKey: ["contracts", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/contracts${filters ? `?${queryString}` : ""}`, {
|
fetchWithAuth(`${Config.backend_uri}/contracts${filters ? `?${queryString}` : ""}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
});
|
});
|
||||||
@@ -643,7 +671,7 @@ export function useGetContract(
|
|||||||
return useQuery<Contract>({
|
return useQuery<Contract>({
|
||||||
queryKey: ["contract"],
|
queryKey: ["contract"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/contracts/${id}`, {
|
fetchWithAuth(`${Config.backend_uri}/contracts/${id}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
@@ -654,7 +682,7 @@ export function useGetContract(
|
|||||||
export function useGetContractFile() {
|
export function useGetContractFile() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (id: number) => {
|
mutationFn: async (id: number) => {
|
||||||
const res = await fetch(`${Config.backend_uri}/contracts/${id}/file`, {
|
const res = await fetchWithAuth(`${Config.backend_uri}/contracts/${id}/file`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res);
|
}).then((res) => res);
|
||||||
|
|
||||||
@@ -682,7 +710,7 @@ export function useGetContractFile() {
|
|||||||
export function useGetAllContractFile() {
|
export function useGetAllContractFile() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (form_id: number) => {
|
mutationFn: async (form_id: number) => {
|
||||||
const res = await fetch(`${Config.backend_uri}/contracts/${form_id}/files`, {
|
const res = await fetchWithAuth(`${Config.backend_uri}/contracts/${form_id}/files`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res);
|
}).then((res) => res);
|
||||||
|
|
||||||
@@ -714,7 +742,6 @@ export function useCreateContract() {
|
|||||||
mutationFn: (newContract: ContractCreate) => {
|
mutationFn: (newContract: ContractCreate) => {
|
||||||
return fetch(`${Config.backend_uri}/contracts`, {
|
return fetch(`${Config.backend_uri}/contracts`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
@@ -737,7 +764,7 @@ export function useDeleteContract() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id: number) => {
|
mutationFn: (id: number) => {
|
||||||
return fetch(`${Config.backend_uri}/contracts/${id}`, {
|
return fetchWithAuth(`${Config.backend_uri}/contracts/${id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -769,7 +796,7 @@ export function useGetRoles(filters?: URLSearchParams): UseQueryResult<Role[], E
|
|||||||
return useQuery<Role[]>({
|
return useQuery<Role[]>({
|
||||||
queryKey: ["roles", queryString],
|
queryKey: ["roles", queryString],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
fetch(`${Config.backend_uri}/users/roles${filters ? `?${queryString}` : ""}`, {
|
fetchWithAuth(`${Config.backend_uri}/users/roles${filters ? `?${queryString}` : ""}`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
}).then((res) => res.json()),
|
}).then((res) => res.json()),
|
||||||
});
|
});
|
||||||
@@ -784,25 +811,5 @@ export function useCurrentUser() {
|
|||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useLogoutUser() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: () => {
|
|
||||||
return fetch(`${Config.backend_uri}/auth/logout`, {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}).then((res) => res.json());
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["currentUser"] });
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
33
frontend/src/services/auth/AuthProvider.tsx
Normal file
33
frontend/src/services/auth/AuthProvider.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { createContext, useContext } from "react";
|
||||||
|
import { useCurrentUser } from "../api";
|
||||||
|
import type { UserLogged } from "../resources/users";
|
||||||
|
|
||||||
|
export type Auth = {
|
||||||
|
loggedUser: UserLogged | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<Auth | undefined>(undefined)
|
||||||
|
|
||||||
|
export function AuthProvider({ children }: {children: React.ReactNode}) {
|
||||||
|
const {data: loggedUser, isLoading} = useCurrentUser();
|
||||||
|
|
||||||
|
const value: Auth = {
|
||||||
|
loggedUser: loggedUser ?? null,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth(): Auth {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useAuth must be used inside AuthProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import { useCurrentUser } from "@/services/api";
|
|
||||||
import { Group, Loader } from "@mantine/core";
|
import { Group, Loader } from "@mantine/core";
|
||||||
import { Navigate, Outlet } from "react-router";
|
import { Navigate, Outlet } from "react-router";
|
||||||
|
import { useAuth } from "../AuthProvider";
|
||||||
|
|
||||||
export function Auth() {
|
export function ProtectedRoute() {
|
||||||
const { data: userLogged, isLoading } = useCurrentUser();
|
const { loggedUser, isLoading } = useAuth();
|
||||||
|
|
||||||
if (!userLogged && isLoading)
|
if (!loggedUser && isLoading)
|
||||||
return (
|
return (
|
||||||
<Group align="center" justify="center" h="80vh" w="100%">
|
<Group align="center" justify="center" h="80vh" w="100%">
|
||||||
<Loader color="pink" />
|
<Loader color="pink" />
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!userLogged?.logged) {
|
if (!loggedUser?.logged) {
|
||||||
return <Navigate to="/" replace />;
|
return <Navigate to="/" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user