Mainly auth re-thinking - just in thought
This commit is contained in:
parent
3cde652d52
commit
8941213c8d
@ -1,22 +0,0 @@
|
||||
function parseJwt (token) {
|
||||
var base64Url = token.split('.')[1];
|
||||
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
|
||||
return JSON.parse(jsonPayload);
|
||||
}
|
||||
|
||||
function isJwtExpired (token) {
|
||||
var jwt = parseJwt(token);
|
||||
return jwt.exp < Date.now() / 1000;
|
||||
}
|
||||
|
||||
async function refreshAccessToken (refreshToken) {
|
||||
const data = await fetch('/api/refresh', {
|
||||
method: 'POST',
|
||||
headers: {'Authorization': 'Bearer ' + refreshToken}
|
||||
});
|
||||
return data.access_token;
|
||||
}
|
@ -2,6 +2,7 @@ from fastapi import FastAPI, Depends, Request, Path
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import RedirectResponse, JSONResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from app.routes.auth_routes import router as auth_router
|
||||
from app.routes.links_routes import router as links_router
|
||||
from app.routes.user_routes import router as user_router
|
||||
@ -35,6 +36,7 @@ app.add_middleware(
|
||||
allow_credentials=True,
|
||||
)
|
||||
|
||||
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
# Import routes
|
||||
|
@ -33,7 +33,7 @@ async def login_for_access_token(
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
access_token_expires = timedelta(minutes=15)
|
||||
access_token_expires = timedelta(minutes=1)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.id, "username": user.username, "refresh": False},
|
||||
expires_delta=access_token_expires,
|
||||
@ -63,7 +63,7 @@ async def refresh_access_token(
|
||||
"""
|
||||
Return a new access token if the refresh token is valid
|
||||
"""
|
||||
access_token_expires = timedelta(minutes=30)
|
||||
access_token_expires = timedelta(minutes=1)
|
||||
access_token = create_access_token(
|
||||
data={"sub": current_user.id, "refresh": False},
|
||||
expires_delta=access_token_expires,
|
||||
|
@ -16,7 +16,7 @@ from app.util.authentication import get_current_user
|
||||
router = APIRouter(prefix="/links", tags=["links"])
|
||||
|
||||
|
||||
@router.get("/", summary="Get all of the links associated with your account")
|
||||
@router.get("", summary="Get all of the links associated with your account")
|
||||
async def get_links(
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
db=Depends(get_db),
|
||||
@ -35,7 +35,7 @@ async def get_links(
|
||||
return links
|
||||
|
||||
|
||||
@router.post("/", summary="Create a new link")
|
||||
@router.post("", summary="Create a new link")
|
||||
async def create_link(
|
||||
url: URLSchema,
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
|
58
app/static/js/jwt.js
Normal file
58
app/static/js/jwt.js
Normal file
@ -0,0 +1,58 @@
|
||||
// Description: This file contains functions to access the API with JWT authentication.
|
||||
|
||||
/**
|
||||
* Accept a full URL, method, and body to send to the API.
|
||||
* - If successful, return the response
|
||||
* - If first fail, attempt to refresh JWT token and try again
|
||||
* - If second fail, return false
|
||||
* @param {*} endpoint API endpoint
|
||||
* @param {*} method String (GET, POST, PUT, DELETE)
|
||||
* @param {*} body Data to send to the API
|
||||
* @returns boolean
|
||||
*/
|
||||
async function accessAPI(endpoint, method, body) {
|
||||
let response = await fetch(`/api${endpoint}`, {
|
||||
method: method,
|
||||
body: body,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
let data = await response.json();
|
||||
data = await data;
|
||||
console.log(data);
|
||||
return data;
|
||||
} else if (response.status === 401) {
|
||||
console.log('REFRESHING TOKEN')
|
||||
if (await refreshAccessToken()) {
|
||||
// Try the request again
|
||||
let response = await fetch(`/api${endpoint}`, {
|
||||
method: method,
|
||||
body: body,
|
||||
});
|
||||
if (response.ok) {
|
||||
let data = await response.json();
|
||||
data = await data;
|
||||
console.log("REFRESHED DATA")
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to refresh the JWT token
|
||||
* @returns boolean
|
||||
*/
|
||||
async function refreshAccessToken () {
|
||||
const response = await fetch('/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
});
|
||||
if (response.ok) {
|
||||
console.log("TOKEN REFRESH")
|
||||
return true;
|
||||
} else {
|
||||
console.log("TOKEN REFRESH FAILED")
|
||||
return false;
|
||||
}
|
||||
}
|
@ -6,12 +6,13 @@
|
||||
<title>LinkLogger | Dashboard</title>
|
||||
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
|
||||
<script src="/static/js/jwt.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<!-- Create a table with 4 columns with a total of 1000px width -->
|
||||
<table>
|
||||
<tr>
|
||||
<tr style="border: 2px solid #ccc;">
|
||||
<th>Link</th>
|
||||
<th>Visits</th>
|
||||
<th>Redirect</th>
|
||||
@ -36,20 +37,30 @@
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
margin: 20px 0 20px 0;
|
||||
text-align: center;
|
||||
font-size: 25px;
|
||||
width: 1250px;
|
||||
width: 1000px;
|
||||
color: #ccc;
|
||||
border-collapse: collapse;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Center all sub tables */
|
||||
.log-table-row table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.log-table-row table {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
table th {
|
||||
background-color: #415eac;
|
||||
padding: 10px;
|
||||
border: 2px solid #ccc;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.link-table-row {
|
||||
@ -91,25 +102,6 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
async function fetchLinks() {
|
||||
const response = await fetch('/api/links');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch links');
|
||||
}
|
||||
const links = await response.json();
|
||||
return links;
|
||||
}
|
||||
|
||||
async function fetchLogs(link) {
|
||||
const response = await fetch(`/api/links/${link}/logs`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch logs');
|
||||
}
|
||||
const logs = await response.json();
|
||||
return logs;
|
||||
}
|
||||
|
||||
|
||||
function createRow(index, link, logs) {
|
||||
// Create the sub-table with the logs
|
||||
let subTable = `
|
||||
@ -124,10 +116,20 @@
|
||||
`;
|
||||
// Loop through the logs and create a row for each one
|
||||
logs.forEach((log, index) => {
|
||||
let logTimestamp = new Date(log.timestamp).toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).replace(',', '');
|
||||
|
||||
let row = `
|
||||
<tr id="${log.id}-log">
|
||||
<td>${logs.length - index}</td>
|
||||
<td>${log.timestamp}</td>
|
||||
<td>${logTimestamp}</td>
|
||||
<td>${log.ip}</td>
|
||||
<td>${log.location}</td>
|
||||
<td>${log.isp}</td>
|
||||
@ -172,21 +174,23 @@
|
||||
|
||||
|
||||
async function getData() {
|
||||
let links = fetchLinks();
|
||||
links = await links;
|
||||
|
||||
console.log(links);
|
||||
const links = await accessAPI(`/links`, 'GET')
|
||||
if (!links) {
|
||||
throw new Error('Failed to fetch links');
|
||||
}
|
||||
// Links is an Array of objects with the link data
|
||||
// Loop through the links and create a row for each one
|
||||
links.forEach(async (link, index) => {
|
||||
// Fetch the logs for the link
|
||||
let logs = await fetchLogs(link.link);
|
||||
logs = await logs;
|
||||
// Create the entire row with sub-table and logs
|
||||
let row = createRow(index, link, logs);
|
||||
// Add the new table row to the main table
|
||||
// Do not use async because then the order or data in the
|
||||
// table will change from time to time
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
let link = links[i];
|
||||
let logs = await accessAPI(`/links/${link.link}/logs`, 'GET')
|
||||
if (!logs) {
|
||||
throw new Error('Failed to fetch logs');
|
||||
}
|
||||
let row = createRow(i, link, logs);
|
||||
document.querySelector('table').innerHTML += row;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// hideLogRows to all log-table-rows
|
||||
@ -203,6 +207,7 @@
|
||||
let id = event.target.id;
|
||||
let logTR = document.getElementById(`${id}-logTR`);
|
||||
if (logTR.style.display === 'none') {
|
||||
// Hide any open log tables
|
||||
hideLogRows();
|
||||
logTR.style.display = 'table-row';
|
||||
} else {
|
||||
@ -214,14 +219,18 @@
|
||||
// Add an event listen to all trash bins
|
||||
document.addEventListener('click', (event) => {
|
||||
if (event.target.classList.contains('fa-trash')) {
|
||||
let id = event.target.id;
|
||||
let link = id.split('/')[1];
|
||||
let logId = id.split('/')[0];
|
||||
fetch(`/api/links/${link}/logs/${logId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
let logRow = document.getElementById(`${logId}-log`)
|
||||
logRow.remove();
|
||||
// Confirm the user wants to delete the log
|
||||
let confirmDelete = confirm('Are you sure you want to delete this log?');
|
||||
if (confirmDelete) {
|
||||
let id = event.target.id;
|
||||
let link = id.split('/')[1];
|
||||
let logId = id.split('/')[0];
|
||||
fetch(`/api/links/${link}/logs/${logId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
let logRow = document.getElementById(`${logId}-log`)
|
||||
logRow.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -72,6 +72,10 @@ async def refresh_get_current_user(
|
||||
return await get_current_user(token, is_refresh=True, db=db)
|
||||
|
||||
|
||||
def process_refresh_token(token: str, db: Session):
|
||||
return False
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
@ -84,15 +88,6 @@ async def get_current_user(
|
||||
Otherwise, the request is from an API and we should return a 401
|
||||
"""
|
||||
|
||||
# If the request is from /api/auth/refresh, it is a request to get
|
||||
# a new access token using a refresh token
|
||||
if request.url.path == "/api/auth/refresh":
|
||||
token = request.cookies.get("refresh_token")
|
||||
is_refresh = True
|
||||
else:
|
||||
token = request.cookies.get("access_token")
|
||||
is_refresh = False
|
||||
|
||||
def raise_unauthorized():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@ -100,6 +95,16 @@ async def get_current_user(
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# If the request is from /api/auth/refresh, it is a request to get
|
||||
# a new access token using a refresh token
|
||||
if request.url.path == "/api/auth/refresh":
|
||||
token = request.cookies.get("refresh_token")
|
||||
user = process_refresh_token(token, db)
|
||||
if user is None:
|
||||
raise_unauthorized()
|
||||
else:
|
||||
token = request.cookies.get("access_token")
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, secret_key, algorithms=[algorithm])
|
||||
id: int = payload.get("sub")
|
||||
|
Loading…
x
Reference in New Issue
Block a user