feat(frontend): migrated old > new silo adjustments
moved the apps around so we can use 1 url for cors bs
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { redirect, useNavigate, useRouter } from "@tanstack/react-router";
|
||||
import { createAuthClient } from "better-auth/client";
|
||||
import { usernameClient } from "better-auth/client/plugins";
|
||||
import { create } from "zustand";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import { redirect, useNavigate, useRouter } from "@tanstack/react-router";
|
||||
import { create } from "zustand";
|
||||
import { api } from "./axiosAPI";
|
||||
|
||||
// ---- TYPES ----
|
||||
@@ -11,184 +11,190 @@ export type Session = typeof authClient.$Infer.Session | null;
|
||||
|
||||
// Zustand store type
|
||||
type SessionState = {
|
||||
session: Session;
|
||||
setSession: (session: Session) => void;
|
||||
clearSession: () => void;
|
||||
session: Session;
|
||||
setSession: (session: Session) => void;
|
||||
clearSession: () => void;
|
||||
};
|
||||
|
||||
export type UserRoles = {
|
||||
userRoleId: string;
|
||||
userId: string;
|
||||
module: string;
|
||||
role: "systemAdmin" | "admin" | "manager" | "user" | "viewer";
|
||||
userRoleId: string;
|
||||
userId: string;
|
||||
module: string;
|
||||
role:
|
||||
| "viewer"
|
||||
| "technician"
|
||||
| "supervisor"
|
||||
| "manager"
|
||||
| "admin"
|
||||
| "systemAdmin";
|
||||
};
|
||||
|
||||
type UserRoleState = {
|
||||
userRoles: UserRoles[] | null;
|
||||
fetchRoles: () => Promise<void>;
|
||||
clearRoles: () => void;
|
||||
userRoles: UserRoles[] | null;
|
||||
fetchRoles: () => Promise<void>;
|
||||
clearRoles: () => void;
|
||||
};
|
||||
|
||||
// ---- ZUSTAND STORE ----
|
||||
export const useAuth = create<SessionState>((set) => ({
|
||||
session: null,
|
||||
setSession: (session) => set({ session }),
|
||||
clearSession: () => set({ session: null }),
|
||||
session: null,
|
||||
setSession: (session) => set({ session }),
|
||||
clearSession: () => set({ session: null }),
|
||||
}));
|
||||
|
||||
export const useUserRoles = create<UserRoleState>((set) => ({
|
||||
userRoles: null,
|
||||
fetchRoles: async () => {
|
||||
try {
|
||||
const res = await api.get("/api/user/roles");
|
||||
const roles = res.data;
|
||||
set({ userRoles: roles.data });
|
||||
} catch (err) {
|
||||
console.error("Error fetching roles:", err);
|
||||
set({ userRoles: null });
|
||||
}
|
||||
},
|
||||
clearRoles: () => set({ userRoles: null }),
|
||||
userRoles: null,
|
||||
fetchRoles: async () => {
|
||||
try {
|
||||
const res = await api.get("/api/user/roles");
|
||||
const roles = res.data;
|
||||
set({ userRoles: roles.data });
|
||||
} catch (err) {
|
||||
console.error("Error fetching roles:", err);
|
||||
set({ userRoles: null });
|
||||
}
|
||||
},
|
||||
clearRoles: () => set({ userRoles: null }),
|
||||
}));
|
||||
|
||||
export function userAccess(
|
||||
moduleName: string | null,
|
||||
roles: UserRoles["role"] | UserRoles["role"][]
|
||||
moduleName: string | null,
|
||||
roles: UserRoles["role"] | UserRoles["role"][],
|
||||
): boolean {
|
||||
const { userRoles } = useUserRoles();
|
||||
const { userRoles } = useUserRoles();
|
||||
|
||||
if (!userRoles) return false;
|
||||
if (!userRoles) return false;
|
||||
|
||||
const roleArray = Array.isArray(roles) ? roles : [roles];
|
||||
const roleArray = Array.isArray(roles) ? roles : [roles];
|
||||
|
||||
return userRoles.some(
|
||||
(m) =>
|
||||
(moduleName ? m.module === moduleName : true) &&
|
||||
roleArray.includes(m.role)
|
||||
);
|
||||
return userRoles.some(
|
||||
(m) =>
|
||||
(moduleName ? m.module === moduleName : true) &&
|
||||
roleArray.includes(m.role),
|
||||
);
|
||||
}
|
||||
|
||||
export async function checkUserAccess({
|
||||
allowedRoles,
|
||||
moduleName,
|
||||
allowedRoles,
|
||||
moduleName,
|
||||
}: {
|
||||
allowedRoles: UserRoles["role"][];
|
||||
moduleName?: string;
|
||||
//location: { pathname: string; search: string };
|
||||
allowedRoles: UserRoles["role"][];
|
||||
moduleName?: string;
|
||||
//location: { pathname: string; search: string };
|
||||
}) {
|
||||
try {
|
||||
// fetch roles from your API (credentials required)
|
||||
const res = await api.get("/api/user/roles", { withCredentials: true });
|
||||
const roles = res.data.data as UserRoles[];
|
||||
try {
|
||||
// fetch roles from your API (credentials required)
|
||||
const res = await api.get("/api/user/roles", { withCredentials: true });
|
||||
const roles = res.data.data as UserRoles[];
|
||||
|
||||
const hasAccess = roles.some(
|
||||
(r) =>
|
||||
(moduleName ? r.module === moduleName : true) &&
|
||||
allowedRoles.includes(r.role)
|
||||
);
|
||||
const hasAccess = roles.some(
|
||||
(r) =>
|
||||
(moduleName ? r.module === moduleName : true) &&
|
||||
allowedRoles.includes(r.role),
|
||||
);
|
||||
|
||||
if (!hasAccess) {
|
||||
throw redirect({
|
||||
to: "/",
|
||||
search: { from: location.pathname + location.search },
|
||||
});
|
||||
}
|
||||
if (!hasAccess) {
|
||||
throw redirect({
|
||||
to: "/",
|
||||
search: { from: location.pathname + location.search },
|
||||
});
|
||||
}
|
||||
|
||||
// return roles so the route component can use them if needed
|
||||
return roles;
|
||||
} catch {
|
||||
throw redirect({
|
||||
to: "/login",
|
||||
search: { redirect: location.pathname + location.search },
|
||||
});
|
||||
}
|
||||
// return roles so the route component can use them if needed
|
||||
return roles;
|
||||
} catch {
|
||||
throw redirect({
|
||||
to: "/login",
|
||||
search: { redirect: location.pathname + location.search },
|
||||
});
|
||||
}
|
||||
}
|
||||
// ---- BETTER AUTH CLIENT ----
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: `${window.location.origin}/lst/api/auth`,
|
||||
plugins: [usernameClient()],
|
||||
callbacks: {
|
||||
callbacks: {
|
||||
onUpdate: (res: any) => {
|
||||
// res has strong type
|
||||
// res.data is `Session | null`
|
||||
useAuth.getState().setSession(res?.data ?? null);
|
||||
},
|
||||
onSignIn: (res: any) => {
|
||||
console.log("Setting session to ", res?.data);
|
||||
useAuth.getState().setSession(res?.data ?? null);
|
||||
},
|
||||
onSignOut: () => {
|
||||
useAuth.getState().clearSession();
|
||||
},
|
||||
},
|
||||
},
|
||||
baseURL: `${window.location.origin}/lst/api/auth`,
|
||||
plugins: [usernameClient()],
|
||||
callbacks: {
|
||||
callbacks: {
|
||||
onUpdate: (res: any) => {
|
||||
// res has strong type
|
||||
// res.data is `Session | null`
|
||||
useAuth.getState().setSession(res?.data ?? null);
|
||||
},
|
||||
onSignIn: (res: any) => {
|
||||
console.log("Setting session to ", res?.data);
|
||||
useAuth.getState().setSession(res?.data ?? null);
|
||||
},
|
||||
onSignOut: () => {
|
||||
useAuth.getState().clearSession();
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// ---- AUTH API HELPERS ----
|
||||
export async function signin(data: { username: string; password: string }) {
|
||||
const res = await authClient.signIn.username(data);
|
||||
const res = await authClient.signIn.username(data);
|
||||
|
||||
if (res.error) throw res.error;
|
||||
await authClient.getSession();
|
||||
return res.data;
|
||||
if (res.error) throw res.error;
|
||||
await authClient.getSession();
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export const useLogout = () => {
|
||||
const { clearSession } = useAuth();
|
||||
const { clearRoles } = useUserRoles();
|
||||
const navigate = useNavigate();
|
||||
const { clearSession } = useAuth();
|
||||
const { clearRoles } = useUserRoles();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const router = useRouter();
|
||||
const logout = async () => {
|
||||
await authClient.signOut();
|
||||
const router = useRouter();
|
||||
const logout = async () => {
|
||||
await authClient.signOut();
|
||||
|
||||
router.invalidate();
|
||||
router.clearCache();
|
||||
clearSession();
|
||||
clearRoles();
|
||||
navigate({ to: "/" });
|
||||
window.location.reload();
|
||||
};
|
||||
router.invalidate();
|
||||
router.clearCache();
|
||||
clearSession();
|
||||
clearRoles();
|
||||
navigate({ to: "/" });
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return logout;
|
||||
return logout;
|
||||
};
|
||||
|
||||
export async function getSession() {
|
||||
const res = await authClient.getSession({
|
||||
query: { disableCookieCache: true },
|
||||
});
|
||||
if (res.error) return null;
|
||||
return res.data;
|
||||
const res = await authClient.getSession({
|
||||
query: { disableCookieCache: true },
|
||||
});
|
||||
if (res.error) return null;
|
||||
return res.data;
|
||||
}
|
||||
|
||||
// ---- REACT QUERY INTEGRATION ----
|
||||
export function useSession() {
|
||||
const { setSession, clearSession } = useAuth();
|
||||
const qc = useQueryClient();
|
||||
const { setSession, clearSession } = useAuth();
|
||||
const qc = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ["session"],
|
||||
queryFn: getSession,
|
||||
refetchInterval: 60_000,
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
const query = useQuery({
|
||||
queryKey: ["session"],
|
||||
queryFn: getSession,
|
||||
refetchInterval: 60_000,
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
|
||||
//console.log("Auth Check", query.data);
|
||||
// react to data change
|
||||
useEffect(() => {
|
||||
if (query.data !== undefined) {
|
||||
setSession(query.data);
|
||||
}
|
||||
}, [query.data, setSession]);
|
||||
//console.log("Auth Check", query.data);
|
||||
// react to data change
|
||||
useEffect(() => {
|
||||
if (query.data !== undefined) {
|
||||
setSession(query.data);
|
||||
}
|
||||
}, [query.data, setSession]);
|
||||
|
||||
// react to error
|
||||
useEffect(() => {
|
||||
if (query.error) {
|
||||
clearSession();
|
||||
qc.removeQueries({ queryKey: ["session"] });
|
||||
}
|
||||
}, [query.error, qc, clearSession]);
|
||||
// react to error
|
||||
useEffect(() => {
|
||||
if (query.error) {
|
||||
clearSession();
|
||||
qc.removeQueries({ queryKey: ["session"] });
|
||||
}
|
||||
}, [query.error, qc, clearSession]);
|
||||
|
||||
return query;
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
import { useRouter } from "@tanstack/react-router";
|
||||
import { useEffect } from "react";
|
||||
import { useModuleStore } from "../../routes/_old/old/-lib/store/useModuleStore";
|
||||
import { useSettingStore } from "../../routes/_old/old/-lib/store/useSettings";
|
||||
import { useSubModuleStore } from "../../routes/_old/old/-lib/store/useSubModuleStore";
|
||||
import { useSession, useUserRoles } from "../authClient";
|
||||
|
||||
export function SessionGuard({ children }: { children: React.ReactNode }) {
|
||||
const { data: session, isLoading } = useSession();
|
||||
const { fetchRoles } = useUserRoles();
|
||||
const { data: session, isLoading } = useSession();
|
||||
const { fetchRoles } = useUserRoles();
|
||||
const { fetchModules } = useModuleStore();
|
||||
const { fetchSettings } = useSettingStore();
|
||||
const { fetchSubModules } = useSubModuleStore();
|
||||
const router = useRouter();
|
||||
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
// if (!isLoading && !session) {
|
||||
// router.navigate({ to: "/" }); // redirect if not logged in
|
||||
// }
|
||||
fetchModules();
|
||||
fetchSettings();
|
||||
fetchSubModules();
|
||||
//if (session) {
|
||||
fetchRoles();
|
||||
//}
|
||||
}, [isLoading, session, router]);
|
||||
|
||||
useEffect(() => {
|
||||
// if (!isLoading && !session) {
|
||||
// router.navigate({ to: "/" }); // redirect if not logged in
|
||||
// }
|
||||
if (session) {
|
||||
fetchRoles();
|
||||
}
|
||||
}, [isLoading, session, router]);
|
||||
|
||||
if (isLoading) return <div>App Loading</div>;
|
||||
return <>{children}</>;
|
||||
if (isLoading) return <div>App Loading</div>;
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user