Add internal staff IAM: named users, roles, and management permissions.
Some checks are pending
offline / test (push) Waiting to run
Some checks are pending
offline / test (push) Waiting to run
verae-staff-iam (:3028) is the people directory — owner, billing-admin, cs, sales, accounting, operator, viewer — with scrypt passwords, sessions, and an audit log. Admin console login uses it when STAFF_IAM_URL is set and hides tabs the account cannot use. CS/sales/accounting/staff/fleet check permissions such as cs.credit and fleet.operate. Shared staff key remains only as a fallback when IAM is unset.
This commit is contained in:
parent
2740d51446
commit
d299d245e8
41 changed files with 1337 additions and 52 deletions
|
|
@ -92,11 +92,41 @@ function currentPeriod() {
|
|||
return new Date().toISOString().slice(0, 7);
|
||||
}
|
||||
|
||||
const TAB_PERM = {
|
||||
endpoints: 'admin.pricing',
|
||||
tiers: 'admin.tiers',
|
||||
customers: 'admin.customers',
|
||||
statement: 'admin.statement',
|
||||
invoices: 'admin.invoices',
|
||||
reports: 'admin.reports',
|
||||
system: 'admin.system',
|
||||
users: 'iam.users.read',
|
||||
};
|
||||
function can(perm) {
|
||||
const p = state.me?.permissions || ['*'];
|
||||
return p.includes('*') || p.includes(perm);
|
||||
}
|
||||
function applyNav() {
|
||||
document.querySelectorAll('aside nav button').forEach((btn) => {
|
||||
const need = TAB_PERM[btn.dataset.tab];
|
||||
btn.style.display = !need || can(need) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
async function load() {
|
||||
state.me = await api('/me').catch(() => ({ permissions: ['*'] }));
|
||||
applyNav();
|
||||
if (!can('admin.pricing') && !can('admin.tiers') && !can('admin.customers')) {
|
||||
if (state.me?.iamUrl) {
|
||||
document.getElementById('endpoints').innerHTML =
|
||||
`<h2>Staff IAM</h2><p class="lede">This console is for billing admins. Manage people at <a href="${state.me.iamUrl}" style="color:var(--accent)">${state.me.iamUrl}</a>.</p>`;
|
||||
}
|
||||
}
|
||||
if (!can('admin.pricing') && !can('admin.tiers')) return;
|
||||
state.pricing = await api('/pricing');
|
||||
state.customers = (await api('/customers')).customers;
|
||||
state.invoices = (await api('/invoices')).invoices;
|
||||
state.users = (await api('/users')).users;
|
||||
state.customers = can('admin.customers') ? (await api('/customers')).customers : [];
|
||||
state.invoices = can('admin.invoices') ? (await api('/invoices')).invoices : [];
|
||||
state.users = can('iam.users.read') || !state.me?.iam ? (await api('/users')).users : [];
|
||||
renderEndpoints();
|
||||
renderTiers();
|
||||
renderCustomers();
|
||||
|
|
@ -703,6 +733,16 @@ async function loadSystem() {
|
|||
/* ---------------- admin users ---------------- */
|
||||
|
||||
function renderUsers() {
|
||||
if (state.me?.iam) {
|
||||
document.getElementById('users').innerHTML = `
|
||||
<h2>Staff users</h2>
|
||||
<p class="lede">Named internal accounts and roles live in Staff IAM — not the billing-console seed table.</p>
|
||||
<div class="card">
|
||||
<p>Open <a class="btn" style="display:inline-block;text-decoration:none" href="${state.me.iamUrl || 'http://127.0.0.1:3028/'}">${state.me.iamUrl || 'http://127.0.0.1:3028/'}</a></p>
|
||||
<p class="hint">Roles: owner, iam-admin, billing-admin, cs, sales, accounting, operator, viewer.</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
const rows = state.users
|
||||
.slice()
|
||||
.sort((a, b) => a.username.localeCompare(b.username))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue