134 lines
4.2 KiB
JavaScript
134 lines
4.2 KiB
JavaScript
import { createContext, useContext, useState, useEffect } from "react";
|
|
import api from "../api/client";
|
|
|
|
const AuthContext = createContext(null);
|
|
|
|
export function AuthProvider({ children }) {
|
|
const [user, setUser] = useState(() => {
|
|
const stored = localStorage.getItem("user");
|
|
return stored ? JSON.parse(stored) : null;
|
|
});
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const token = localStorage.getItem("access_token");
|
|
if (!token) {
|
|
setUser(null);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
if (payload.exp * 1000 < Date.now()) {
|
|
localStorage.removeItem("access_token");
|
|
localStorage.removeItem("user");
|
|
setUser(null);
|
|
}
|
|
} catch {
|
|
localStorage.removeItem("access_token");
|
|
localStorage.removeItem("user");
|
|
setUser(null);
|
|
}
|
|
setLoading(false);
|
|
}, []);
|
|
|
|
const login = async (email, password) => {
|
|
const data = await api.post("/auth/login", { email, password });
|
|
localStorage.setItem("access_token", data.access_token);
|
|
const userInfo = {
|
|
name: data.name,
|
|
role: data.role,
|
|
permissions: data.permissions || null,
|
|
};
|
|
localStorage.setItem("user", JSON.stringify(userInfo));
|
|
setUser(userInfo);
|
|
|
|
// Fetch full profile from /staff/me for up-to-date permissions
|
|
try {
|
|
const me = await api.get("/staff/me");
|
|
if (me.permissions) {
|
|
const updated = { ...userInfo, permissions: me.permissions };
|
|
localStorage.setItem("user", JSON.stringify(updated));
|
|
setUser(updated);
|
|
}
|
|
} catch {
|
|
// Non-critical, permissions from login response are used
|
|
}
|
|
|
|
return data;
|
|
};
|
|
|
|
const logout = () => {
|
|
localStorage.removeItem("access_token");
|
|
localStorage.removeItem("user");
|
|
setUser(null);
|
|
};
|
|
|
|
const hasRole = (...roles) => {
|
|
if (!user) return false;
|
|
if (user.role === "sysadmin") return true;
|
|
return roles.includes(user.role);
|
|
};
|
|
|
|
/**
|
|
* hasPermission(section, action)
|
|
*
|
|
* Sections and their action keys:
|
|
* melodies: view, add, delete, safe_edit, full_edit, archetype_access, settings_access, compose_access
|
|
* devices: view, add, delete, safe_edit, edit_bells, edit_clock, edit_warranty, full_edit, control
|
|
* app_users: view, add, delete, safe_edit, full_edit
|
|
* issues_notes: view, add, delete, edit
|
|
* mail: view, compose, reply
|
|
* crm: activity_log
|
|
* crm_customers: full_access, overview, orders_view, orders_edit, quotations_view, quotations_edit,
|
|
* comms_view, comms_log, comms_edit, comms_compose, add, delete,
|
|
* files_view, files_edit, devices_view, devices_edit
|
|
* crm_orders: view (→ crm_customers.orders_view), edit (→ crm_customers.orders_edit) [derived]
|
|
* crm_products: view, add, edit
|
|
* mfg: view_inventory, edit, provision, firmware_view, firmware_edit
|
|
* api_reference: access
|
|
* mqtt: access
|
|
*/
|
|
const hasPermission = (section, action) => {
|
|
if (!user) return false;
|
|
// sysadmin and admin have full access
|
|
if (user.role === "sysadmin" || user.role === "admin") return true;
|
|
|
|
const perms = user.permissions;
|
|
if (!perms) return false;
|
|
|
|
// crm_orders is derived from crm_customers
|
|
if (section === "crm_orders") {
|
|
const cc = perms.crm_customers;
|
|
if (!cc) return false;
|
|
if (cc.full_access) return true;
|
|
if (action === "view") return !!cc.orders_view;
|
|
if (action === "edit") return !!cc.orders_edit;
|
|
return false;
|
|
}
|
|
|
|
const sectionPerms = perms[section];
|
|
if (!sectionPerms) return false;
|
|
|
|
// crm_customers.full_access grants everything in that section
|
|
if (section === "crm_customers" && sectionPerms.full_access) return true;
|
|
|
|
return !!sectionPerms[action];
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, login, logout, loading, hasRole, hasPermission }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error("useAuth must be used within an AuthProvider");
|
|
}
|
|
return context;
|
|
}
|