remake apis 0.151
All checks were successful
Build Docker / deploy (push) Successful in 44s
Build Docker / build (push) Successful in 34s

This commit is contained in:
2026-03-19 02:41:39 +03:00
parent a13b88bd32
commit 62d58e30cd
4 changed files with 101 additions and 92 deletions

View File

@@ -9,6 +9,10 @@ https://ru.homyk.space {
} }
handle_path /main/* { handle_path /main/* {
forward_auth backend:{$PORT} {
uri /api/verify
copy_headers Authorization
}
root * /srv/main root * /srv/main
file_server file_server
} }

View File

@@ -1,16 +1,21 @@
from fastapi import FastAPI, Depends, HTTPException,status from fastapi import FastAPI, Depends, HTTPException,status, Response, Cookie
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import server.backend.schema.pydantic as pydantic import server.backend.schema.pydantic as pydantic
import server.backend.database.db as db import server.backend.database.db as db
from server.backend.auth.JWT import signJWT, decodeJWT from server.backend.auth.JWT import signJWT, decodeJWT
api = FastAPI(openapi_url="/api/openapi.json",docs_url="/api/docs", redoc_url="/api/redoc") api = FastAPI(openapi_url="/api/openapi.json",docs_url="/api/docs", redoc_url="/api/redoc")
security = HTTPBearer() security = HTTPBearer(auto_error=False)
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)): async def get_current_user(
token = credentials.credentials credentials: HTTPAuthorizationCredentials = Depends(security),
token_cookie: str = Cookie(default=None)
):
token = credentials.credentials if credentials else token_cookie
if not token:
raise HTTPException(status_code=401, detail="Not authenticated")
user = decodeJWT(token) user = decodeJWT(token)
if not user: if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token") raise HTTPException(status_code=401, detail="Invalid token")
return user return user
async def check_roles(user=Depends(get_current_user)): async def check_roles(user=Depends(get_current_user)):
user_check = await db.list_user(user["user_id"]) user_check = await db.list_user(user["user_id"])
@@ -58,10 +63,29 @@ async def list_users(user=Depends(check_roles)):
list_of_users = await db.list_users() list_of_users = await db.list_users()
return list_of_users return list_of_users
@api.post("/api/auth",response_model=pydantic.Token) @api.post("/api/auth")
async def auth(code:pydantic.UserAccess): async def auth(code: pydantic.UserAccess, response: Response):
login = await db.login_user(code) login = await db.login_user(code)
if login == None: if login is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Forbidden") raise HTTPException(status_code=401, detail="Forbidden")
token = signJWT(login) token = signJWT(login)
response.set_cookie(
key="token",
value=token,
httponly=True,
secure=True,
samesite="strict"
)
return {"access_token": token, "token_type": "bearer"} return {"access_token": token, "token_type": "bearer"}
@api.get("/api/verify")
async def verify(token: str = Cookie(default=None)):
if not token:
raise HTTPException(status_code=401, detail="No token")
user = decodeJWT(token)
if not user:
raise HTTPException(status_code=401, detail="Invalid token")
return {"status": "ok"}
@api.post("/api/logout")
async def logout(response: Response):
response.delete_cookie(key="token")
return {"status": "ok"}

View File

@@ -1,39 +1,29 @@
function getToken() { document.addEventListener('DOMContentLoaded', () => {
return localStorage.getItem("token") || sessionStorage.getItem("token"); fetch('/api/verify', { credentials: 'include' })
} .then(r => { if (r.ok) window.location.href = '/main/'; })
function tokenCheck(){ .catch(() => {});
const token = getToken();
if (token) { document.getElementById('loginForm').addEventListener('submit', async function(e) {
window.location.href = "https://ru.homyk.space/main/";
}
}
document.getElementById('loginForm').addEventListener('submit', async function (e) {
e.preventDefault(); e.preventDefault();
const password = document.getElementById('password').value;
const userData = { const code = document.getElementById('password').value;
password
};
try { try {
const response = await fetch("https://ru.homyk.space/api/auth", { const response = await fetch('/api/auth', {
method: "POST", method: 'POST',
headers: { headers: { 'Content-Type': 'application/json' },
"Content-Type": "application/json", credentials: 'include', // ← чтобы браузер принял cookie
}, body: JSON.stringify({ code })
body: JSON.stringify({
code: userData.password
}),
}); });
const data = await response.json(); // читаем только один раз const data = await response.json();
if (response.ok) { // сохраняем только при успехе
// в sessionstorage до перезахода в браузер(
sessionStorage.setItem("token", data.access_token);
window.location.href = 'https://ru.homyk.space/main/'; if (response.ok) {
} else { //парсинг и вывод ошибок, если есть window.location.href = '/main/';
} else {
if (Array.isArray(data.detail)) { if (Array.isArray(data.detail)) {
const messages = data.detail.map(e => { const messages = data.detail.map(e => {
const field = e.loc.filter(locPart => locPart !== 'body').join(' -> '); const field = e.loc.filter(p => p !== 'body').join(' -> ');
return `${field}: ${e.msg}`; return `${field}: ${e.msg}`;
}); });
showError(messages); showError(messages);
@@ -46,28 +36,29 @@ document.getElementById('loginForm').addEventListener('submit', async function (
} catch (err) { } catch (err) {
showError(['Connection error: ' + err.message]); showError(['Connection error: ' + err.message]);
} }
});
}); });
function showError(messages){ //Добавление их на form со стилями
function showError(messages) {
let errorElem = document.getElementById('formError'); let errorElem = document.getElementById('formError');
let container = document.getElementById('glass-container'); if (!errorElem) {
if (!errorElem){
errorElem = document.createElement('div'); errorElem = document.createElement('div');
errorElem.style.transition="3s";
errorElem.id = 'formError'; errorElem.id = 'formError';
errorElem.style.color = 'red'; errorElem.style.cssText = `
errorElem.style.marginTop = '20px'; color: red;
errorElem.style.fontSize = '14px'; margin-top: 20px;
errorElem.style.fontWeight = '100'; margin-bottom: 20px;
errorElem.style.marginBottom = '20px'; font-size: 14px;
errorElem.style.lineHeight="120%"; font-weight: 100;
errorElem.style.height = 'auto'; line-height: 120%;
const form = document.getElementById('loginForm'); transition: 3s;
form.insertAdjacentElement('afterend', errorElem); `;
}; document.getElementById('loginForm').insertAdjacentElement('afterend', errorElem);
}
errorElem.innerHTML = ''; errorElem.innerHTML = '';
messages.forEach(msg => { messages.forEach(msg => {
const li = document.createElement('li'); const li = document.createElement('li');
li.style.listStyleType="none"; li.style.listStyleType = 'none';
li.textContent = msg; li.textContent = msg;
errorElem.appendChild(li); errorElem.appendChild(li);
}); });

View File

@@ -1,29 +1,23 @@
function getToken() {
return localStorage.getItem("token") || sessionStorage.getItem("token");
}
function tokenCheck() {
const token = getToken();
if (!token) {
window.location.href = "https://ru.homyk.space";
}
}
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
tokenCheck();
document.getElementById('logoutForm').addEventListener('submit', function(e) { // Проверка авторизации через cookie
fetch('/api/verify', { credentials: 'include' })
.then(r => { if (!r.ok) window.location.href = '/'; })
.catch(() => { window.location.href = '/'; });
// Logout — удаляем cookie на бэкенде
document.getElementById('logoutForm').addEventListener('submit', async function(e) {
e.preventDefault(); e.preventDefault();
localStorage.removeItem("token"); await fetch('/api/logout', {
sessionStorage.removeItem("token"); method: 'POST',
tokenCheck(); credentials: 'include'
});
window.location.href = '/';
}); });
document.querySelector(".form-info").addEventListener("submit", async (e) => { document.querySelector(".form-info").addEventListener("submit", async (e) => {
e.preventDefault(); e.preventDefault();
const token = getToken();
const guestData = { const guestData = {
name: document.getElementById('ffname').value || "", name: document.getElementById('ffname').value || "",
middlename: document.getElementById('fmname').value || "", middlename: document.getElementById('fmname').value || "",
@@ -36,15 +30,11 @@ document.addEventListener("DOMContentLoaded", () => {
.join(', ') .join(', ')
}; };
console.log(guestData);
try { try {
const response = await fetch('/api/update', { const response = await fetch('/api/update', {
method: 'POST', method: 'POST',
headers: { headers: { 'Content-Type': 'application/json' },
'Content-Type': 'application/json', credentials: 'include', // ← токен идёт через cookie
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(guestData) body: JSON.stringify(guestData)
}); });