mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 17:33:16 +00:00
- Add IAM v2 auth with role_id, is_owner, status checks, and API key without tenant_admin bypass - Enforce CRUD permissions and project/warehouse scope across API routes - Rewrite usuarios.vue with ribbon, tree, user/role management, and permission matrix - Add dynamic menu, route guards, usePermissions/useScope composables - Apply role templates on create; filter project docs by category; hide cost tab without permission - Add iam_test unit tests; update bootstrap and SaaS tenant admin to use role_id Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { displayErrorMessage, isApiEnvelope, unwrapApiPayload } from "./api-response.ts";
|
|
|
|
export function useApi() {
|
|
async function api<T>(path: string, opts: RequestInit & { json?: unknown } = {}): Promise<T> {
|
|
const headers = new Headers(opts.headers);
|
|
if (opts.json !== undefined) headers.set("Content-Type", "application/json");
|
|
const res = await fetch(path, {
|
|
...opts,
|
|
headers,
|
|
credentials: "include",
|
|
body: opts.json !== undefined ? JSON.stringify(opts.json) : opts.body,
|
|
});
|
|
const ct = res.headers.get("content-type") || "";
|
|
const isJson = ct.includes("application/json");
|
|
const raw = isJson ? await res.json().catch(() => ({})) : null;
|
|
if (!res.ok) {
|
|
const errBody = isApiEnvelope(raw) ? raw : (raw as { error?: string } | null);
|
|
throw Object.assign(
|
|
new Error(displayErrorMessage(errBody, res.statusText)),
|
|
{ status: res.status, data: errBody },
|
|
);
|
|
}
|
|
if (isJson) return unwrapApiPayload<T>(raw);
|
|
return (await res.blob()) as T;
|
|
}
|
|
async function download(path: string, filename: string) {
|
|
const blob = await api<Blob>(path);
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.rel = "noopener";
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
window.setTimeout(() => URL.revokeObjectURL(url), 2000);
|
|
}
|
|
return { api, download };
|
|
}
|
|
|
|
export const useAuth = () => {
|
|
const user = useState<{
|
|
id: number;
|
|
username: string;
|
|
display_name: string;
|
|
company_id?: number | null;
|
|
company_code?: string | null;
|
|
company_name?: string | null;
|
|
company_kind?: string | null;
|
|
tenant_id?: number | null;
|
|
role_id?: number | null;
|
|
role_code?: string;
|
|
is_owner?: boolean;
|
|
status?: string;
|
|
realm?: "app" | "platform";
|
|
must_change_password?: boolean;
|
|
} | null>("auth-user", () => null);
|
|
const { api } = useApi();
|
|
async function fetchMe() {
|
|
try {
|
|
const r = await api<{ user: NonNullable<typeof user.value> }>("/v1/auth/me");
|
|
user.value = r.user;
|
|
} catch {
|
|
user.value = null;
|
|
}
|
|
}
|
|
async function login(username: string, password: string) {
|
|
const r = await api<{ user: NonNullable<typeof user.value> }>("/v1/auth/login", {
|
|
method: "POST",
|
|
json: { username, password },
|
|
});
|
|
user.value = r.user;
|
|
}
|
|
async function logout() {
|
|
await api("/v1/auth/logout", { method: "POST" });
|
|
user.value = null;
|
|
await navigateTo("/login");
|
|
}
|
|
return { user, fetchMe, login, logout };
|
|
};
|