feat(bus,webui): bus read of credential status; the Console shows it (#1549, row 60)
The credentials read returns service, account, role, date kind, date and state for the capability's business, to human and reader sessions only. It never returns a token, file path or variable name. Author Filbert, reviewers Dewey (27558) and Darkwing (27575), candidate manifest 13541657. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -86,12 +86,15 @@ export class Broker {
|
||||
#runs = new Map();
|
||||
#ended = new Set();
|
||||
#secretCheck;
|
||||
#credentialStatus;
|
||||
#clock = 0;
|
||||
constructor({ store, businesses, secretCheck = () => {} }) {
|
||||
// credentialStatus is the runtime's metadata-only Credentials.status(); the broker never gets use().
|
||||
constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [] }) {
|
||||
if (!object(businesses) || !Object.keys(businesses).length) fail('invalid-business');
|
||||
this.#businesses = structuredClone(businesses);
|
||||
this.#store = store;
|
||||
this.#secretCheck = secretCheck;
|
||||
this.#credentialStatus = credentialStatus;
|
||||
for (const table of [
|
||||
'events',
|
||||
'role_claims',
|
||||
@@ -489,7 +492,7 @@ export class Broker {
|
||||
if (!object(args)) fail('invalid-request');
|
||||
this.#secretCheck(args);
|
||||
return this.#store.transaction(() => {
|
||||
if (s.reader && !['inbox', 'agents', 'tasks', 'trail', 'decisions'].includes(request.verb))
|
||||
if (s.reader && !['inbox', 'agents', 'tasks', 'trail', 'decisions', 'credentials'].includes(request.verb))
|
||||
fail('read-only');
|
||||
const result = this.#dispatch(s, request.verb, args);
|
||||
this.#secretCheck(result);
|
||||
@@ -875,6 +878,28 @@ export class Broker {
|
||||
.map((d) => ({ ...this.#decisionView(d), closed: this.#closed(d.id) }));
|
||||
fail('invalid-request');
|
||||
}
|
||||
// Row 60 (#1549): what the running host loaded, checked against its clock, for the session's
|
||||
// business. Service, account, role, date and state only; no token, file path or variable name.
|
||||
case 'credentials': {
|
||||
if (!s.human && !s.reader) fail('human-required');
|
||||
keys(a, [], []);
|
||||
const prefix = s.business + '/';
|
||||
return this.#credentialStatus()
|
||||
.filter((c) => c.instance.startsWith(prefix))
|
||||
.map(({ instance, service, date, state }) => {
|
||||
const role = instance.slice(prefix.length),
|
||||
sync = role === '@sync',
|
||||
bot = sync ? b.tracker?.sync?.bot : service === 'vikunja' ? b.roles?.[role]?.tracker?.bot : null;
|
||||
return {
|
||||
service,
|
||||
account: bot ?? null,
|
||||
role: sync ? 'tracker sync' : role,
|
||||
dateKind: service === 'gitea' ? 'rotateBy' : 'expires',
|
||||
date,
|
||||
state,
|
||||
};
|
||||
});
|
||||
}
|
||||
default:
|
||||
fail('unknown-verb');
|
||||
}
|
||||
|
||||
@@ -31,7 +31,12 @@ export async function startBroker({
|
||||
];
|
||||
credentials = new Credentials({ references, repoRoots: protectedRoots, dataRoot });
|
||||
store = new Store(dataRoot);
|
||||
const broker = new Broker({ store, businesses, secretCheck: (value) => credentials.assertClean(value) });
|
||||
const broker = new Broker({
|
||||
store,
|
||||
businesses,
|
||||
secretCheck: (value) => credentials.assertClean(value),
|
||||
credentialStatus: () => credentials.status(),
|
||||
});
|
||||
// Process identities must be supplied for every managed run when using the human transport.
|
||||
const launchRecords = [];
|
||||
function bindLaunch(r) {
|
||||
|
||||
@@ -6,5 +6,6 @@ export function views(client) {
|
||||
agents: () => client.call('agents'),
|
||||
trail: (subject) => client.call('trail', { subject }),
|
||||
decisions: (list) => client.call('decisions', { list }),
|
||||
credentials: () => client.call('credentials'),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user