rememberme, dashboard, logout features
This commit is contained in:
@@ -7,7 +7,9 @@ export async function login(email: string, password: string) {
|
|||||||
body,
|
body,
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error("Неверный email или пароль");
|
if (!res.ok){
|
||||||
|
throw new Error("Неверный email или пароль");
|
||||||
|
};
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setAccessToken(data.access_token);
|
setAccessToken(data.access_token);
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { API_URL, setAccessToken } from "../token";
|
||||||
|
|
||||||
|
export async function refreshToken(): Promise<void> {
|
||||||
|
const response = await fetch(`${API_URL}/protected/refresh`, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Session update wasn't successful");
|
||||||
|
};
|
||||||
|
const data = await response.json();
|
||||||
|
setAccessToken(data.access_token);
|
||||||
|
}
|
||||||
@@ -5,7 +5,9 @@ export async function requestPasswordReset(email: string): Promise<void> {
|
|||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const body = await res.json().catch(() => null);
|
const body = await res.json().catch(function(){
|
||||||
|
return null
|
||||||
|
});
|
||||||
throw new Error(typeof body?.detail === "string" ? body.detail : "Не удалось отправить запрос");
|
throw new Error(typeof body?.detail === "string" ? body.detail : "Не удалось отправить запрос");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { UserOut } from "../../types/UserTypes";
|
||||||
|
import { authFetch } from "../token";
|
||||||
|
|
||||||
|
|
||||||
|
export async function fetchUser(): Promise<UserOut> {
|
||||||
|
const response = await authFetch("/protected/me");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Not authenticated");
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
console.log(data)
|
||||||
|
return data;
|
||||||
|
}
|
||||||
@@ -1,17 +1,49 @@
|
|||||||
|
import { refreshToken } from "./RefreshAPI/Refresh";
|
||||||
|
|
||||||
export const API_URL = import.meta.env.VITE_BACKEND_URL;
|
export const API_URL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
|
||||||
let accessToken: string | null = null;
|
let accessToken: string | null = null;
|
||||||
|
let refreshTimer: ReturnType<typeof setInterval> | null =null;
|
||||||
|
|
||||||
export function setAccessToken(token: string | null) {
|
export function setAccessToken(token: string | null):void{
|
||||||
accessToken = token;
|
accessToken = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAccessToken() {
|
export function getAccessToken():string|null {
|
||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function authFetch(path: string, init: RequestInit = {}) {
|
export async function authFetch(path: string, init: RequestInit = {}) {
|
||||||
const headers = new Headers(init.headers);
|
const headers = new Headers(init.headers);
|
||||||
if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
|
if (accessToken) {
|
||||||
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
||||||
|
};
|
||||||
return fetch(`${API_URL}${path}`, { ...init, headers, credentials: "include" });
|
return fetch(`${API_URL}${path}`, { ...init, headers, credentials: "include" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function startRefreshTimer(intervalms:number) {
|
||||||
|
stopRefreshTimer();
|
||||||
|
refreshTimer = setInterval(function () {
|
||||||
|
refreshToken().catch(function() {
|
||||||
|
|
||||||
|
});
|
||||||
|
}, intervalms);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopRefreshTimer() {
|
||||||
|
if (refreshTimer) clearInterval(refreshTimer);
|
||||||
|
refreshTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logout() {
|
||||||
|
try {
|
||||||
|
await authFetch(`/protected/logout`);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("Something went wrong", { cause: err });
|
||||||
|
}
|
||||||
|
|
||||||
|
setAccessToken(null);
|
||||||
|
stopRefreshTimer();
|
||||||
|
localStorage.removeItem("rememberMe");
|
||||||
|
sessionStorage.removeItem("rememberMe");
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,15 +12,16 @@ export function Auth():ReactElement {
|
|||||||
const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
|
const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const validateEmail = (value: string) => {
|
function validateEmail(value: string) {
|
||||||
if (!value) return "Введите email";
|
if (!value) return "Введите email";
|
||||||
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
}
|
||||||
const validatePassword = (value: string) =>
|
function validatePassword(value: string) {
|
||||||
value ? undefined : "Введите пароль";
|
return value ? undefined : "Введите пароль";
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
|
async function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const next = { email: validateEmail(email), password: validatePassword(password) };
|
const next = { email: validateEmail(email), password: validatePassword(password) };
|
||||||
setErrors(next);
|
setErrors(next);
|
||||||
@@ -34,7 +35,7 @@ export function Auth():ReactElement {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
setErrors({ password: "Неверный email или пароль" });
|
setErrors({ password: "Неверный email или пароль" });
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ export function Reset():ReactElement {
|
|||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [errors, setErrors] = useState<{ email?: string;}>({});
|
const [errors, setErrors] = useState<{ email?: string;}>({});
|
||||||
|
|
||||||
const validateEmail = (value: string) => {
|
function validateEmail(value: string) {
|
||||||
if (!value) return "Введите email";
|
if (!value) return "Введите email";
|
||||||
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
|
async function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const next = {
|
const next = {
|
||||||
email: validateEmail(email),
|
email: validateEmail(email),
|
||||||
|
|||||||
@@ -3,7 +3,17 @@ import { createRoot } from 'react-dom/client'
|
|||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
import { refreshToken } from './api/RefreshAPI/Refresh.ts'
|
||||||
|
import { getRememberMe } from './utils/rememberMe.ts'
|
||||||
|
import { startRefreshTimer } from './api/token.ts'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (getRememberMe()) {
|
||||||
|
await refreshToken().catch(() => {});
|
||||||
|
console.log("Timer started");
|
||||||
|
startRefreshTimer(10*60*1000);
|
||||||
|
}
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
|||||||
@@ -1,9 +1,50 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { fetchUser } from "../api/UserAPI/User";
|
||||||
|
import { logout } from "../api/token";
|
||||||
|
|
||||||
|
|
||||||
export function DashboardPage() {
|
export function DashboardPage() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [permissions, setPermissions] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
useEffect(function () {
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
const user = await fetchUser();
|
||||||
|
|
||||||
|
const permissions = new Set<string>();
|
||||||
|
|
||||||
|
for (const p of user.direct_permissions) {
|
||||||
|
permissions.add(p.permission);
|
||||||
|
}
|
||||||
|
for (const g of user.group) {
|
||||||
|
for (const p of g.permissions) {
|
||||||
|
permissions.add(p.permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setPermissions(permissions);
|
||||||
|
setLoading(false);
|
||||||
|
} catch {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, [navigate]);
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
async function handleLogout() {
|
||||||
|
await logout();
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div><p>test</p></div>
|
{permissions.has("admin") && (<div><p>test</p></div>
|
||||||
|
)}
|
||||||
|
<button onClick={handleLogout}>logout</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,36 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Auth } from "../features/auth/components/AuthComponent";
|
import { Auth } from "../features/auth/components/AuthComponent";
|
||||||
import { AuthLayout } from "../layouts/AuthLayout";
|
import { AuthLayout } from "../layouts/AuthLayout";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { fetchUser } from "../api/UserAPI/User";
|
||||||
|
import { getAccessToken } from "../api/token";
|
||||||
|
|
||||||
export function LoginPage(){
|
export function LoginPage(){
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [checking, setChecking] = useState(function () {
|
||||||
|
return !!getAccessToken();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(function () {
|
||||||
|
if (!getAccessToken()) return;
|
||||||
|
|
||||||
|
async function check() {
|
||||||
|
try {
|
||||||
|
await fetchUser();
|
||||||
|
navigate("/dashboard");
|
||||||
|
} catch {
|
||||||
|
setChecking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check();
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (checking) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return(
|
return(
|
||||||
<AuthLayout>
|
<AuthLayout>
|
||||||
<Auth />
|
<Auth />
|
||||||
|
|||||||
@@ -3,7 +3,19 @@
|
|||||||
export function persistRememberMe(value:boolean):void {
|
export function persistRememberMe(value:boolean):void {
|
||||||
if (value) {
|
if (value) {
|
||||||
localStorage.setItem("rememberMe", "True");
|
localStorage.setItem("rememberMe", "True");
|
||||||
|
sessionStorage.removeItem("rememberMe");
|
||||||
} else{
|
} else{
|
||||||
|
sessionStorage.setItem("rememberMe", "True");
|
||||||
localStorage.removeItem("rememberMe");
|
localStorage.removeItem("rememberMe");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getRememberMe():boolean {
|
||||||
|
if (localStorage.getItem("rememberMe") || sessionStorage.getItem("rememberMe")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user