Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66b6878e50 | |||
| 7bdf422c32 | |||
| 9a259cb637 | |||
| 54072eb87d | |||
| ecf374685f | |||
| 62d58e30cd | |||
| a13b88bd32 | |||
| ea7bfc64f2 | |||
| 0e2a17a3e3 | |||
| 185cefe250 | |||
| 25ec2c7bcb | |||
| b667cde3d7 | |||
| d30af82765 | |||
| efd8510853 | |||
| e558aa6bdd | |||
| bd9210a3b5 | |||
| 1eba5623a3 | |||
| c762662231 |
@@ -4,23 +4,41 @@ http://ru.homyk.space {
|
|||||||
https://ru.homyk.space {
|
https://ru.homyk.space {
|
||||||
encode gzip
|
encode gzip
|
||||||
|
|
||||||
# --- API ---
|
|
||||||
handle /api/* {
|
handle /api/* {
|
||||||
reverse_proxy backend:{$PORT}
|
reverse_proxy backend:{$PORT}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Frontend ---
|
handle_path /main/* {
|
||||||
root * /srv
|
@images path_regexp \.(jpg|jpeg|png|webp|gif|svg)$
|
||||||
file_server
|
header @images Cache-Control "public, max-age=604800"
|
||||||
|
|
||||||
|
forward_auth backend:{$PORT} {
|
||||||
|
uri /api/verify
|
||||||
|
copy_headers Authorization
|
||||||
|
}
|
||||||
|
root * /srv/main
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_errors {
|
||||||
|
@unauth expression {http.error.status_code} == 401
|
||||||
|
redir @unauth / 302
|
||||||
|
}
|
||||||
|
handle {
|
||||||
|
root * /srv/auth
|
||||||
|
file_server {
|
||||||
|
index login.html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log {
|
log {
|
||||||
output file /data/logs/caddy.log {
|
output file /data/logs/caddy.log {
|
||||||
roll_size 5mb
|
roll_size 5mb
|
||||||
roll_keep 5
|
roll_keep 5
|
||||||
roll_keep_for 72h
|
roll_keep_for 72h
|
||||||
|
}
|
||||||
|
format json
|
||||||
}
|
}
|
||||||
format json
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
https://music.homyk.space {
|
https://music.homyk.space {
|
||||||
encode gzip
|
encode gzip
|
||||||
@@ -35,7 +53,7 @@ https://music.homyk.space {
|
|||||||
roll_size 5mb
|
roll_size 5mb
|
||||||
roll_keep 5
|
roll_keep 5
|
||||||
roll_keep_for 72h
|
roll_keep_for 72h
|
||||||
|
}
|
||||||
|
format json
|
||||||
}
|
}
|
||||||
format json
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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"}
|
||||||
@@ -1,73 +1,64 @@
|
|||||||
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) {
|
|
||||||
window.location.href = "http://localhost:5500/server/frontend/main/index.html";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.getElementById('loginForm').addEventListener('submit', async function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const password = document.getElementById('password').value;
|
|
||||||
const userData = {
|
|
||||||
password
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const response = await fetch("http://localhost:8000/api/auth", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
password: userData.password
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json(); // читаем только один раз
|
document.getElementById('loginForm').addEventListener('submit', async function(e) {
|
||||||
if (response.ok) { // сохраняем только при успехе
|
e.preventDefault();
|
||||||
// в sessionstorage до перезахода в браузер(
|
|
||||||
sessionStorage.setItem("token", data.access_token);
|
|
||||||
|
|
||||||
window.location.href = 'http://localhost:5500/server/frontend/main/index.html';
|
const code = document.getElementById('password').value;
|
||||||
} else { //парсинг и вывод ошибок, если есть
|
|
||||||
if (Array.isArray(data.detail)) {
|
try {
|
||||||
const messages = data.detail.map(e => {
|
const response = await fetch('/api/auth', {
|
||||||
const field = e.loc.filter(locPart => locPart !== 'body').join(' -> ');
|
method: 'POST',
|
||||||
return `${field}: ${e.msg}`;
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
credentials: 'include', // ← чтобы браузер принял cookie
|
||||||
showError(messages);
|
body: JSON.stringify({ code })
|
||||||
} else if (typeof data.detail === 'string') {
|
});
|
||||||
showError([data.detail]);
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.href = '/main/';
|
||||||
} else {
|
} else {
|
||||||
showError(['Unknown error']);
|
if (Array.isArray(data.detail)) {
|
||||||
|
const messages = data.detail.map(e => {
|
||||||
|
const field = e.loc.filter(p => p !== 'body').join(' -> ');
|
||||||
|
return `${field}: ${e.msg}`;
|
||||||
|
});
|
||||||
|
showError(messages);
|
||||||
|
} else if (typeof data.detail === 'string') {
|
||||||
|
showError([data.detail]);
|
||||||
|
} else {
|
||||||
|
showError(['Unknown error']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showError(['Connection error: ' + err.message]);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
});
|
||||||
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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,69 +1,55 @@
|
|||||||
function getToken() {
|
|
||||||
return localStorage.getItem("token") || sessionStorage.getItem("token");
|
|
||||||
}
|
|
||||||
function tokenCheck(){
|
|
||||||
const token = getToken();
|
|
||||||
if (!token) {
|
|
||||||
window.location.href = "http://localhost:5500/server/frontend/auth/login.html";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenCheck()
|
|
||||||
document.getElementById('logoutForm').addEventListener('submit', async function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
localStorage.removeItem("token");
|
|
||||||
sessionStorage.removeItem("token");
|
|
||||||
tokenCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const form = document.querySelector(".form-info");
|
|
||||||
|
|
||||||
form.addEventListener("submit", async (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('click', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// try {
|
await fetch('/api/logout', {
|
||||||
// собираем данные из формы
|
method: 'POST',
|
||||||
const name = document.getElementById('ffname').value || "";
|
credentials: 'include'
|
||||||
const middlename = document.getElementById('fmname').value || "";
|
});
|
||||||
const surname = document.getElementById('flname').value || "";
|
window.location.href = '/';
|
||||||
const text_field = document.getElementById('text_field')?.value || "";
|
});
|
||||||
const food = document.querySelector('input[name="food"]:checked')?.value || "";
|
|
||||||
const types_of_alco = Array.from(document.querySelectorAll('input[name="drink"]:checked'))
|
|
||||||
.map(el => el.value)
|
|
||||||
.join(', ');
|
|
||||||
|
|
||||||
const guestData = {
|
document.querySelector(".form-info").addEventListener("submit", async (e) => {
|
||||||
name: name,
|
e.preventDefault();
|
||||||
middlename: middlename,
|
|
||||||
surname: surname,
|
|
||||||
text_field: text_field,
|
|
||||||
activated: true,
|
|
||||||
types_of_food: food,
|
|
||||||
types_of_alco: types_of_alco
|
|
||||||
};
|
|
||||||
console.log(guestData)
|
|
||||||
|
|
||||||
// отправка на /api/update с Authorization
|
const guestData = {
|
||||||
// const updateResponse = await fetch('/api/update', {
|
name: document.getElementById('ffname').value || "",
|
||||||
// method: 'POST',
|
middlename: document.getElementById('fmname').value || "",
|
||||||
// headers: {
|
surname: document.getElementById('flname').value || "",
|
||||||
// 'Content-Type': 'application/json',
|
text_field: document.getElementById('text_field')?.value || "",
|
||||||
// 'Authorization': `Bearer ${token}`
|
activated: true,
|
||||||
// },
|
types_of_food: document.querySelector('input[name="food"]:checked')?.value || "",
|
||||||
// body: JSON.stringify(guestData)
|
types_of_alco: Array.from(document.querySelectorAll('input[name="drink"]:checked'))
|
||||||
// });
|
.map(el => el.value)
|
||||||
|
.join(', ')
|
||||||
|
};
|
||||||
|
|
||||||
// if (!updateResponse.ok) {
|
try {
|
||||||
// throw new Error('Ошибка при отправке формы');
|
const response = await fetch('/api/update', {
|
||||||
// }
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'include', // ← токен идёт через cookie
|
||||||
|
body: JSON.stringify(guestData)
|
||||||
|
});
|
||||||
|
|
||||||
// const updateData = await updateResponse.json();
|
if (!response.ok) {
|
||||||
// console.log('Форма успешно отправлена:', updateData);
|
const err = await response.json();
|
||||||
|
throw new Error(JSON.stringify(err.detail || 'Ошибка при отправке'));
|
||||||
|
}
|
||||||
|
|
||||||
// } catch (err) {
|
const data = await response.json();
|
||||||
// console.error(err);
|
console.log('Успешно:', data);
|
||||||
// alert('Ошибка: ' + err.message);
|
alert('Данные сохранены!');
|
||||||
// }
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert('Ошибка: ' + err.message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -469,8 +469,10 @@
|
|||||||
<button type="submit" class="answer_btn" id="bsubmit">
|
<button type="submit" class="answer_btn" id="bsubmit">
|
||||||
Подтвердить участие
|
Подтвердить участие
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
<button type="logout" class="answer_btn" id="logoutForm">
|
||||||
|
Выйти
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
Reference in New Issue
Block a user