@adonis-agora/authkit-server 1.0.0-rc.0 → 1.0.0-rc.10
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/CHANGELOG.md +111 -0
- package/README.md +4 -5
- package/build/commands/agora_doctor.d.ts +22 -0
- package/build/commands/agora_doctor.js +109 -0
- package/build/commands/clients_create.d.ts +4 -0
- package/build/commands/clients_create.js +97 -0
- package/build/commands/commands.json +78 -0
- package/build/commands/doctor.js +8 -74
- package/build/host/views/account/apps.edge +1 -5
- package/build/host/views/account/extension.edge +77 -0
- package/build/host/views/account/mfa.edge +2 -0
- package/build/host/views/account/partials/extension_actions.edge +14 -0
- package/build/host/views/account/partials/extension_block.edge +197 -0
- package/build/host/views/account/partials/extension_value.edge +8 -0
- package/build/host/views/account/partials/nav.edge +14 -0
- package/build/host/views/account/security.edge +1 -5
- package/build/host/views/account/tokens.edge +2 -2
- package/build/host/views/agents/consent.edge +11 -2
- package/build/host/views/agents/done.edge +1 -1
- package/build/host/views/partials/styles.edge +1 -1
- package/build/index.d.ts +14 -12
- package/build/index.js +8 -9
- package/build/providers/authkit_server_provider.js +36 -8
- package/build/src/account/extension.d.ts +10 -0
- package/build/src/account/extension.js +9 -0
- package/build/src/account/http.d.ts +77 -0
- package/build/src/account/http.js +381 -0
- package/build/src/account/mount.d.ts +40 -0
- package/build/src/account/mount.js +171 -0
- package/build/src/account/registry.d.ts +41 -0
- package/build/src/account/registry.js +81 -0
- package/build/src/account/remote.d.ts +72 -0
- package/build/src/account/remote.js +406 -0
- package/build/src/account/runtime.d.ts +289 -0
- package/build/src/account/runtime.js +641 -0
- package/build/src/accounts/account_store.d.ts +6 -0
- package/build/src/accounts/lucid_store/organizations.js +6 -0
- package/build/src/agents/config.d.ts +9 -13
- package/build/src/agents/config.js +17 -11
- package/build/src/agents/delegation_service.js +3 -3
- package/build/src/agents/delegation_store.d.ts +3 -2
- package/build/src/agents/delegation_store.js +6 -5
- package/build/src/agents/extension.d.ts +261 -0
- package/build/src/agents/extension.js +40 -0
- package/build/src/agents/middleware.js +1 -1
- package/build/src/agents/protocol.d.ts +4 -4
- package/build/src/agents/registry_store.d.ts +3 -2
- package/build/src/agents/registry_store.js +1 -1
- package/build/src/agents/runtime.d.ts +26 -7
- package/build/src/agents/runtime.js +110 -75
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/audit/audit_sink.js +10 -1
- package/build/src/console_authorize.d.ts +56 -0
- package/build/src/console_authorize.js +85 -0
- package/build/src/console_switcher.d.ts +132 -0
- package/build/src/console_switcher.js +206 -0
- package/build/src/define_config.d.ts +54 -14
- package/build/src/define_config.js +17 -8
- package/build/src/dev_tools/contract.d.ts +72 -0
- package/build/src/dev_tools/contract.js +10 -0
- package/build/src/dev_tools/gate.d.ts +17 -0
- package/build/src/dev_tools/gate.js +69 -0
- package/build/src/dev_tools/register.d.ts +11 -0
- package/build/src/dev_tools/register.js +25 -0
- package/build/src/dev_tools/tools.d.ts +24 -0
- package/build/src/dev_tools/tools.js +384 -0
- package/build/src/doctor/agora_checks.d.ts +15 -0
- package/build/src/doctor/agora_checks.js +150 -0
- package/build/src/doctor/agora_doctor.d.ts +115 -0
- package/build/src/doctor/agora_doctor.js +692 -0
- package/build/src/doctor/checks.d.ts +7 -1
- package/build/src/doctor/checks.js +47 -1
- package/build/src/doctor/doctor_input.d.ts +27 -0
- package/build/src/doctor/doctor_input.js +86 -0
- package/build/src/doctor/index.d.ts +5 -0
- package/build/src/doctor/index.js +4 -0
- package/build/src/host/access_token_verifier.js +7 -0
- package/build/src/host/account_api/account_api_controller.js +8 -6
- package/build/src/host/account_session_sign_in.d.ts +12 -0
- package/build/src/host/account_session_sign_in.js +19 -0
- package/build/src/host/admin_api/admin_users_service.d.ts +5 -2
- package/build/src/host/admin_api/api_clients_controller.d.ts +37 -0
- package/build/src/host/admin_api/api_clients_controller.js +31 -1
- package/build/src/host/admin_api/dto.d.ts +3 -1
- package/build/src/host/admin_api/dto.js +2 -0
- package/build/src/host/admin_authorize.d.ts +8 -0
- package/build/src/host/admin_authorize.js +25 -0
- package/build/src/host/admin_clients_service.d.ts +20 -0
- package/build/src/host/admin_clients_service.js +65 -1
- package/build/src/host/admin_console/admin_console_entry.d.ts +14 -0
- package/build/src/host/admin_console/admin_console_entry.js +80 -0
- package/build/src/host/admin_console/admin_shell_controller.js +8 -0
- package/build/src/host/admin_console/console_clients_controller.d.ts +41 -0
- package/build/src/host/admin_console/console_clients_controller.js +34 -1
- package/build/src/host/admin_console/console_personal_agents_controller.d.ts +1 -6
- package/build/src/host/admin_console/console_personal_agents_controller.js +50 -30
- package/build/src/host/admin_console/console_switcher_controller.d.ts +11 -0
- package/build/src/host/admin_console/console_switcher_controller.js +23 -0
- package/build/src/host/admin_console_warning.d.ts +8 -0
- package/build/src/host/admin_console_warning.js +18 -0
- package/build/src/host/admin_sessions_service.js +8 -5
- package/build/src/host/admin_validators.d.ts +29 -0
- package/build/src/host/admin_validators.js +30 -0
- package/build/src/host/auth_host_config.d.ts +14 -2
- package/build/src/host/console_denied_page.d.ts +6 -0
- package/build/src/host/console_denied_page.js +18 -0
- package/build/src/host/console_sso.d.ts +24 -0
- package/build/src/host/console_sso.js +70 -0
- package/build/src/host/controllers/account_apps_controller.js +15 -11
- package/build/src/host/controllers/account_session_controller.js +10 -1
- package/build/src/host/controllers/console_sso_controller.d.ts +12 -0
- package/build/src/host/controllers/console_sso_controller.js +61 -0
- package/build/src/host/controllers/interaction_controller.js +3 -1
- package/build/src/host/csrf.d.ts +3 -3
- package/build/src/host/csrf.js +3 -13
- package/build/src/host/default_mailer.d.ts +11 -6
- package/build/src/host/default_mailer.js +16 -12
- package/build/src/host/i18n.d.ts +41 -14
- package/build/src/host/i18n.js +55 -13
- package/build/src/host/rate_limit.d.ts +8 -0
- package/build/src/host/rate_limit.js +9 -0
- package/build/src/host/register_auth_host.d.ts +16 -0
- package/build/src/host/register_auth_host.js +230 -88
- package/build/src/host/renderers/edge_renderer.js +5 -0
- package/build/src/host/renderers/inertia_renderer.js +7 -1
- package/build/src/host/screen_props.d.ts +39 -8
- package/build/src/host/ui-dist/assets/index-Dq3dJVBv.js +67 -0
- package/build/src/host/ui-dist/assets/{index-DTSmD4RU.css → index-WNIyCEPA.css} +1 -1
- package/build/src/host/ui-dist/index.html +2 -2
- package/build/src/provider/build_provider.js +43 -6
- package/build/src/provider/oidc_service.d.ts +1 -1
- package/build/src/provider/oidc_service.js +1 -1
- package/build/src/schema/ensure.d.ts +14 -0
- package/build/src/schema/ensure.js +43 -150
- package/build/src/service_tenants/client_metadata.d.ts +39 -0
- package/build/src/service_tenants/client_metadata.js +80 -0
- package/build/src/service_tenants/config.d.ts +44 -0
- package/build/src/service_tenants/config.js +44 -0
- package/build/src/service_tenants/provider_hooks.d.ts +52 -0
- package/build/src/service_tenants/provider_hooks.js +124 -0
- package/build/src/service_tenants/tenant_verifier.d.ts +73 -0
- package/build/src/service_tenants/tenant_verifier.js +104 -0
- package/build/src/tenancy/tenant_labels.d.ts +30 -0
- package/build/src/tenancy/tenant_labels.js +64 -0
- package/build/types.d.ts +3 -1
- package/package.json +12 -9
- package/build/src/agents/poppy/client_registry.d.ts +0 -50
- package/build/src/agents/poppy/client_registry.js +0 -260
- package/build/src/agents/poppy/config.d.ts +0 -396
- package/build/src/agents/poppy/config.js +0 -228
- package/build/src/agents/poppy/dpop.d.ts +0 -61
- package/build/src/agents/poppy/dpop.js +0 -158
- package/build/src/agents/poppy/email_code_sign_in.d.ts +0 -27
- package/build/src/agents/poppy/email_code_sign_in.js +0 -140
- package/build/src/agents/poppy/errors.d.ts +0 -25
- package/build/src/agents/poppy/errors.js +0 -25
- package/build/src/agents/poppy/replay.d.ts +0 -25
- package/build/src/agents/poppy/replay.js +0 -106
- package/build/src/agents/poppy/resource.d.ts +0 -100
- package/build/src/agents/poppy/resource.js +0 -129
- package/build/src/agents/poppy/safe_fetch.d.ts +0 -48
- package/build/src/agents/poppy/safe_fetch.js +0 -262
- package/build/src/agents/poppy/service.d.ts +0 -293
- package/build/src/agents/poppy/service.js +0 -1458
- package/build/src/agents/poppy/session_jwt.d.ts +0 -30
- package/build/src/agents/poppy/session_jwt.js +0 -133
- package/build/src/agents/poppy/store.d.ts +0 -106
- package/build/src/agents/poppy/store.js +0 -261
- package/build/src/agents/poppy/web_session.d.ts +0 -39
- package/build/src/agents/poppy/web_session.js +0 -122
- package/build/src/host/controllers/poppy_consent_controller.d.ts +0 -19
- package/build/src/host/controllers/poppy_consent_controller.js +0 -199
- package/build/src/host/controllers/poppy_controller.d.ts +0 -33
- package/build/src/host/controllers/poppy_controller.js +0 -219
- package/build/src/host/poppy_session_key.d.ts +0 -9
- package/build/src/host/poppy_session_key.js +0 -9
- package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +0 -67
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { CLIENT_DISABLED_KEY, isClientDisabled, normalizeServiceTenant, SERVICE_TENANT_KEY, serviceTenantOf, } from '../service_tenants/client_metadata.js';
|
|
3
|
+
import { assertClientMetadata, ClientMetadataError, } from './client_metadata.js';
|
|
4
|
+
import { CONSOLE_SSO_CLIENT_ID } from './console_sso.js';
|
|
3
5
|
/** Métodos públicos (sem segredo) — espelham os "non-secret" do oidc-provider. */
|
|
4
6
|
const PUBLIC_AUTH_METHODS = new Set(['none']);
|
|
5
7
|
/** Gera um identificador opaco no estilo do oidc-provider (~43 chars base64url). */
|
|
@@ -51,6 +53,7 @@ export class AdminClientsService {
|
|
|
51
53
|
*/
|
|
52
54
|
async create(input) {
|
|
53
55
|
const clientId = (input.clientId ?? '').trim() || randomId();
|
|
56
|
+
assertNotReservedClientId(clientId);
|
|
54
57
|
const confidential = !PUBLIC_AUTH_METHODS.has(input.tokenEndpointAuthMethod);
|
|
55
58
|
const clientSecret = confidential ? randomId() : undefined;
|
|
56
59
|
const payload = this.#buildPayload(clientId, input, clientSecret);
|
|
@@ -68,6 +71,7 @@ export class AdminClientsService {
|
|
|
68
71
|
*/
|
|
69
72
|
async createWithSecret(input, providedSecret) {
|
|
70
73
|
const clientId = (input.clientId ?? '').trim() || randomId();
|
|
74
|
+
assertNotReservedClientId(clientId);
|
|
71
75
|
const confidential = !PUBLIC_AUTH_METHODS.has(input.tokenEndpointAuthMethod);
|
|
72
76
|
const clientSecret = confidential ? (providedSecret ?? randomId()) : undefined;
|
|
73
77
|
const payload = this.#buildPayload(clientId, input, clientSecret);
|
|
@@ -108,14 +112,37 @@ export class AdminClientsService {
|
|
|
108
112
|
logoUri: input.logoUri === null
|
|
109
113
|
? undefined
|
|
110
114
|
: (input.logoUri ?? existing.logo_uri),
|
|
115
|
+
serviceTenant: input.serviceTenant === undefined
|
|
116
|
+
? serviceTenantOf(existing)
|
|
117
|
+
: input.serviceTenant,
|
|
111
118
|
};
|
|
112
119
|
const confidential = !PUBLIC_AUTH_METHODS.has(merged.tokenEndpointAuthMethod);
|
|
113
120
|
// Mantém o secret atual se continua confidencial; se virou public, remove-o.
|
|
114
121
|
const clientSecret = confidential ? (previousSecret ?? randomId()) : undefined;
|
|
115
122
|
const payload = this.#buildPayload(clientId, merged, clientSecret);
|
|
123
|
+
if (isClientDisabled(existing))
|
|
124
|
+
payload[CLIENT_DISABLED_KEY] = true;
|
|
116
125
|
await this.#adapter.upsert(clientId, payload, 0);
|
|
117
126
|
await this.oidc.evictDynamicClientCache();
|
|
118
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Disables or re-enables a client. A disabled client gets `invalid_client` on every
|
|
130
|
+
* endpoint from the next request on (tokens already issued stay valid until they
|
|
131
|
+
* expire). Returns `false` when the client does not exist.
|
|
132
|
+
*/
|
|
133
|
+
async setDisabled(clientId, disabled) {
|
|
134
|
+
const existing = await this.#adapter.find(clientId);
|
|
135
|
+
if (!existing)
|
|
136
|
+
return false;
|
|
137
|
+
const payload = { ...existing };
|
|
138
|
+
if (disabled)
|
|
139
|
+
payload[CLIENT_DISABLED_KEY] = true;
|
|
140
|
+
else
|
|
141
|
+
delete payload[CLIENT_DISABLED_KEY];
|
|
142
|
+
await this.#adapter.upsert(clientId, payload, 0);
|
|
143
|
+
await this.oidc.evictDynamicClientCache();
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
119
146
|
/**
|
|
120
147
|
* Regenera o client_secret de um client confidencial, preservando o resto da
|
|
121
148
|
* metadata. Retorna o novo secret em claro (mostrado UMA vez). Lança se o
|
|
@@ -145,6 +172,8 @@ export class AdminClientsService {
|
|
|
145
172
|
* `client_metadata.ts`). Lança `ClientMetadataError`.
|
|
146
173
|
*/
|
|
147
174
|
#assertValid(input) {
|
|
175
|
+
if (input.serviceTenant)
|
|
176
|
+
this.#assertServiceTenant(input);
|
|
148
177
|
assertClientMetadata({
|
|
149
178
|
applicationType: input.applicationType ?? 'web',
|
|
150
179
|
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
@@ -153,6 +182,27 @@ export class AdminClientsService {
|
|
|
153
182
|
postLogoutRedirectUris: input.postLogoutRedirectUris,
|
|
154
183
|
});
|
|
155
184
|
}
|
|
185
|
+
/** Rules of a service-tenant client (see {@link ClientInput.serviceTenant}). */
|
|
186
|
+
#assertServiceTenant(input) {
|
|
187
|
+
if (!this.oidc.config.serviceTenants?.enabled) {
|
|
188
|
+
throw new ClientMetadataError('service tenants are not enabled: declare serviceTenants.resources in config/authkit.ts');
|
|
189
|
+
}
|
|
190
|
+
if (PUBLIC_AUTH_METHODS.has(input.tokenEndpointAuthMethod)) {
|
|
191
|
+
throw new ClientMetadataError('a service tenant must be confidential (it has a secret)');
|
|
192
|
+
}
|
|
193
|
+
if ((input.applicationType ?? 'web') !== 'web') {
|
|
194
|
+
throw new ClientMetadataError('a service tenant must be a web (server) client');
|
|
195
|
+
}
|
|
196
|
+
if (input.grantTypes.length !== 1 || input.grantTypes[0] !== 'client_credentials') {
|
|
197
|
+
throw new ClientMetadataError('a service tenant can only use the client_credentials grant');
|
|
198
|
+
}
|
|
199
|
+
if (input.redirectUris.length || input.postLogoutRedirectUris.length) {
|
|
200
|
+
throw new ClientMetadataError('a service tenant has no redirect URIs');
|
|
201
|
+
}
|
|
202
|
+
if (input.backchannelLogoutUri) {
|
|
203
|
+
throw new ClientMetadataError('a service tenant has no back-channel logout URI');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
156
206
|
/**
|
|
157
207
|
* Monta o payload na forma snake_case que o oidc-provider espera/persiste —
|
|
158
208
|
* verificada contra o que o registro dinâmico (RFC 7591) grava. As chaves de
|
|
@@ -190,6 +240,12 @@ export class AdminClientsService {
|
|
|
190
240
|
payload.backchannel_logout_session_required = input.backchannelLogoutSessionRequired;
|
|
191
241
|
}
|
|
192
242
|
}
|
|
243
|
+
if (input.serviceTenant) {
|
|
244
|
+
const tenant = normalizeServiceTenant(input.serviceTenant);
|
|
245
|
+
if (typeof tenant === 'string')
|
|
246
|
+
throw new ClientMetadataError(tenant);
|
|
247
|
+
payload[SERVICE_TENANT_KEY] = tenant;
|
|
248
|
+
}
|
|
193
249
|
return payload;
|
|
194
250
|
}
|
|
195
251
|
/** Projeta um payload persistido para a forma exibida no console admin. */
|
|
@@ -206,6 +262,8 @@ export class AdminClientsService {
|
|
|
206
262
|
applicationType: p.application_type === 'native' ? 'native' : 'web',
|
|
207
263
|
clientName: typeof p.client_name === 'string' && p.client_name ? p.client_name : null,
|
|
208
264
|
logoUri: typeof p.logo_uri === 'string' && p.logo_uri ? p.logo_uri : null,
|
|
265
|
+
disabled: isClientDisabled(p),
|
|
266
|
+
serviceTenant: serviceTenantOf(p),
|
|
209
267
|
};
|
|
210
268
|
if (p.backchannel_logout_uri) {
|
|
211
269
|
result.backchannelLogoutUri = p.backchannel_logout_uri;
|
|
@@ -215,3 +273,9 @@ export class AdminClientsService {
|
|
|
215
273
|
return result;
|
|
216
274
|
}
|
|
217
275
|
}
|
|
276
|
+
/** O `client_id` do client interno do console (ver `console_sso.ts`) não pode ser cadastrado. */
|
|
277
|
+
function assertNotReservedClientId(clientId) {
|
|
278
|
+
if (clientId === CONSOLE_SSO_CLIENT_ID) {
|
|
279
|
+
throw new ClientMetadataError(`client_id "${clientId}" é reservado pelo authkit`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import { type AgoraConsoleEntry, type AgoraConsoleRegistry, type AgoraConsoleSearchHit } from '../../console_switcher.js';
|
|
3
|
+
/** The admin console's id in the `'agora.consoles'` registry. */
|
|
4
|
+
export declare const ADMIN_CONSOLE_ID = "authkit";
|
|
5
|
+
/** The `'agora.consoles'` registry, or `null` (none bound, or no booted app). */
|
|
6
|
+
export declare function consoleRegistry(): Promise<AgoraConsoleRegistry | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Global search of the admin console: users by email (as the account store's `listAccounts`
|
|
9
|
+
* search matches them), and organizations by name or slug.
|
|
10
|
+
* Each hit opens the matching drawer in the console.
|
|
11
|
+
*/
|
|
12
|
+
export declare function searchAdminConsole(ctx: HttpContext, adminPrefix: string, query: string, limit: number): Promise<AgoraConsoleSearchHit[]>;
|
|
13
|
+
/** The admin console's registry entry, for the shell mounted at `adminPrefix`. */
|
|
14
|
+
export declare function adminConsoleEntry(adminPrefix: string, authorize: AgoraConsoleEntry['authorize']): AgoraConsoleEntry;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getBootedApp } from '../../../services/booted_app.js';
|
|
2
|
+
import { supportsOrganizations } from '../../accounts/account_store.js';
|
|
3
|
+
import { readAgoraConsoles, } from '../../console_switcher.js';
|
|
4
|
+
/** The admin console's id in the `'agora.consoles'` registry. */
|
|
5
|
+
export const ADMIN_CONSOLE_ID = 'authkit';
|
|
6
|
+
/** The `'agora.consoles'` registry, or `null` (none bound, or no booted app). */
|
|
7
|
+
export async function consoleRegistry() {
|
|
8
|
+
try {
|
|
9
|
+
return await readAgoraConsoles(getBootedApp());
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const ORG_CACHE_MS = 30_000;
|
|
16
|
+
const orgCache = new WeakMap();
|
|
17
|
+
/** The store's organizations, cached for 30 s per store so a search session does one query. */
|
|
18
|
+
function cachedOrgs(store, load) {
|
|
19
|
+
const hit = orgCache.get(store);
|
|
20
|
+
if (hit && Date.now() - hit.at < ORG_CACHE_MS)
|
|
21
|
+
return hit.rows;
|
|
22
|
+
const rows = load();
|
|
23
|
+
orgCache.set(store, { at: Date.now(), rows });
|
|
24
|
+
rows.catch(() => orgCache.delete(store));
|
|
25
|
+
return rows;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Global search of the admin console: users by email (as the account store's `listAccounts`
|
|
29
|
+
* search matches them), and organizations by name or slug.
|
|
30
|
+
* Each hit opens the matching drawer in the console.
|
|
31
|
+
*/
|
|
32
|
+
export async function searchAdminConsole(ctx, adminPrefix, query, limit) {
|
|
33
|
+
const service = await ctx.containerResolver.make('authkit.server');
|
|
34
|
+
const store = service.config.accountStore;
|
|
35
|
+
const hits = [];
|
|
36
|
+
const accounts = await store.listAccounts({ search: query, page: 1, size: limit });
|
|
37
|
+
for (const account of accounts.data.slice(0, limit)) {
|
|
38
|
+
hits.push({
|
|
39
|
+
title: account.email,
|
|
40
|
+
subtitle: account.name ? `User · ${account.name}` : 'User',
|
|
41
|
+
href: `${adminPrefix}?view=users&user=${encodeURIComponent(account.id)}`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Organizations only from a store that lists them in one query, for queries of 2+ characters,
|
|
45
|
+
// from a list cached briefly per process: search runs on every (debounced) keystroke.
|
|
46
|
+
if (hits.length < limit && query.length >= 2 && supportsOrganizations(store)) {
|
|
47
|
+
const listAll = store
|
|
48
|
+
.listAllOrgsWithMemberCount;
|
|
49
|
+
if (typeof listAll === 'function') {
|
|
50
|
+
const needle = query.toLowerCase();
|
|
51
|
+
const orgs = await cachedOrgs(store, () => listAll.call(store));
|
|
52
|
+
for (const org of orgs) {
|
|
53
|
+
if (hits.length >= limit)
|
|
54
|
+
break;
|
|
55
|
+
if (!org.name.toLowerCase().includes(needle) && !org.slug.toLowerCase().includes(needle)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
hits.push({
|
|
59
|
+
title: org.name,
|
|
60
|
+
subtitle: `Organization · ${org.slug}`,
|
|
61
|
+
href: `${adminPrefix}?view=orgs&org=${encodeURIComponent(org.id)}`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return hits;
|
|
67
|
+
}
|
|
68
|
+
/** The admin console's registry entry, for the shell mounted at `adminPrefix`. */
|
|
69
|
+
export function adminConsoleEntry(adminPrefix, authorize) {
|
|
70
|
+
return {
|
|
71
|
+
id: ADMIN_CONSOLE_ID,
|
|
72
|
+
title: 'Identity',
|
|
73
|
+
package: '@adonis-agora/authkit-server',
|
|
74
|
+
icon: 'identity',
|
|
75
|
+
path: adminPrefix,
|
|
76
|
+
order: 90,
|
|
77
|
+
authorize,
|
|
78
|
+
search: (ctx, query, limit) => searchAdminConsole(ctx, adminPrefix, query, limit),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { extname } from 'node:path';
|
|
3
|
+
import { countOtherConsoles } from '../../console_switcher.js';
|
|
3
4
|
import { accountPath } from '../account_paths.js';
|
|
4
5
|
import { resolveAccountRoles, rolesManagedExternally } from '../account_roles.js';
|
|
5
6
|
import { getAdminPrefix } from '../admin_prefix.js';
|
|
7
|
+
import { ADMIN_CONSOLE_ID, consoleRegistry } from './admin_console_entry.js';
|
|
6
8
|
// ─── Shell HTML cache ─────────────────────────────────────────────────────────
|
|
7
9
|
/**
|
|
8
10
|
* Points to the Vite-built index.html inside build/host/ui-dist/.
|
|
@@ -141,6 +143,9 @@ export default class AdminShellController {
|
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
const adminBase = getAdminPrefix();
|
|
146
|
+
// Console switcher: only announced when other `@adonis-agora` consoles are registered; the SPA
|
|
147
|
+
// then asks the endpoint which of them this viewer may open.
|
|
148
|
+
const others = countOtherConsoles(await consoleRegistry(), ADMIN_CONSOLE_ID);
|
|
144
149
|
const authkitConfig = {
|
|
145
150
|
adminBase,
|
|
146
151
|
csrfToken: ctx.request.csrfToken ?? null,
|
|
@@ -152,6 +157,9 @@ export default class AdminShellController {
|
|
|
152
157
|
// Confirmação de identidade (sudo) das ações destrutivas.
|
|
153
158
|
confirm: accountPath('confirm'),
|
|
154
159
|
},
|
|
160
|
+
...(others > 0 ? { switcher: { endpoint: `${adminBase}/api/consoles`, others } } : {}),
|
|
161
|
+
// Global search (Ctrl/Cmd-K) across this console and every other one the viewer may open.
|
|
162
|
+
search: { endpoint: `${adminBase}/api/consoles/search` },
|
|
155
163
|
features: {
|
|
156
164
|
// A página de personal agents só aparece com `personalAgents` no config.
|
|
157
165
|
personalAgents: !!cfg.personalAgents,
|
|
@@ -9,8 +9,11 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
9
9
|
* PATCH {prefix}/api/clients/:id → atualizar metadata
|
|
10
10
|
* DELETE {prefix}/api/clients/:id → remover client
|
|
11
11
|
* POST {prefix}/api/clients/:id/regenerate-secret → gerar novo secret
|
|
12
|
+
* POST {prefix}/api/clients/:id/disable → stop issuing tokens to the client
|
|
13
|
+
* POST {prefix}/api/clients/:id/enable → issue tokens again
|
|
12
14
|
*/
|
|
13
15
|
export default class ConsoleClientsController {
|
|
16
|
+
#private;
|
|
14
17
|
/** GET {prefix}/api/clients */
|
|
15
18
|
index(ctx: HttpContext): Promise<{
|
|
16
19
|
data: {
|
|
@@ -25,6 +28,8 @@ export default class ConsoleClientsController {
|
|
|
25
28
|
backchannelLogoutSessionRequired: boolean;
|
|
26
29
|
clientName: string | null;
|
|
27
30
|
logoUri: string | null;
|
|
31
|
+
disabled: boolean;
|
|
32
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
28
33
|
}[];
|
|
29
34
|
canList: boolean;
|
|
30
35
|
}>;
|
|
@@ -41,6 +46,8 @@ export default class ConsoleClientsController {
|
|
|
41
46
|
backchannelLogoutSessionRequired: boolean;
|
|
42
47
|
clientName: string | null;
|
|
43
48
|
logoUri: string | null;
|
|
49
|
+
disabled: boolean;
|
|
50
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
44
51
|
}>;
|
|
45
52
|
/** POST {prefix}/api/clients */
|
|
46
53
|
store(ctx: HttpContext): Promise<void | {
|
|
@@ -60,12 +67,46 @@ export default class ConsoleClientsController {
|
|
|
60
67
|
backchannelLogoutSessionRequired: boolean;
|
|
61
68
|
clientName: string | null;
|
|
62
69
|
logoUri: string | null;
|
|
70
|
+
disabled: boolean;
|
|
71
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
63
72
|
}>;
|
|
64
73
|
/** DELETE {prefix}/api/clients/:id */
|
|
65
74
|
destroy(ctx: HttpContext): Promise<void | {
|
|
66
75
|
ok: boolean;
|
|
67
76
|
deleted: string;
|
|
68
77
|
}>;
|
|
78
|
+
/** POST {prefix}/api/clients/:id/disable */
|
|
79
|
+
disable(ctx: HttpContext): Promise<void | {
|
|
80
|
+
clientId: string;
|
|
81
|
+
confidential: boolean;
|
|
82
|
+
grantTypes: string[];
|
|
83
|
+
redirectUris: string[];
|
|
84
|
+
postLogoutRedirectUris: string[];
|
|
85
|
+
tokenEndpointAuthMethod: string;
|
|
86
|
+
applicationType: import("../client_metadata.js").ApplicationType;
|
|
87
|
+
backchannelLogoutUri: string | null;
|
|
88
|
+
backchannelLogoutSessionRequired: boolean;
|
|
89
|
+
clientName: string | null;
|
|
90
|
+
logoUri: string | null;
|
|
91
|
+
disabled: boolean;
|
|
92
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
93
|
+
}>;
|
|
94
|
+
/** POST {prefix}/api/clients/:id/enable */
|
|
95
|
+
enable(ctx: HttpContext): Promise<void | {
|
|
96
|
+
clientId: string;
|
|
97
|
+
confidential: boolean;
|
|
98
|
+
grantTypes: string[];
|
|
99
|
+
redirectUris: string[];
|
|
100
|
+
postLogoutRedirectUris: string[];
|
|
101
|
+
tokenEndpointAuthMethod: string;
|
|
102
|
+
applicationType: import("../client_metadata.js").ApplicationType;
|
|
103
|
+
backchannelLogoutUri: string | null;
|
|
104
|
+
backchannelLogoutSessionRequired: boolean;
|
|
105
|
+
clientName: string | null;
|
|
106
|
+
logoUri: string | null;
|
|
107
|
+
disabled: boolean;
|
|
108
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
109
|
+
}>;
|
|
69
110
|
/** POST {prefix}/api/clients/:id/regenerate-secret */
|
|
70
111
|
regenerateSecret(ctx: HttpContext): Promise<void | {
|
|
71
112
|
clientId: string;
|
|
@@ -19,6 +19,8 @@ function invalidMetadata(ctx, err) {
|
|
|
19
19
|
* PATCH {prefix}/api/clients/:id → atualizar metadata
|
|
20
20
|
* DELETE {prefix}/api/clients/:id → remover client
|
|
21
21
|
* POST {prefix}/api/clients/:id/regenerate-secret → gerar novo secret
|
|
22
|
+
* POST {prefix}/api/clients/:id/disable → stop issuing tokens to the client
|
|
23
|
+
* POST {prefix}/api/clients/:id/enable → issue tokens again
|
|
22
24
|
*/
|
|
23
25
|
export default class ConsoleClientsController {
|
|
24
26
|
/** GET {prefix}/api/clients */
|
|
@@ -107,6 +109,34 @@ export default class ConsoleClientsController {
|
|
|
107
109
|
});
|
|
108
110
|
return { ok: true, deleted: id };
|
|
109
111
|
}
|
|
112
|
+
/** POST {prefix}/api/clients/:id/disable */
|
|
113
|
+
async disable(ctx) {
|
|
114
|
+
return this.#setDisabled(ctx, true);
|
|
115
|
+
}
|
|
116
|
+
/** POST {prefix}/api/clients/:id/enable */
|
|
117
|
+
async enable(ctx) {
|
|
118
|
+
return this.#setDisabled(ctx, false);
|
|
119
|
+
}
|
|
120
|
+
/** Disabled clients get `invalid_client` from the next request on. */
|
|
121
|
+
async #setDisabled(ctx, disabled) {
|
|
122
|
+
const service = await ctx.containerResolver.make('authkit.server');
|
|
123
|
+
const svc = new AdminClientsService(service);
|
|
124
|
+
const id = ctx.request.param('id');
|
|
125
|
+
if (!(await svc.setDisabled(id, disabled))) {
|
|
126
|
+
return ctx.response.notFound(apiError('not_found', 'Client não encontrado.'));
|
|
127
|
+
}
|
|
128
|
+
const client = await svc.find(id);
|
|
129
|
+
await service.config.audit?.record({
|
|
130
|
+
type: disabled ? 'client.disabled' : 'client.enabled',
|
|
131
|
+
clientId: id,
|
|
132
|
+
ip: ctx.request.ip?.() ?? null,
|
|
133
|
+
metadata: {
|
|
134
|
+
actor: 'admin-console',
|
|
135
|
+
...(client?.serviceTenant ? { partition: client.serviceTenant.partition } : {}),
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return clientDto(client);
|
|
139
|
+
}
|
|
110
140
|
/** POST {prefix}/api/clients/:id/regenerate-secret */
|
|
111
141
|
async regenerateSecret(ctx) {
|
|
112
142
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
@@ -129,7 +159,10 @@ export default class ConsoleClientsController {
|
|
|
129
159
|
type: 'client.secret_regenerated',
|
|
130
160
|
clientId: id,
|
|
131
161
|
ip: ctx.request.ip?.() ?? null,
|
|
132
|
-
metadata: {
|
|
162
|
+
metadata: {
|
|
163
|
+
actor: 'admin-console',
|
|
164
|
+
...(existing.serviceTenant ? { partition: existing.serviceTenant.partition } : {}),
|
|
165
|
+
},
|
|
133
166
|
});
|
|
134
167
|
return { clientId: id, clientSecret: newSecret };
|
|
135
168
|
}
|
|
@@ -22,7 +22,7 @@ interface AgentEntry {
|
|
|
22
22
|
connections: number;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Endpoints JSON de personal agents (PACT e
|
|
25
|
+
* Endpoints JSON de personal agents (PACT e `personalAgents.extensions`) do console admin React.
|
|
26
26
|
*
|
|
27
27
|
* GET {prefix}/api/personal-agents → agentes (config + console + conectados)
|
|
28
28
|
* GET {prefix}/api/personal-agents/connections?clientId=&accountId=&protocol=&page=&size=
|
|
@@ -41,11 +41,6 @@ export default class ConsolePersonalAgentsController {
|
|
|
41
41
|
/** A lista do config é uma função do host (o console não enxerga). */
|
|
42
42
|
hostResolver: boolean;
|
|
43
43
|
} | null;
|
|
44
|
-
poppy: {
|
|
45
|
-
requireRegistration: boolean;
|
|
46
|
-
hostResolver: boolean;
|
|
47
|
-
allowInsecureClientIds: boolean;
|
|
48
|
-
} | null;
|
|
49
44
|
};
|
|
50
45
|
agents: AgentEntry[];
|
|
51
46
|
}>;
|
|
@@ -31,10 +31,21 @@ function enabledProtocols(runtime) {
|
|
|
31
31
|
const out = [];
|
|
32
32
|
if (runtime.config.pact)
|
|
33
33
|
out.push('pact');
|
|
34
|
-
|
|
35
|
-
out.push('poppy');
|
|
34
|
+
out.push(...runtime.extensions.keys());
|
|
36
35
|
return out;
|
|
37
36
|
}
|
|
37
|
+
/** O protocolo dono de um grant, pelo prefixo do id. */
|
|
38
|
+
function protocolOfGrant(runtime, grantId) {
|
|
39
|
+
for (const [id, { extension }] of runtime.extensions) {
|
|
40
|
+
if (grantId.startsWith(extension.grantIdPrefix))
|
|
41
|
+
return id;
|
|
42
|
+
}
|
|
43
|
+
return 'pact';
|
|
44
|
+
}
|
|
45
|
+
/** Prefixo de id dos grants de um protocolo (`agrant_` = PACT). */
|
|
46
|
+
function grantPrefixOf(runtime, protocol) {
|
|
47
|
+
return runtime.extensions.get(protocol)?.extension.grantIdPrefix ?? 'agrant_';
|
|
48
|
+
}
|
|
38
49
|
function isUrl(value, allowHttp) {
|
|
39
50
|
if (typeof value !== 'string' || value.length > 2048)
|
|
40
51
|
return false;
|
|
@@ -52,14 +63,14 @@ function isUrl(value, allowHttp) {
|
|
|
52
63
|
async function agentName(runtime, protocol, identifier, fallback) {
|
|
53
64
|
if (fallback)
|
|
54
65
|
return fallback;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
const extension = runtime.extensions.get(protocol);
|
|
67
|
+
if (extension)
|
|
68
|
+
return extension.runtime.displayName(identifier);
|
|
58
69
|
const registration = await runtime.config.resolveAgent(identifier);
|
|
59
70
|
return displayNameFor(registration ?? { issuer: identifier, jwksUri: '' });
|
|
60
71
|
}
|
|
61
72
|
/**
|
|
62
|
-
* Endpoints JSON de personal agents (PACT e
|
|
73
|
+
* Endpoints JSON de personal agents (PACT e `personalAgents.extensions`) do console admin React.
|
|
63
74
|
*
|
|
64
75
|
* GET {prefix}/api/personal-agents → agentes (config + console + conectados)
|
|
65
76
|
* GET {prefix}/api/personal-agents/connections?clientId=&accountId=&protocol=&page=&size=
|
|
@@ -105,17 +116,20 @@ export default class ConsolePersonalAgentsController {
|
|
|
105
116
|
e.jwksUri = agent.jwksUri;
|
|
106
117
|
names.set(keyOf('pact', agent.issuer), agent.name ?? null);
|
|
107
118
|
}
|
|
108
|
-
for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, '
|
|
119
|
+
for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, 'agrant_')) {
|
|
109
120
|
entry('pact', clientId).connections = count;
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
const summaries = {};
|
|
124
|
+
for (const [protocol, { extension, runtime: ext }] of runtime.extensions) {
|
|
125
|
+
const summary = ext.consoleSummary();
|
|
126
|
+
summaries[protocol] = { label: extension.label, ...summary };
|
|
127
|
+
for (const id of summary.configured.allowed)
|
|
128
|
+
entry(protocol, id).config = 'allowed';
|
|
129
|
+
for (const id of summary.configured.blocked)
|
|
130
|
+
entry(protocol, id).config = 'blocked';
|
|
131
|
+
for (const [clientId, count] of await runtime.grants.countLiveGrantsByClient(now, extension.grantIdPrefix)) {
|
|
132
|
+
entry(protocol, clientId).connections = count;
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
135
|
for (const row of await runtime.registry.list()) {
|
|
@@ -153,13 +167,17 @@ export default class ConsolePersonalAgentsController {
|
|
|
153
167
|
hostResolver: cfg.staticAgents === null,
|
|
154
168
|
}
|
|
155
169
|
: null,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
170
|
+
...Object.fromEntries(Object.entries(summaries).map(([protocol, summary]) => [
|
|
171
|
+
protocol,
|
|
172
|
+
{
|
|
173
|
+
label: summary.label,
|
|
174
|
+
requireRegistration: summary.requireRegistration,
|
|
175
|
+
hostResolver: summary.hostResolver,
|
|
176
|
+
allowHttpIdentifiers: summary.allowHttpIdentifiers,
|
|
177
|
+
identifierLabel: summary.identifierLabel,
|
|
178
|
+
identifierPlaceholder: summary.identifierPlaceholder ?? null,
|
|
179
|
+
},
|
|
180
|
+
])),
|
|
163
181
|
},
|
|
164
182
|
agents,
|
|
165
183
|
};
|
|
@@ -172,11 +190,9 @@ export default class ConsolePersonalAgentsController {
|
|
|
172
190
|
const qs = ctx.request.qs();
|
|
173
191
|
const page = parseListPage(qs.page);
|
|
174
192
|
const size = parseListSize(qs.size);
|
|
175
|
-
const protocol = qs.protocol
|
|
176
|
-
? qs.protocol
|
|
177
|
-
: undefined;
|
|
193
|
+
const protocol = qs.protocol && enabledProtocols(runtime).includes(qs.protocol) ? qs.protocol : undefined;
|
|
178
194
|
const filter = {
|
|
179
|
-
protocol,
|
|
195
|
+
idPrefix: protocol ? grantPrefixOf(runtime, protocol) : undefined,
|
|
180
196
|
clientId: qs.clientId?.trim() || undefined,
|
|
181
197
|
accountId: qs.accountId?.trim() || undefined,
|
|
182
198
|
};
|
|
@@ -189,7 +205,7 @@ export default class ConsolePersonalAgentsController {
|
|
|
189
205
|
const emails = new Map();
|
|
190
206
|
const nameCache = new Map();
|
|
191
207
|
const data = await Promise.all(rows.map(async (g) => {
|
|
192
|
-
const rowProtocol = g.id
|
|
208
|
+
const rowProtocol = protocolOfGrant(runtime, g.id);
|
|
193
209
|
if (!emails.has(g.accountId)) {
|
|
194
210
|
const account = await store.findById(g.accountId).catch(() => null);
|
|
195
211
|
emails.set(g.accountId, account?.email ?? null);
|
|
@@ -248,17 +264,21 @@ export default class ConsolePersonalAgentsController {
|
|
|
248
264
|
const invalid = (message) => ctx.response.unprocessableEntity(apiError('invalid_request', message));
|
|
249
265
|
const protocol = body.protocol;
|
|
250
266
|
if (!enabledProtocols(runtime).includes(protocol)) {
|
|
251
|
-
return invalid(
|
|
267
|
+
return invalid(`\`protocol\` precisa ser um protocolo ligado (${enabledProtocols(runtime)
|
|
268
|
+
.map((p) => `\`${p}\``)
|
|
269
|
+
.join(', ')}).`);
|
|
252
270
|
}
|
|
253
271
|
const status = body.status;
|
|
254
272
|
if (status !== 'allowed' && status !== 'blocked') {
|
|
255
273
|
return invalid('`status` precisa ser `allowed` ou `blocked`.');
|
|
256
274
|
}
|
|
257
|
-
const
|
|
275
|
+
const extension = runtime.extensions.get(protocol);
|
|
276
|
+
const summary = extension?.runtime.consoleSummary();
|
|
277
|
+
const allowHttp = !!summary?.allowHttpIdentifiers;
|
|
258
278
|
const identifier = typeof body.identifier === 'string' ? body.identifier.trim() : '';
|
|
259
279
|
if (!isUrl(identifier, allowHttp)) {
|
|
260
|
-
return invalid(
|
|
261
|
-
?
|
|
280
|
+
return invalid(summary
|
|
281
|
+
? `\`identifier\` precisa ser uma URL ${allowHttp ? 'http(s)' : 'HTTPS'}: ${summary.identifierLabel}.`
|
|
262
282
|
: '`identifier` precisa ser o `iss` do agente (URL HTTPS).');
|
|
263
283
|
}
|
|
264
284
|
const name = typeof body.name === 'string' && body.name.trim() ? body.name.trim().slice(0, 200) : null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* The console switcher endpoints of the admin console (behind the admin console's own guard).
|
|
4
|
+
*
|
|
5
|
+
* GET {prefix}/api/consoles → the other consoles this viewer may open
|
|
6
|
+
* GET {prefix}/api/consoles/search?q= → hits from every console this viewer may open
|
|
7
|
+
*/
|
|
8
|
+
export default class ConsoleSwitcherController {
|
|
9
|
+
index(ctx: HttpContext): Promise<import("../../console_switcher.js").VisibleConsoles>;
|
|
10
|
+
search(ctx: HttpContext): Promise<import("../../console_switcher.js").ConsoleSearchResults>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { listVisibleConsoles, searchConsoles } from '../../console_switcher.js';
|
|
2
|
+
import { ADMIN_CONSOLE_ID, consoleRegistry } from './admin_console_entry.js';
|
|
3
|
+
/**
|
|
4
|
+
* The console switcher endpoints of the admin console (behind the admin console's own guard).
|
|
5
|
+
*
|
|
6
|
+
* GET {prefix}/api/consoles → the other consoles this viewer may open
|
|
7
|
+
* GET {prefix}/api/consoles/search?q= → hits from every console this viewer may open
|
|
8
|
+
*/
|
|
9
|
+
export default class ConsoleSwitcherController {
|
|
10
|
+
async index(ctx) {
|
|
11
|
+
const body = await listVisibleConsoles(await consoleRegistry(), ADMIN_CONSOLE_ID, ctx, {
|
|
12
|
+
logger: ctx.logger,
|
|
13
|
+
});
|
|
14
|
+
ctx.response.header('Cache-Control', 'private, no-store');
|
|
15
|
+
return body;
|
|
16
|
+
}
|
|
17
|
+
async search(ctx) {
|
|
18
|
+
const q = ctx.request.input('q', '');
|
|
19
|
+
const body = await searchConsoles(await consoleRegistry(), ADMIN_CONSOLE_ID, ctx, typeof q === 'string' ? q : '', { logger: ctx.logger });
|
|
20
|
+
ctx.response.header('Cache-Control', 'private, no-store');
|
|
21
|
+
return body;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The boot warning for an admin console with no `admin.authorize`. Same wording in every console. */
|
|
2
|
+
export declare const ADMIN_CONSOLE_NO_AUTHORIZE_WARNING = "[@adonis-agora/authkit-server] console has no `authorize` configured: it is open outside production and denies every request in production. Set `authorize` in config/authkit.ts.";
|
|
3
|
+
/** Logs {@link ADMIN_CONSOLE_NO_AUTHORIZE_WARNING} once per process. */
|
|
4
|
+
export declare function warnAdminConsoleUnauthorized(logger: {
|
|
5
|
+
warn: (message: string) => unknown;
|
|
6
|
+
} | null | undefined): boolean;
|
|
7
|
+
/** Test helper: lets the warning fire again. */
|
|
8
|
+
export declare function resetAdminConsoleWarning(): void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The boot warning for an admin console with no `admin.authorize`. Same wording in every console. */
|
|
2
|
+
export const ADMIN_CONSOLE_NO_AUTHORIZE_WARNING = '[@adonis-agora/authkit-server] console has no `authorize` configured: it is open outside production and denies every request in production. Set `authorize` in config/authkit.ts.';
|
|
3
|
+
let warned = false;
|
|
4
|
+
/** Logs {@link ADMIN_CONSOLE_NO_AUTHORIZE_WARNING} once per process. */
|
|
5
|
+
export function warnAdminConsoleUnauthorized(logger) {
|
|
6
|
+
if (warned)
|
|
7
|
+
return false;
|
|
8
|
+
warned = true;
|
|
9
|
+
if (logger)
|
|
10
|
+
logger.warn(ADMIN_CONSOLE_NO_AUTHORIZE_WARNING);
|
|
11
|
+
else
|
|
12
|
+
console.warn(ADMIN_CONSOLE_NO_AUTHORIZE_WARNING);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
/** Test helper: lets the warning fire again. */
|
|
16
|
+
export function resetAdminConsoleWarning() {
|
|
17
|
+
warned = false;
|
|
18
|
+
}
|
|
@@ -87,7 +87,7 @@ export class AdminSessionsService {
|
|
|
87
87
|
/**
|
|
88
88
|
* Revogação total também corta os personal agents: "sair de todas as sessões",
|
|
89
89
|
* reset de senha, a ação do admin e a exclusão de conta não podem deixar um
|
|
90
|
-
* agente agindo na conta (delegações do PACT e
|
|
90
|
+
* agente agindo na conta (delegações do PACT e conexões das extensions). Sem
|
|
91
91
|
* `personalAgents` é no-op — e aí nem resolve o banco. Diferente do
|
|
92
92
|
* `recordSubRevocation`, uma falha AQUI propaga: não há outra camada que corte
|
|
93
93
|
* esses tokens.
|
|
@@ -142,11 +142,14 @@ export class AdminSessionsService {
|
|
|
142
142
|
return [];
|
|
143
143
|
return (await adapter.list()).filter((r) => grantIds.has(r.payload.grantId));
|
|
144
144
|
}
|
|
145
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Grants da conta, exceto os espelhos dos grants das `personalAgents.extensions` (os access
|
|
147
|
+
* tokens de MCP que elas emitem pelo oidc-provider usam o id do grant delas) — aparecem e são
|
|
148
|
+
* revogados como personal agent, não como app OIDC.
|
|
149
|
+
*/
|
|
146
150
|
async #accountGrants(accountId) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return (await this.#listModelOfAccount('Grant', accountId)).filter((r) => !r.id.startsWith('pgrant_'));
|
|
151
|
+
const prefixes = (this.#oidc.config.personalAgents?.extensions ?? []).map((e) => e.grantIdPrefix);
|
|
152
|
+
return (await this.#listModelOfAccount('Grant', accountId)).filter((r) => !prefixes.some((prefix) => r.id.startsWith(prefix)));
|
|
150
153
|
}
|
|
151
154
|
/**
|
|
152
155
|
* Conta TODAS as sessões ativas do IdP (todas as contas). 0 quando o adapter não
|