[feat](trx-frontend-http): separate transmit permission
CI / frontend (pull_request) Successful in 5m21s
CI / lint (pull_request) Successful in 2m24s
CI / test (pull_request) Successful in 9m11s
CI / test (push) Successful in 8m16s
CI / frontend (push) Successful in 4m24s
CI / reuse (push) Successful in 5s
CI / reuse (pull_request) Successful in 5s
CI / lint (push) Successful in 2m25s
CI / frontend (pull_request) Successful in 5m21s
CI / lint (pull_request) Successful in 2m24s
CI / test (pull_request) Successful in 9m11s
CI / test (push) Successful in 8m16s
CI / frontend (push) Successful in 4m24s
CI / reuse (push) Successful in 5s
CI / reuse (pull_request) Successful in 5s
CI / lint (push) Successful in 2m25s
Assisted-By: Codex (GPT-5) Signed-off-by: Stan Grams <sjg@haxx.space>
This commit was merged in pull request #64.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
// src/api/auth.ts
|
||||
var AUTH_ROLES = ["guest", "read", "control", "transmit", "write", "administrator"];
|
||||
var AUTH_ADMIN_ROLES = AUTH_ROLES.filter((role) => role !== "guest");
|
||||
var AUTH_ROLE_LABELS = {
|
||||
guest: "Guest",
|
||||
read: "Read",
|
||||
control: "Control",
|
||||
transmit: "Transmit",
|
||||
write: "Write",
|
||||
administrator: "Administrator"
|
||||
};
|
||||
function isAuthRole(value) {
|
||||
return typeof value === "string" && AUTH_ROLES.includes(value);
|
||||
}
|
||||
function normalizeAuthRoles(roles) {
|
||||
return AUTH_ROLES.filter((role) => roles.includes(role));
|
||||
}
|
||||
function hasAuthRole(roles, required) {
|
||||
return roles.includes("administrator") || roles.includes(required) || required === "read" && roles.includes("guest") || required === "read" && (roles.includes("control") || roles.includes("transmit"));
|
||||
}
|
||||
function hasAccountControls(roles) {
|
||||
return roles.length > 0 && !roles.includes("guest");
|
||||
}
|
||||
function decodeRoles(value, context) {
|
||||
if (!Array.isArray(value) || !value.every(isAuthRole)) {
|
||||
throw new TypeError(`${context} has invalid roles`);
|
||||
}
|
||||
return normalizeAuthRoles(value);
|
||||
}
|
||||
function decodeAuthSession(value) {
|
||||
if (typeof value !== "object" || value === null) {
|
||||
throw new TypeError("The authentication response is malformed");
|
||||
}
|
||||
const session = value;
|
||||
if (typeof session.authenticated !== "boolean") {
|
||||
throw new TypeError("The authentication response has no authenticated flag");
|
||||
}
|
||||
if (session.auth_disabled !== void 0 && typeof session.auth_disabled !== "boolean") {
|
||||
throw new TypeError("The authentication response has an invalid auth_disabled flag");
|
||||
}
|
||||
const decoded = {
|
||||
authenticated: session.authenticated,
|
||||
roles: decodeRoles(session.roles, "The authentication response")
|
||||
};
|
||||
if (session.username !== void 0) {
|
||||
if (typeof session.username !== "string") throw new TypeError("The authentication response has an invalid username");
|
||||
decoded.username = session.username;
|
||||
}
|
||||
if (session.auth_disabled !== void 0) decoded.auth_disabled = session.auth_disabled;
|
||||
return decoded;
|
||||
}
|
||||
var authDisabledSession = {
|
||||
authenticated: true,
|
||||
roles: [...AUTH_ADMIN_ROLES],
|
||||
auth_disabled: true
|
||||
};
|
||||
async function fetchAuthSession() {
|
||||
try {
|
||||
const response = await fetch("/auth/session");
|
||||
if (response.status === 404) return authDisabledSession;
|
||||
if (!response.ok) return { authenticated: false, roles: [] };
|
||||
return decodeAuthSession(await response.json());
|
||||
} catch (error) {
|
||||
console.error("Auth check failed:", error);
|
||||
return { authenticated: false, roles: [] };
|
||||
}
|
||||
}
|
||||
async function login(username, password) {
|
||||
const response = await fetch("/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
if (response.status === 404) return authDisabledSession;
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
throw new Error(message || "Login failed");
|
||||
}
|
||||
return decodeAuthSession(await response.json());
|
||||
}
|
||||
async function userRequest(path, init) {
|
||||
const response = await fetch(path, init);
|
||||
if (!response.ok) {
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
throw new Error(payload.error || `User operation failed (${response.status})`);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
async function listUsers() {
|
||||
const value = await userRequest("/auth/users").then((response) => response.json());
|
||||
if (!Array.isArray(value) || !value.every((user) => {
|
||||
if (typeof user !== "object" || user === null) return false;
|
||||
const record = user;
|
||||
return typeof record.username === "string" && typeof record.enabled === "boolean" && Array.isArray(record.roles) && record.roles.every(isAuthRole);
|
||||
})) {
|
||||
throw new TypeError("The user list response is malformed");
|
||||
}
|
||||
return value.map((user) => ({ ...user, roles: normalizeAuthRoles(user.roles) }));
|
||||
}
|
||||
async function createUser(username, password, roles, enabled = true) {
|
||||
await userRequest("/auth/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password, roles, enabled }) });
|
||||
}
|
||||
async function updateUser(username, changes) {
|
||||
await userRequest(`/auth/users/${encodeURIComponent(username)}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(changes) });
|
||||
}
|
||||
async function changeOwnPassword(currentPassword, newPassword) {
|
||||
await userRequest("/auth/account/password", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ current_password: currentPassword, new_password: newPassword })
|
||||
});
|
||||
}
|
||||
async function deleteUser(username) {
|
||||
await userRequest(`/auth/users/${encodeURIComponent(username)}`, { method: "DELETE" });
|
||||
}
|
||||
async function logout() {
|
||||
const response = await fetch("/auth/logout", { method: "POST" });
|
||||
if (response.status !== 404 && !response.ok) throw new Error("Logout failed");
|
||||
}
|
||||
|
||||
export {
|
||||
AUTH_ROLES,
|
||||
AUTH_ADMIN_ROLES,
|
||||
AUTH_ROLE_LABELS,
|
||||
normalizeAuthRoles,
|
||||
hasAuthRole,
|
||||
hasAccountControls,
|
||||
fetchAuthSession,
|
||||
login,
|
||||
listUsers,
|
||||
createUser,
|
||||
updateUser,
|
||||
changeOwnPassword,
|
||||
deleteUser,
|
||||
logout
|
||||
};
|
||||
Reference in New Issue
Block a user