dev
RekHoto 2 years ago
parent bc7db43a1a
commit 2d9ff8831a

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.0.1", "@ant-design/icons": "^5.0.1",
"@nanostores/react": "^0.4.1", "@nanostores/react": "^0.4.1",
"@react-keycloak/web": "^3.4.0",
"@tanstack/react-query": "^4.24.9", "@tanstack/react-query": "^4.24.9",
"@turf/bbox": "^6.5.0", "@turf/bbox": "^6.5.0",
"@turf/helpers": "^6.5.0", "@turf/helpers": "^6.5.0",
@ -20,6 +21,7 @@
"chart.js": "^4.4.0", "chart.js": "^4.4.0",
"immer": "^9.0.19", "immer": "^9.0.19",
"immutable": "^4.3.0", "immutable": "^4.3.0",
"keycloak-js": "^23.0.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"mapbox-gl": "npm:empty-npm-package@1.0.0", "mapbox-gl": "npm:empty-npm-package@1.0.0",
"maplibre-gl": "^2.4.0", "maplibre-gl": "^2.4.0",

@ -11,7 +11,8 @@ import { usePendingPointsFilters } from "./stores/usePendingPointsFilters";
import { useOnApprovalPointsFilters } from "./stores/useOnApprovalPointsFilters"; import { useOnApprovalPointsFilters } from "./stores/useOnApprovalPointsFilters";
import { useWorkingPointsFilters } from "./stores/useWorkingPointsFilters"; import { useWorkingPointsFilters } from "./stores/useWorkingPointsFilters";
import useLocalStorage from "./hooks/useLocalStorage.js"; import useLocalStorage from "./hooks/useLocalStorage.js";
import { ReactKeycloakProvider } from "@react-keycloak/web";
import { keycloak } from "./keycloak.js";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
enableMapSet(); enableMapSet();
@ -35,6 +36,7 @@ function App() {
} }
return ( return (
<ReactKeycloakProvider authClient={keycloak}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<BrowserRouter basename={import.meta.env.BASE_URL}> <BrowserRouter basename={import.meta.env.BASE_URL}>
<Routes> <Routes>
@ -45,6 +47,7 @@ function App() {
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
</QueryClientProvider> </QueryClientProvider>
</ReactKeycloakProvider>
); );
} }

@ -1,16 +1,9 @@
import { Button, Popover, Tooltip } from "antd"; import { Button, Popover, Tooltip } from "antd";
import { ArrowRightOutlined, LogoutOutlined } from "@ant-design/icons"; import { ArrowRightOutlined, LogoutOutlined } from "@ant-design/icons";
import { api } from "./api"; import { api } from "./api";
import { setAuth } from "./stores/auth";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Title } from "./components/Title"; import { Title } from "./components/Title";
import { keycloak } from "./keycloak.js";
export const logOut = () => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('expires_in');
setAuth(false);
};
export function SignOut() { export function SignOut() {
const { data } = useQuery(["profile"], async () => { const { data } = useQuery(["profile"], async () => {
@ -24,7 +17,7 @@ export function SignOut() {
content={ content={
<> <>
<Title text={data?.username} classNameText={"lowercase"} /> <Title text={data?.username} classNameText={"lowercase"} />
<Button type="primary" block onClick={logOut}> <Button type="primary" block onClick={keycloak.logout}>
<span className="mr-1">Выйти</span> <span className="mr-1">Выйти</span>
<ArrowRightOutlined /> <ArrowRightOutlined />
</Button> </Button>

@ -1,21 +1,17 @@
import { useStore } from "@nanostores/react"; import { useKeycloak } from "@react-keycloak/web";
import { Spin } from "antd";
import { Navigate } from "react-router-dom";
import { isAuthorized$, userInfoLoading$ } from "./stores/auth";
export function WithAuth(props) { export function WithAuth(props) {
const isAuthorized = useStore(isAuthorized$); const { keycloak } = useKeycloak();
const userInfoLoading = useStore(userInfoLoading$);
if (userInfoLoading) { const login = async () => {
return <Spin className="user-info-loader" />; await keycloak.login();
} }
if (isAuthorized) { if (keycloak.authenticated) {
return <>{props.children}</>; return <>{props.children}</>;
} else {
login()
} }
return <Navigate to="/signin" replace={true} />; return <></>;
} }

@ -7,6 +7,8 @@ import { appendFiltersInUse } from "./utils.js";
import { useMode } from "./stores/useMode.js"; import { useMode } from "./stores/useMode.js";
import { useMemo } from "react"; import { useMemo } from "react";
import { useUpdateLayerCounter } from "./stores/useUpdateLayerCounter.js"; import { useUpdateLayerCounter } from "./stores/useUpdateLayerCounter.js";
import { keycloak } from "./keycloak.js";
import { useKeycloak } from "@react-keycloak/web";
export const BASE_URL = import.meta.env.VITE_API_URL; export const BASE_URL = import.meta.env.VITE_API_URL;
@ -17,10 +19,10 @@ export const api = axios.create({
: BASE_URL, : BASE_URL,
}); });
api.interceptors.request.use(function (config) { api.interceptors.request.use(async function (config) {
const token = localStorage.getItem("access_token"); const token = keycloak.token;
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${ token }`;
} }
return config; return config;
@ -235,14 +237,14 @@ const TASK_STATUSES = {
finished: "Перерасчет ML завершен" finished: "Перерасчет ML завершен"
} }
export const useCanEdit = () => { export const useCanEdit = () => {
const { data } = useGetPermissions(); const { keycloak } = useKeycloak();
const { data: statusData } = useLastMLRun(); const { data: statusData } = useLastMLRun();
const hasFinishedUpdate = useMemo(() => { const hasFinishedUpdate = useMemo(() => {
return statusData?.task_status === TASK_STATUSES.finished return statusData?.task_status === TASK_STATUSES.finished
}, [statusData]); }, [statusData]);
return data === "editor" && hasFinishedUpdate; return keycloak.hasResourceRole("postnet_editor", "postnet") && hasFinishedUpdate;
}; };
export const useUpdatePostamatId = () => { export const useUpdatePostamatId = () => {

@ -0,0 +1,7 @@
import Keycloak from "keycloak-js";
export const keycloak = new Keycloak({
url: import.meta.env.VITE_KEYCLOAK_URL,
realm: "SST",
clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
});

@ -3,27 +3,13 @@ import { Alert, Button, Form, Input, Space, Typography } from "antd";
import { LockOutlined, UserOutlined } from "@ant-design/icons"; import { LockOutlined, UserOutlined } from "@ant-design/icons";
import React from "react"; import React from "react";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { isAuthorized$, refreshTokenIntervalFunction, setAuthLocalStorage } from "../stores/auth"; import { isAuthorized$ } from "../stores/auth";
import { signin, signinError$, signinLoading$ } from "../stores/signin"; import { signinError$, signinLoading$ } from "../stores/signin";
function LoginForm() { function LoginForm() {
const signinError = useStore(signinError$); const signinError = useStore(signinError$);
const signinLoading = useStore(signinLoading$); const signinLoading = useStore(signinLoading$);
const onFinish = async (values) => {
const data = await signin(values);
setAuthLocalStorage(data);
let interval;
setTimeout(async () => {
await refreshTokenIntervalFunction();
interval = setInterval(async () => {
await refreshTokenIntervalFunction();
}, (data.expires_in - 5) * 1000)
}, 0);
};
return ( return (
<Space direction="vertical" style={{ width: "320px" }}> <Space direction="vertical" style={{ width: "320px" }}>
{signinError.length > 0 ? ( {signinError.length > 0 ? (

@ -1,6 +1,4 @@
import { action, atom } from "nanostores"; import { action, atom } from "nanostores";
import { api } from "../api";
import { logOut } from "../SignOut.jsx";
export const userInfoLoading$ = atom(true); export const userInfoLoading$ = atom(true);
@ -9,67 +7,3 @@ export const isAuthorized$ = atom(false);
export const setAuth = action(isAuthorized$, "setAuth", (store, newValue) => { export const setAuth = action(isAuthorized$, "setAuth", (store, newValue) => {
store.set(newValue); store.set(newValue);
}); });
export const refreshToken = () => {
const url = import.meta.env.VITE_KEYCLOAK_URL || 'https://kk.dev.selftech.ru/';
const clientId = import.meta.env.VITE_KEYCLOAK_CLIENT_ID || 'postnet';
const clientSecret = import.meta.env.VITE_KEYCLOAK_CLIENT_SECRET || 'K2yHweEUispkVeWn03VMk843sW2Moic5';
return api.request({
url: "/realms/SST/protocol/openid-connect/token",
baseURL: url,
method: "POST",
data: {
grant_type: "refresh_token",
client_id: clientId,
client_secret: clientSecret,
token_type: "bearer",
refresh_token: localStorage.getItem("refresh_token")
},
headers: {
'Content-type': 'application/x-www-form-urlencoded',
},
});
}
export const setAuthLocalStorage = (data) => {
localStorage.setItem("access_token", data.access_token);
localStorage.setItem("refresh_token", data.refresh_token);
localStorage.setItem("expires_in", data.expires_in);
}
export const refreshTokenIntervalFunction = async () => {
try {
const { data: refreshData } = await refreshToken();
setAuthLocalStorage(refreshData);
} catch (error) {
clearInterval(interval);
logOut();
throw error;
}
}
async function checkAuth() {
try {
const { data } = await refreshToken();
setAuthLocalStorage(data);
let interval;
setTimeout(async () => {
await refreshTokenIntervalFunction();
interval = setInterval(async () => {
await refreshTokenIntervalFunction();
}, (data.expires_in - 5) * 1000)
}, 0);
setAuth(true);
} catch (e) {
console.log("Not authorized");
clearInterval(interval);
} finally {
userInfoLoading$.set(false);
}
}
checkAuth();

Loading…
Cancel
Save