@adonis-agora/authkit-server 0.78.0 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/agents/config.d.ts +2 -0
- package/build/src/agents/config.js +3 -0
- package/build/src/agents/delegation_store.d.ts +16 -0
- package/build/src/agents/delegation_store.js +44 -0
- package/build/src/agents/poppy/config.d.ts +3 -0
- package/build/src/agents/poppy/config.js +7 -0
- package/build/src/agents/poppy/service.d.ts +6 -0
- package/build/src/agents/poppy/service.js +12 -0
- package/build/src/agents/poppy/store.d.ts +2 -0
- package/build/src/agents/poppy/store.js +9 -0
- package/build/src/agents/registry_store.d.ts +41 -0
- package/build/src/agents/registry_store.js +138 -0
- package/build/src/agents/runtime.d.ts +13 -0
- package/build/src/agents/runtime.js +87 -5
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/audit/audit_sink.js +6 -0
- package/build/src/host/admin_api/dto.d.ts +1 -1
- package/build/src/host/admin_console/admin_shell_controller.js +7 -0
- package/build/src/host/admin_console/console_personal_agents_controller.d.ts +83 -0
- package/build/src/host/admin_console/console_personal_agents_controller.js +337 -0
- package/build/src/host/register_auth_host.js +16 -0
- package/build/src/host/ui-dist/assets/index-LrScaR9C.js +155 -0
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/schema/ensure.js +28 -0
- package/package.json +2 -2
- package/build/src/host/ui-dist/assets/index-CIqtoNcO.js +0 -155
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import '../augmentations.js';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
+
import type { AgentProtocolId } from '../../agents/registry_store.js';
|
|
4
|
+
type ListStatus = 'allowed' | 'blocked';
|
|
5
|
+
interface AgentEntry {
|
|
6
|
+
protocol: AgentProtocolId;
|
|
7
|
+
identifier: string;
|
|
8
|
+
name: string;
|
|
9
|
+
/** O que o `defineConfig` diz (só listas estáticas); `null` = nada. */
|
|
10
|
+
config: ListStatus | null;
|
|
11
|
+
/** O que o console diz; `null` = nada. */
|
|
12
|
+
console: {
|
|
13
|
+
id: string;
|
|
14
|
+
status: ListStatus;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
updatedBy: string | null;
|
|
17
|
+
} | null;
|
|
18
|
+
/** Resultado: bloqueado em qualquer lado vence; senão liberado em qualquer lado; senão `unlisted`. */
|
|
19
|
+
effective: ListStatus | 'unlisted';
|
|
20
|
+
jwksUri: string | null;
|
|
21
|
+
/** Conexões vivas (grants não revogados nem expirados). */
|
|
22
|
+
connections: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Endpoints JSON de personal agents (PACT e Poppy) do console admin React.
|
|
26
|
+
*
|
|
27
|
+
* GET {prefix}/api/personal-agents → agentes (config + console + conectados)
|
|
28
|
+
* GET {prefix}/api/personal-agents/connections?clientId=&accountId=&protocol=&page=&size=
|
|
29
|
+
* POST {prefix}/api/personal-agents/connections/:grantId/revoke (sudo)
|
|
30
|
+
* POST {prefix}/api/personal-agents/registry → libera/bloqueia (sudo)
|
|
31
|
+
* DELETE {prefix}/api/personal-agents/registry/:id → tira do registro do console (sudo)
|
|
32
|
+
*
|
|
33
|
+
* 404 `capability_unsupported` quando `personalAgents` não está configurado ou não há banco.
|
|
34
|
+
*/
|
|
35
|
+
export default class ConsolePersonalAgentsController {
|
|
36
|
+
/** GET {prefix}/api/personal-agents */
|
|
37
|
+
index(ctx: HttpContext): Promise<void | {
|
|
38
|
+
protocols: {
|
|
39
|
+
pact: {
|
|
40
|
+
open: boolean;
|
|
41
|
+
/** A lista do config é uma função do host (o console não enxerga). */
|
|
42
|
+
hostResolver: boolean;
|
|
43
|
+
} | null;
|
|
44
|
+
poppy: {
|
|
45
|
+
requireRegistration: boolean;
|
|
46
|
+
hostResolver: boolean;
|
|
47
|
+
allowInsecureClientIds: boolean;
|
|
48
|
+
} | null;
|
|
49
|
+
};
|
|
50
|
+
agents: AgentEntry[];
|
|
51
|
+
}>;
|
|
52
|
+
/** GET {prefix}/api/personal-agents/connections */
|
|
53
|
+
connections(ctx: HttpContext): Promise<void | {
|
|
54
|
+
meta: {
|
|
55
|
+
page: number;
|
|
56
|
+
size: number;
|
|
57
|
+
total: number;
|
|
58
|
+
};
|
|
59
|
+
data: {
|
|
60
|
+
id: string;
|
|
61
|
+
protocol: AgentProtocolId;
|
|
62
|
+
clientId: string;
|
|
63
|
+
agentName: string;
|
|
64
|
+
accountId: string;
|
|
65
|
+
accountEmail: string | null;
|
|
66
|
+
scopes: string[];
|
|
67
|
+
createdAt: string;
|
|
68
|
+
updatedAt: string;
|
|
69
|
+
expiresAt: string;
|
|
70
|
+
}[];
|
|
71
|
+
}>;
|
|
72
|
+
/** POST {prefix}/api/personal-agents/connections/:grantId/revoke */
|
|
73
|
+
revokeConnection(ctx: HttpContext): Promise<void | {}>;
|
|
74
|
+
/**
|
|
75
|
+
* POST {prefix}/api/personal-agents/registry
|
|
76
|
+
* body: `{ protocol, identifier, status: 'allowed'|'blocked', name?, jwksUri? }`.
|
|
77
|
+
* Bloquear também desconecta o agente de todas as contas, na hora.
|
|
78
|
+
*/
|
|
79
|
+
upsert(ctx: HttpContext): Promise<void | {}>;
|
|
80
|
+
/** DELETE {prefix}/api/personal-agents/registry/:id */
|
|
81
|
+
destroy(ctx: HttpContext): Promise<void | {}>;
|
|
82
|
+
}
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import '../augmentations.js';
|
|
2
|
+
import { displayNameFor } from '../../agents/agent_identity.js';
|
|
3
|
+
import { parseScope } from '../../agents/config.js';
|
|
4
|
+
import { personalAgentsFor } from '../../agents/runtime.js';
|
|
5
|
+
import { parseListPage, parseListSize } from '../../pagination.js';
|
|
6
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
7
|
+
import { apiError } from '../admin_api/dto.js';
|
|
8
|
+
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
9
|
+
import { requireSudo } from '../sudo_mode.js';
|
|
10
|
+
/** Mesmo gate de sudo das outras ações destrutivas do console (sessões, chaves). */
|
|
11
|
+
async function gateSudo(ctx) {
|
|
12
|
+
const settings = await resolveRuntimeSettings(ctx);
|
|
13
|
+
const result = await requireSudo(ctx, settings);
|
|
14
|
+
if (result === true)
|
|
15
|
+
return null;
|
|
16
|
+
return ctx.response
|
|
17
|
+
.status(403)
|
|
18
|
+
.send(apiError('sudo_required', 'Identity confirmation required.'));
|
|
19
|
+
}
|
|
20
|
+
function notSupported(ctx) {
|
|
21
|
+
return ctx.response.notFound(apiError('capability_unsupported', 'Personal agents não estão configurados (`personalAgents` no defineConfig) ou não há banco.'));
|
|
22
|
+
}
|
|
23
|
+
/** O runtime com banco, ou `null` (404 honesto). */
|
|
24
|
+
async function runtimeFor(ctx) {
|
|
25
|
+
const runtime = await personalAgentsFor(ctx);
|
|
26
|
+
if (!runtime?.registry || !runtime.grants)
|
|
27
|
+
return null;
|
|
28
|
+
return runtime;
|
|
29
|
+
}
|
|
30
|
+
function enabledProtocols(runtime) {
|
|
31
|
+
const out = [];
|
|
32
|
+
if (runtime.config.pact)
|
|
33
|
+
out.push('pact');
|
|
34
|
+
if (runtime.config.poppy)
|
|
35
|
+
out.push('poppy');
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function isUrl(value, allowHttp) {
|
|
39
|
+
if (typeof value !== 'string' || value.length > 2048)
|
|
40
|
+
return false;
|
|
41
|
+
try {
|
|
42
|
+
const url = new URL(value);
|
|
43
|
+
return ((url.protocol === 'https:' || (allowHttp && url.protocol === 'http:')) &&
|
|
44
|
+
!url.hash &&
|
|
45
|
+
!url.username &&
|
|
46
|
+
!url.password);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function agentName(runtime, protocol, identifier, fallback) {
|
|
53
|
+
if (fallback)
|
|
54
|
+
return fallback;
|
|
55
|
+
if (protocol === 'poppy' && runtime.poppy) {
|
|
56
|
+
return runtime.poppy.registry.displayName(identifier);
|
|
57
|
+
}
|
|
58
|
+
const registration = await runtime.config.resolveAgent(identifier);
|
|
59
|
+
return displayNameFor(registration ?? { issuer: identifier, jwksUri: '' });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Endpoints JSON de personal agents (PACT e Poppy) do console admin React.
|
|
63
|
+
*
|
|
64
|
+
* GET {prefix}/api/personal-agents → agentes (config + console + conectados)
|
|
65
|
+
* GET {prefix}/api/personal-agents/connections?clientId=&accountId=&protocol=&page=&size=
|
|
66
|
+
* POST {prefix}/api/personal-agents/connections/:grantId/revoke (sudo)
|
|
67
|
+
* POST {prefix}/api/personal-agents/registry → libera/bloqueia (sudo)
|
|
68
|
+
* DELETE {prefix}/api/personal-agents/registry/:id → tira do registro do console (sudo)
|
|
69
|
+
*
|
|
70
|
+
* 404 `capability_unsupported` quando `personalAgents` não está configurado ou não há banco.
|
|
71
|
+
*/
|
|
72
|
+
export default class ConsolePersonalAgentsController {
|
|
73
|
+
/** GET {prefix}/api/personal-agents */
|
|
74
|
+
async index(ctx) {
|
|
75
|
+
const runtime = await runtimeFor(ctx);
|
|
76
|
+
if (!runtime)
|
|
77
|
+
return notSupported(ctx);
|
|
78
|
+
const cfg = runtime.config;
|
|
79
|
+
const now = new Date();
|
|
80
|
+
const entries = new Map();
|
|
81
|
+
const keyOf = (p, id) => `${p} ${id}`;
|
|
82
|
+
const entry = (protocol, identifier) => {
|
|
83
|
+
const k = keyOf(protocol, identifier);
|
|
84
|
+
let e = entries.get(k);
|
|
85
|
+
if (!e) {
|
|
86
|
+
e = {
|
|
87
|
+
protocol,
|
|
88
|
+
identifier,
|
|
89
|
+
name: '',
|
|
90
|
+
config: null,
|
|
91
|
+
console: null,
|
|
92
|
+
effective: 'unlisted',
|
|
93
|
+
jwksUri: null,
|
|
94
|
+
connections: 0,
|
|
95
|
+
};
|
|
96
|
+
entries.set(k, e);
|
|
97
|
+
}
|
|
98
|
+
return e;
|
|
99
|
+
};
|
|
100
|
+
const names = new Map();
|
|
101
|
+
if (cfg.pact) {
|
|
102
|
+
for (const agent of cfg.staticAgents ?? []) {
|
|
103
|
+
const e = entry('pact', agent.issuer);
|
|
104
|
+
e.config = agent.enabled === false ? 'blocked' : 'allowed';
|
|
105
|
+
e.jwksUri = agent.jwksUri;
|
|
106
|
+
names.set(keyOf('pact', agent.issuer), agent.name ?? null);
|
|
107
|
+
}
|
|
108
|
+
for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, 'pact')) {
|
|
109
|
+
entry('pact', clientId).connections = count;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (cfg.poppy) {
|
|
113
|
+
for (const id of cfg.poppy.clients.registeredList ?? [])
|
|
114
|
+
entry('poppy', id).config = 'allowed';
|
|
115
|
+
for (const id of cfg.poppy.clients.blockedList ?? [])
|
|
116
|
+
entry('poppy', id).config = 'blocked';
|
|
117
|
+
for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, 'poppy')) {
|
|
118
|
+
entry('poppy', clientId).connections = count;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const row of await runtime.registry.list()) {
|
|
122
|
+
if (!enabledProtocols(runtime).includes(row.protocol))
|
|
123
|
+
continue;
|
|
124
|
+
const e = entry(row.protocol, row.identifier);
|
|
125
|
+
e.console = {
|
|
126
|
+
id: row.id,
|
|
127
|
+
status: row.status,
|
|
128
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
129
|
+
updatedBy: row.updatedBy,
|
|
130
|
+
};
|
|
131
|
+
if (row.jwksUri)
|
|
132
|
+
e.jwksUri = row.jwksUri;
|
|
133
|
+
if (row.name)
|
|
134
|
+
names.set(keyOf(row.protocol, row.identifier), row.name);
|
|
135
|
+
}
|
|
136
|
+
const agents = await Promise.all([...entries.values()].map(async (e) => {
|
|
137
|
+
const statuses = [e.config, e.console?.status];
|
|
138
|
+
e.effective = statuses.includes('blocked')
|
|
139
|
+
? 'blocked'
|
|
140
|
+
: statuses.includes('allowed')
|
|
141
|
+
? 'allowed'
|
|
142
|
+
: 'unlisted';
|
|
143
|
+
e.name = await agentName(runtime, e.protocol, e.identifier, names.get(keyOf(e.protocol, e.identifier)) ?? null);
|
|
144
|
+
return e;
|
|
145
|
+
}));
|
|
146
|
+
agents.sort((a, b) => b.connections - a.connections || a.name.localeCompare(b.name));
|
|
147
|
+
return {
|
|
148
|
+
protocols: {
|
|
149
|
+
pact: cfg.pact
|
|
150
|
+
? {
|
|
151
|
+
open: cfg.open,
|
|
152
|
+
/** A lista do config é uma função do host (o console não enxerga). */
|
|
153
|
+
hostResolver: cfg.staticAgents === null,
|
|
154
|
+
}
|
|
155
|
+
: null,
|
|
156
|
+
poppy: cfg.poppy
|
|
157
|
+
? {
|
|
158
|
+
requireRegistration: cfg.poppy.clients.requireRegistration,
|
|
159
|
+
hostResolver: cfg.poppy.clients.registeredList === null || cfg.poppy.clients.blockedList === null,
|
|
160
|
+
allowInsecureClientIds: cfg.poppy.allowInsecureClientIds,
|
|
161
|
+
}
|
|
162
|
+
: null,
|
|
163
|
+
},
|
|
164
|
+
agents,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** GET {prefix}/api/personal-agents/connections */
|
|
168
|
+
async connections(ctx) {
|
|
169
|
+
const runtime = await runtimeFor(ctx);
|
|
170
|
+
if (!runtime)
|
|
171
|
+
return notSupported(ctx);
|
|
172
|
+
const qs = ctx.request.qs();
|
|
173
|
+
const page = parseListPage(qs.page);
|
|
174
|
+
const size = parseListSize(qs.size);
|
|
175
|
+
const protocol = qs.protocol === 'pact' || qs.protocol === 'poppy'
|
|
176
|
+
? qs.protocol
|
|
177
|
+
: undefined;
|
|
178
|
+
const filter = {
|
|
179
|
+
protocol,
|
|
180
|
+
clientId: qs.clientId?.trim() || undefined,
|
|
181
|
+
accountId: qs.accountId?.trim() || undefined,
|
|
182
|
+
};
|
|
183
|
+
const now = new Date();
|
|
184
|
+
const [total, rows] = await Promise.all([
|
|
185
|
+
runtime.grants.countLiveGrants(now, filter),
|
|
186
|
+
runtime.grants.listLiveGrants({ ...filter, now, offset: (page - 1) * size, limit: size }),
|
|
187
|
+
]);
|
|
188
|
+
const store = (await ctx.containerResolver.make('authkit.server')).config.accountStore;
|
|
189
|
+
const emails = new Map();
|
|
190
|
+
const nameCache = new Map();
|
|
191
|
+
const data = await Promise.all(rows.map(async (g) => {
|
|
192
|
+
const rowProtocol = g.id.startsWith('pgrant_') ? 'poppy' : 'pact';
|
|
193
|
+
if (!emails.has(g.accountId)) {
|
|
194
|
+
const account = await store.findById(g.accountId).catch(() => null);
|
|
195
|
+
emails.set(g.accountId, account?.email ?? null);
|
|
196
|
+
}
|
|
197
|
+
const k = `${rowProtocol} ${g.clientId}`;
|
|
198
|
+
if (!nameCache.has(k))
|
|
199
|
+
nameCache.set(k, agentName(runtime, rowProtocol, g.clientId, null));
|
|
200
|
+
return {
|
|
201
|
+
id: g.id,
|
|
202
|
+
protocol: rowProtocol,
|
|
203
|
+
clientId: g.clientId,
|
|
204
|
+
agentName: await nameCache.get(k),
|
|
205
|
+
accountId: g.accountId,
|
|
206
|
+
accountEmail: emails.get(g.accountId) ?? null,
|
|
207
|
+
scopes: parseScope(g.scope),
|
|
208
|
+
createdAt: g.createdAt.toISOString(),
|
|
209
|
+
updatedAt: g.updatedAt.toISOString(),
|
|
210
|
+
expiresAt: g.expiresAt.toISOString(),
|
|
211
|
+
};
|
|
212
|
+
}));
|
|
213
|
+
return { meta: { page, size, total }, data };
|
|
214
|
+
}
|
|
215
|
+
/** POST {prefix}/api/personal-agents/connections/:grantId/revoke */
|
|
216
|
+
async revokeConnection(ctx) {
|
|
217
|
+
const runtime = await runtimeFor(ctx);
|
|
218
|
+
if (!runtime)
|
|
219
|
+
return notSupported(ctx);
|
|
220
|
+
const denied = await gateSudo(ctx);
|
|
221
|
+
if (denied)
|
|
222
|
+
return denied;
|
|
223
|
+
const grantId = ctx.request.param('grantId');
|
|
224
|
+
const grant = await runtime.revokeGrantById(grantId);
|
|
225
|
+
if (!grant) {
|
|
226
|
+
return ctx.response.notFound(apiError('not_found', 'Conexão não encontrada ou já revogada.'));
|
|
227
|
+
}
|
|
228
|
+
const cfg = (await ctx.containerResolver.make('authkit.server')).config;
|
|
229
|
+
await cfg.audit?.record({
|
|
230
|
+
type: 'agent.grant_revoked_by_admin',
|
|
231
|
+
accountId: grant.accountId,
|
|
232
|
+
clientId: grant.clientId,
|
|
233
|
+
ip: ctx.request.ip?.() ?? null,
|
|
234
|
+
metadata: { grantId, actor: ctx.session.get(ACCOUNT_SESSION_KEY) ?? null },
|
|
235
|
+
});
|
|
236
|
+
return { revoked: true };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* POST {prefix}/api/personal-agents/registry
|
|
240
|
+
* body: `{ protocol, identifier, status: 'allowed'|'blocked', name?, jwksUri? }`.
|
|
241
|
+
* Bloquear também desconecta o agente de todas as contas, na hora.
|
|
242
|
+
*/
|
|
243
|
+
async upsert(ctx) {
|
|
244
|
+
const runtime = await runtimeFor(ctx);
|
|
245
|
+
if (!runtime)
|
|
246
|
+
return notSupported(ctx);
|
|
247
|
+
const body = (ctx.request.body() ?? {});
|
|
248
|
+
const invalid = (message) => ctx.response.unprocessableEntity(apiError('invalid_request', message));
|
|
249
|
+
const protocol = body.protocol;
|
|
250
|
+
if (!enabledProtocols(runtime).includes(protocol)) {
|
|
251
|
+
return invalid('`protocol` precisa ser um protocolo ligado (`pact` ou `poppy`).');
|
|
252
|
+
}
|
|
253
|
+
const status = body.status;
|
|
254
|
+
if (status !== 'allowed' && status !== 'blocked') {
|
|
255
|
+
return invalid('`status` precisa ser `allowed` ou `blocked`.');
|
|
256
|
+
}
|
|
257
|
+
const allowHttp = protocol === 'poppy' && !!runtime.config.poppy?.allowInsecureClientIds;
|
|
258
|
+
const identifier = typeof body.identifier === 'string' ? body.identifier.trim() : '';
|
|
259
|
+
if (!isUrl(identifier, allowHttp)) {
|
|
260
|
+
return invalid(protocol === 'poppy'
|
|
261
|
+
? '`identifier` precisa ser o `client_id` do agente (URL HTTPS do documento de metadata).'
|
|
262
|
+
: '`identifier` precisa ser o `iss` do agente (URL HTTPS).');
|
|
263
|
+
}
|
|
264
|
+
const name = typeof body.name === 'string' && body.name.trim() ? body.name.trim().slice(0, 200) : null;
|
|
265
|
+
let jwksUri = null;
|
|
266
|
+
if (protocol === 'pact') {
|
|
267
|
+
const raw = typeof body.jwksUri === 'string' ? body.jwksUri.trim() : '';
|
|
268
|
+
if (raw && !isUrl(raw, false))
|
|
269
|
+
return invalid('`jwksUri` precisa ser uma URL HTTPS.');
|
|
270
|
+
jwksUri = raw || null;
|
|
271
|
+
if (status === 'allowed' && !jwksUri) {
|
|
272
|
+
return invalid('Liberar um agente PACT exige o `jwksUri` dele.');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const denied = await gateSudo(ctx);
|
|
276
|
+
if (denied)
|
|
277
|
+
return denied;
|
|
278
|
+
const actor = ctx.session.get(ACCOUNT_SESSION_KEY) ?? null;
|
|
279
|
+
const row = await runtime.registry.upsert({
|
|
280
|
+
protocol,
|
|
281
|
+
identifier,
|
|
282
|
+
status,
|
|
283
|
+
name,
|
|
284
|
+
jwksUri,
|
|
285
|
+
updatedBy: actor,
|
|
286
|
+
});
|
|
287
|
+
const disconnected = status === 'blocked' ? await runtime.disconnectAgent(protocol, identifier) : 0;
|
|
288
|
+
const cfg = (await ctx.containerResolver.make('authkit.server')).config;
|
|
289
|
+
const event = {
|
|
290
|
+
clientId: identifier,
|
|
291
|
+
ip: ctx.request.ip?.() ?? null,
|
|
292
|
+
metadata: { protocol, actor, disconnected },
|
|
293
|
+
};
|
|
294
|
+
if (status === 'blocked')
|
|
295
|
+
await cfg.audit?.record({ type: 'agent.blocked', ...event });
|
|
296
|
+
else
|
|
297
|
+
await cfg.audit?.record({ type: 'agent.allowed', ...event });
|
|
298
|
+
return {
|
|
299
|
+
entry: {
|
|
300
|
+
id: row.id,
|
|
301
|
+
protocol: row.protocol,
|
|
302
|
+
identifier: row.identifier,
|
|
303
|
+
status: row.status,
|
|
304
|
+
name: row.name,
|
|
305
|
+
jwksUri: row.jwksUri,
|
|
306
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
307
|
+
},
|
|
308
|
+
disconnected,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/** DELETE {prefix}/api/personal-agents/registry/:id */
|
|
312
|
+
async destroy(ctx) {
|
|
313
|
+
const runtime = await runtimeFor(ctx);
|
|
314
|
+
if (!runtime)
|
|
315
|
+
return notSupported(ctx);
|
|
316
|
+
const denied = await gateSudo(ctx);
|
|
317
|
+
if (denied)
|
|
318
|
+
return denied;
|
|
319
|
+
const id = ctx.request.param('id');
|
|
320
|
+
const row = await runtime.registry.findById(id);
|
|
321
|
+
if (!row || !(await runtime.registry.delete(id))) {
|
|
322
|
+
return ctx.response.notFound(apiError('not_found', 'Entrada não encontrada.'));
|
|
323
|
+
}
|
|
324
|
+
const cfg = (await ctx.containerResolver.make('authkit.server')).config;
|
|
325
|
+
await cfg.audit?.record({
|
|
326
|
+
type: 'agent.registry_removed',
|
|
327
|
+
clientId: row.identifier,
|
|
328
|
+
ip: ctx.request.ip?.() ?? null,
|
|
329
|
+
metadata: {
|
|
330
|
+
protocol: row.protocol,
|
|
331
|
+
status: row.status,
|
|
332
|
+
actor: ctx.session.get(ACCOUNT_SESSION_KEY) ?? null,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
return { removed: true };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -220,6 +220,7 @@ const C = {
|
|
|
220
220
|
consoleSettings: () => import('./admin_console/console_settings_controller.js'),
|
|
221
221
|
consoleKeys: () => import('./admin_console/console_keys_controller.js'),
|
|
222
222
|
consoleImpersonation: () => import('./admin_console/console_impersonation_controller.js'),
|
|
223
|
+
consolePersonalAgents: () => import('./admin_console/console_personal_agents_controller.js'),
|
|
223
224
|
apiUsers: () => import('./admin_api/api_users_controller.js'),
|
|
224
225
|
apiClients: () => import('./admin_api/api_clients_controller.js'),
|
|
225
226
|
apiMisc: () => import('./admin_api/api_misc_controller.js'),
|
|
@@ -915,6 +916,21 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
915
916
|
// Chave de assinatura managed (status + rotação ao vivo).
|
|
916
917
|
router.get(`${ap}/api/keys`, [C.consoleKeys, 'status']);
|
|
917
918
|
router.post(`${ap}/api/keys/rotate`, [C.consoleKeys, 'rotate']);
|
|
919
|
+
// Personal agents (capability-gated: 404 sem `personalAgents` ou sem banco).
|
|
920
|
+
router.get(`${ap}/api/personal-agents`, [C.consolePersonalAgents, 'index']);
|
|
921
|
+
router.get(`${ap}/api/personal-agents/connections`, [
|
|
922
|
+
C.consolePersonalAgents,
|
|
923
|
+
'connections',
|
|
924
|
+
]);
|
|
925
|
+
router.post(`${ap}/api/personal-agents/connections/:grantId/revoke`, [
|
|
926
|
+
C.consolePersonalAgents,
|
|
927
|
+
'revokeConnection',
|
|
928
|
+
]);
|
|
929
|
+
router.post(`${ap}/api/personal-agents/registry`, [C.consolePersonalAgents, 'upsert']);
|
|
930
|
+
router.delete(`${ap}/api/personal-agents/registry/:id`, [
|
|
931
|
+
C.consolePersonalAgents,
|
|
932
|
+
'destroy',
|
|
933
|
+
]);
|
|
918
934
|
// Impersonation (capability-gated: 404 quando desabilitado ou sem client).
|
|
919
935
|
router.get(`${ap}/api/impersonation/:userId`, [C.consoleImpersonation, 'handle']);
|
|
920
936
|
// ─── Shell HTML — POR ÚLTIMO (catch-all) ──────────────────────────────
|