rememberme, dashboard, logout features
This commit is contained in:
@@ -7,7 +7,9 @@ export async function login(email: string, password: string) {
|
||||
body,
|
||||
credentials: "include",
|
||||
});
|
||||
if (!res.ok) throw new Error("Неверный email или пароль");
|
||||
if (!res.ok){
|
||||
throw new Error("Неверный email или пароль");
|
||||
};
|
||||
const data = await res.json();
|
||||
setAccessToken(data.access_token);
|
||||
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",
|
||||
});
|
||||
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 : "Не удалось отправить запрос");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function getAccessToken() {
|
||||
export function getAccessToken():string|null {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
export async function authFetch(path: string, init: RequestInit = {}) {
|
||||
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" });
|
||||
}
|
||||
|
||||
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,29 +12,30 @@ export function Auth():ReactElement {
|
||||
const [errors, setErrors] = useState<{ email?: string; password?: string }>({});
|
||||
const navigate = useNavigate();
|
||||
|
||||
const validateEmail = (value: string) => {
|
||||
function validateEmail(value: string) {
|
||||
if (!value) return "Введите email";
|
||||
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
||||
return undefined;
|
||||
};
|
||||
const validatePassword = (value: string) =>
|
||||
value ? undefined : "Введите пароль";
|
||||
}
|
||||
function validatePassword(value: string) {
|
||||
return value ? undefined : "Введите пароль";
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const next = { email: validateEmail(email), password: validatePassword(password) };
|
||||
setErrors(next);
|
||||
if (next.email || next.password) return;
|
||||
async function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
const next = { email: validateEmail(email), password: validatePassword(password) };
|
||||
setErrors(next);
|
||||
if (next.email || next.password) return;
|
||||
|
||||
try {
|
||||
await login(email, password);
|
||||
persistRememberMe(rememberMe);
|
||||
navigate("/dashboard");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setErrors({ password: "Неверный email или пароль" });
|
||||
try {
|
||||
await login(email, password);
|
||||
persistRememberMe(rememberMe);
|
||||
navigate("/dashboard");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setErrors({ password: "Неверный email или пароль" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -8,12 +8,12 @@ export function Reset():ReactElement {
|
||||
const [email, setEmail] = useState("");
|
||||
const [errors, setErrors] = useState<{ email?: string;}>({});
|
||||
|
||||
const validateEmail = (value: string) => {
|
||||
function validateEmail(value: string) {
|
||||
if (!value) return "Введите email";
|
||||
if (!/^\S+@\S+\.\S+$/.test(value)) return "Некорректный email";
|
||||
return undefined;
|
||||
};
|
||||
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
|
||||
async function handleSubmit(e: SubmitEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
const next = {
|
||||
email: validateEmail(email),
|
||||
|
||||
@@ -3,7 +3,17 @@ import { createRoot } from 'react-dom/client'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import './index.css'
|
||||
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(
|
||||
<StrictMode>
|
||||
<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() {
|
||||
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 (
|
||||
<>
|
||||
<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 { AuthLayout } from "../layouts/AuthLayout";
|
||||
import { useEffect, useState } from "react";
|
||||
import { fetchUser } from "../api/UserAPI/User";
|
||||
import { getAccessToken } from "../api/token";
|
||||
|
||||
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(
|
||||
<AuthLayout>
|
||||
<Auth />
|
||||
|
||||
@@ -3,7 +3,19 @@
|
||||
export function persistRememberMe(value:boolean):void {
|
||||
if (value) {
|
||||
localStorage.setItem("rememberMe", "True");
|
||||
sessionStorage.removeItem("rememberMe");
|
||||
} else{
|
||||
sessionStorage.setItem("rememberMe", "True");
|
||||
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