@adonis-agora/authkit-server 1.0.0-rc.1 → 1.0.0-rc.11
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 +107 -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 +12 -12
- package/build/index.js +7 -9
- package/build/providers/authkit_server_provider.js +29 -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 +13 -7
- package/build/src/accounts/lucid_account_store.d.ts +3 -3
- package/build/src/accounts/lucid_account_store.js +42 -17
- package/build/src/accounts/lucid_store/mfa.js +3 -3
- package/build/src/accounts/lucid_store/organization_models.d.ts +14 -3
- package/build/src/accounts/lucid_store/organization_models.js +31 -3
- package/build/src/accounts/lucid_store/organizations.d.ts +2 -2
- package/build/src/accounts/lucid_store/organizations.js +40 -31
- package/build/src/accounts/lucid_store/shared.d.ts +11 -3
- package/build/src/accounts/lucid_store/shared.js +7 -5
- package/build/src/accounts/lucid_store/webauthn.js +1 -1
- package/build/src/adapters/factory.d.ts +13 -2
- package/build/src/adapters/factory.js +2 -2
- 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/commands/expire_scan_command.js +1 -1
- package/build/src/commands/settings_commands.js +2 -2
- package/build/src/console_switcher.d.ts +132 -0
- package/build/src/console_switcher.js +206 -0
- package/build/src/define_config.d.ts +38 -13
- package/build/src/define_config.js +32 -10
- 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.js +2 -2
- 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 +55 -0
- 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_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 +9 -2
- package/build/src/host/controllers/account_apps_controller.js +15 -11
- 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 +14 -0
- package/build/src/host/register_auth_host.js +167 -106
- package/build/src/host/renderers/edge_renderer.js +5 -0
- package/build/src/host/renderers/inertia_renderer.js +7 -1
- package/build/src/host/runtime_settings.d.ts +6 -6
- package/build/src/host/runtime_settings.js +4 -3
- 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 +37 -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 +24 -1
- package/build/src/schema/ensure.js +44 -154
- package/build/src/schema/tables_location.d.ts +27 -0
- package/build/src/schema/tables_location.js +22 -0
- 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 +9 -6
- 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
|
@@ -553,8 +553,8 @@ export function checkSettings(input) {
|
|
|
553
553
|
// Table absent — opt-in feature, silently ok.
|
|
554
554
|
return null;
|
|
555
555
|
}
|
|
556
|
-
// Informa a conexão usada quando nomeada (
|
|
557
|
-
const connectionName = cfg.
|
|
556
|
+
// Informa a conexão usada quando nomeada (a das tabelas do authkit).
|
|
557
|
+
const connectionName = cfg.schema?.connection;
|
|
558
558
|
const connectionHint = connectionName ? ` (connection: '${connectionName}')` : '';
|
|
559
559
|
// Table present — check if botProtection.verify is configured.
|
|
560
560
|
const hasBotVerify = typeof cfg.botProtection?.verify === 'function';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ApplicationService } from '@adonisjs/core/types';
|
|
2
|
+
import { type DoctorInput, type Finding } from './checks.js';
|
|
3
|
+
/** Logger mínimo para reportar peers instalados-mas-quebrados. */
|
|
4
|
+
export interface CanImportLogger {
|
|
5
|
+
warning(msg: string): void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Tenta importar um peer; `true` se importável.
|
|
9
|
+
*
|
|
10
|
+
* Distingue "não instalado" de "instalado mas quebrado": só um erro de
|
|
11
|
+
* módulo-não-encontrado (`ERR_MODULE_NOT_FOUND`/`MODULE_NOT_FOUND`) é tratado como
|
|
12
|
+
* peer ausente (silencioso — é o caso esperado). Qualquer outro erro significa que
|
|
13
|
+
* o peer ESTÁ presente mas falhou ao carregar (import circular, erro de sintaxe,
|
|
14
|
+
* config inválida) — isso o doctor precisa gritar, senão um peer quebrado aparece
|
|
15
|
+
* como "ausente" e o diagnóstico mente.
|
|
16
|
+
*/
|
|
17
|
+
export declare function canImport(specifier: string, logger?: CanImportLogger): Promise<boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Monta o `DoctorInput` a partir do app (config `authkit` resolvida, `session`, peers). Usado por
|
|
20
|
+
* `authkit:doctor` e pelos checks do `agora:doctor`. Lança se `config/authkit.ts` não resolver.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildDoctorInput(app: ApplicationService, logger?: CanImportLogger): Promise<DoctorInput>;
|
|
23
|
+
/**
|
|
24
|
+
* O finding de idade da chave de assinatura managed (best-effort): `null` quando o jwks não é
|
|
25
|
+
* `managed` com store, ou quando o keystore não pôde ser lido.
|
|
26
|
+
*/
|
|
27
|
+
export declare function managedSigningKeyAgeFinding(app: ApplicationService, authkitConfig: Record<string, any> | null): Promise<Finding | null>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { DatabaseAdapter } from '../adapters/database_adapter.js';
|
|
10
|
+
import { resolveAuthkitConfig } from '../commands/resolve_config.js';
|
|
11
|
+
import { defaultEncryptForStore } from '../define_config.js';
|
|
12
|
+
import { signingKeyAgeDays } from '../keys/keystore.js';
|
|
13
|
+
import { KeystoreCodec } from '../keys/keystore_codec.js';
|
|
14
|
+
import { loadEncryptionService } from '../keys/keystore_crypto.js';
|
|
15
|
+
import { KeystoreManager, resolveKeystoreVault } from '../keys/keystore_manager.js';
|
|
16
|
+
import { signingKeyAgeFinding } from './checks.js';
|
|
17
|
+
/**
|
|
18
|
+
* Tenta importar um peer; `true` se importável.
|
|
19
|
+
*
|
|
20
|
+
* Distingue "não instalado" de "instalado mas quebrado": só um erro de
|
|
21
|
+
* módulo-não-encontrado (`ERR_MODULE_NOT_FOUND`/`MODULE_NOT_FOUND`) é tratado como
|
|
22
|
+
* peer ausente (silencioso — é o caso esperado). Qualquer outro erro significa que
|
|
23
|
+
* o peer ESTÁ presente mas falhou ao carregar (import circular, erro de sintaxe,
|
|
24
|
+
* config inválida) — isso o doctor precisa gritar, senão um peer quebrado aparece
|
|
25
|
+
* como "ausente" e o diagnóstico mente.
|
|
26
|
+
*/
|
|
27
|
+
export async function canImport(specifier, logger) {
|
|
28
|
+
try {
|
|
29
|
+
await import(__rewriteRelativeImportExtension(specifier));
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const code = err?.code;
|
|
34
|
+
if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND') {
|
|
35
|
+
logger?.warning(`⚠️ ${specifier} está instalado mas falhou ao carregar: ${err.message}`);
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Monta o `DoctorInput` a partir do app (config `authkit` resolvida, `session`, peers). Usado por
|
|
42
|
+
* `authkit:doctor` e pelos checks do `agora:doctor`. Lança se `config/authkit.ts` não resolver.
|
|
43
|
+
*/
|
|
44
|
+
export async function buildDoctorInput(app, logger) {
|
|
45
|
+
const config = await app.container.make('config');
|
|
46
|
+
// `defineConfig` exporta um config provider — resolve antes de inspecionar,
|
|
47
|
+
// senão todo campo aparece como "ausente" (provider cru não tem issuer etc.).
|
|
48
|
+
const authkitConfig = await resolveAuthkitConfig(app, config.get('authkit', null));
|
|
49
|
+
const sessionConfig = config.get('session', null) ?? null;
|
|
50
|
+
return {
|
|
51
|
+
authkitConfig,
|
|
52
|
+
sessionConfig,
|
|
53
|
+
peers: {
|
|
54
|
+
session: await canImport('@adonisjs/session', logger),
|
|
55
|
+
shield: await canImport('@adonisjs/shield', logger),
|
|
56
|
+
ally: await canImport('@adonisjs/ally', logger),
|
|
57
|
+
limiter: await canImport('@adonisjs/limiter', logger),
|
|
58
|
+
},
|
|
59
|
+
__adapterClasses: { DatabaseAdapter },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* O finding de idade da chave de assinatura managed (best-effort): `null` quando o jwks não é
|
|
64
|
+
* `managed` com store, ou quando o keystore não pôde ser lido.
|
|
65
|
+
*/
|
|
66
|
+
export async function managedSigningKeyAgeFinding(app, authkitConfig) {
|
|
67
|
+
const jwksInput = (authkitConfig?.jwksConfig ?? authkitConfig?.jwks);
|
|
68
|
+
if (jwksInput?.source !== 'managed' || !jwksInput?.store)
|
|
69
|
+
return null;
|
|
70
|
+
try {
|
|
71
|
+
const vault = resolveKeystoreVault(jwksInput.store, {
|
|
72
|
+
makePath: (p) => app.makePath(p),
|
|
73
|
+
container: app.container,
|
|
74
|
+
});
|
|
75
|
+
const encrypt = jwksInput.encrypt ?? defaultEncryptForStore(jwksInput.store);
|
|
76
|
+
const enc = encrypt ? await loadEncryptionService().catch(() => undefined) : undefined;
|
|
77
|
+
const mgr = new KeystoreManager(vault, new KeystoreCodec({ encrypt, enc }), jwksInput.algorithm ?? 'RS256');
|
|
78
|
+
const store = await mgr.read().catch(() => null);
|
|
79
|
+
const maxAge = jwksInput.rotationDays ?? 90;
|
|
80
|
+
return signingKeyAgeFinding(signingKeyAgeDays(store), maxAge);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* idade é best-effort no doctor */
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AgoraDoctorCheck } from './agora_doctor.js';
|
|
2
|
+
export type { AgoraDoctorCheck, AgoraDoctorRegistry, AgoraDoctorReport, AgoraDoctorResult, AgoraDoctorStatus, } from './agora_doctor.js';
|
|
3
|
+
export { AGORA_DOCTOR_BINDING, registerDoctorChecks } from './agora_doctor.js';
|
|
4
|
+
/** authkit's checks, for `node ace agora:doctor --no-boot` (which imports this entry instead of booting). */
|
|
5
|
+
export declare const agoraDoctorChecks: AgoraDoctorCheck[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { authkitDoctorChecks } from './agora_checks.js';
|
|
2
|
+
export { AGORA_DOCTOR_BINDING, registerDoctorChecks } from './agora_doctor.js';
|
|
3
|
+
/** authkit's checks, for `node ace agora:doctor --no-boot` (which imports this entry instead of booting). */
|
|
4
|
+
export const agoraDoctorChecks = authkitDoctorChecks();
|
|
@@ -41,6 +41,10 @@ function fromJwtPayload(payload) {
|
|
|
41
41
|
if (typeof payload.sub !== 'string' || !payload.sub)
|
|
42
42
|
return null;
|
|
43
43
|
const clientId = payload.client_id;
|
|
44
|
+
// No user behind it: a `client_credentials` token (a service tenant's, for one) has
|
|
45
|
+
// `sub` = the client id (RFC 9068 §2.2) and never authenticates an account.
|
|
46
|
+
if (payload.sub === clientId)
|
|
47
|
+
return null;
|
|
44
48
|
return {
|
|
45
49
|
format: 'jwt',
|
|
46
50
|
sub: payload.sub,
|
|
@@ -209,6 +213,9 @@ export function remoteAccessTokenVerifier(options) {
|
|
|
209
213
|
return null;
|
|
210
214
|
if (typeof body.sub !== 'string' || !body.sub)
|
|
211
215
|
return null;
|
|
216
|
+
// `client_credentials` token: no account behind it.
|
|
217
|
+
if (body.sub === body.client_id)
|
|
218
|
+
return null;
|
|
212
219
|
if (typeof body.exp === 'number' && body.exp <= Math.floor(Date.now() / 1000))
|
|
213
220
|
return null;
|
|
214
221
|
return {
|
|
@@ -611,7 +611,7 @@ export default class AccountApiController {
|
|
|
611
611
|
/** Personal agents com delegação ativa na conta (scopes aprovados). */
|
|
612
612
|
async listAgents(ctx) {
|
|
613
613
|
const runtime = await personalAgentsFor(ctx);
|
|
614
|
-
if (!runtime
|
|
614
|
+
if (!runtime || (!runtime.delegation && runtime.extensions.size === 0)) {
|
|
615
615
|
return ctx.response
|
|
616
616
|
.status(404)
|
|
617
617
|
.send(apiErr('capability_unsupported', 'Personal agent delegation is not enabled.'));
|
|
@@ -620,16 +620,18 @@ export default class AccountApiController {
|
|
|
620
620
|
const pact = runtime.delegation
|
|
621
621
|
? (await runtime.delegation.listGrants(userId)).map((g) => ({ ...g, protocol: 'pact' }))
|
|
622
622
|
: [];
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
623
|
+
const others = [];
|
|
624
|
+
for (const [protocol, { runtime: extension }] of runtime.extensions) {
|
|
625
|
+
for (const g of await extension.listAccountGrants(userId))
|
|
626
|
+
others.push({ ...g, protocol });
|
|
627
|
+
}
|
|
628
|
+
return { agents: [...pact, ...others] };
|
|
627
629
|
}
|
|
628
630
|
// ─── DELETE /account/api/agents/:id ──────────────────────────────────────
|
|
629
631
|
/** Revoga a delegação de um personal agent (efeito imediato nos tokens). */
|
|
630
632
|
async revokeAgent(ctx) {
|
|
631
633
|
const runtime = await personalAgentsFor(ctx);
|
|
632
|
-
if (!runtime
|
|
634
|
+
if (!runtime || (!runtime.delegation && runtime.extensions.size === 0)) {
|
|
633
635
|
return ctx.response
|
|
634
636
|
.status(404)
|
|
635
637
|
.send(apiErr('capability_unsupported', 'Personal agent delegation is not enabled.'));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abrir e fechar a sessão de CONTA do authkit (o cookie do console, `ACCOUNT_SESSION_KEY`) a partir
|
|
3
|
+
* de código de fora do host-kit — o navegador de um personal agent que entrou na conta, por
|
|
4
|
+
* exemplo. Sincroniza `ctx.auth` (`adonisAuth.guard`) como o login do próprio console.
|
|
5
|
+
*
|
|
6
|
+
* Estável no 1.x (usado pelas `personalAgents.extensions`).
|
|
7
|
+
*/
|
|
8
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
9
|
+
/** Liga a sessão de conta a `accountId` e sincroniza `ctx.auth`. Não regenera a sessão. */
|
|
10
|
+
export declare function signInAccountSession(ctx: HttpContext, accountId: string): Promise<void>;
|
|
11
|
+
/** Desliga a sessão de conta e sincroniza `ctx.auth`. Não regenera a sessão. */
|
|
12
|
+
export declare function signOutAccountSession(ctx: HttpContext): Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
2
|
+
import { syncAdonisAuthLogin, syncAdonisAuthLogout } from './adonis_auth_sync.js';
|
|
3
|
+
/** Liga a sessão de conta a `accountId` e sincroniza `ctx.auth`. Não regenera a sessão. */
|
|
4
|
+
export async function signInAccountSession(ctx, accountId) {
|
|
5
|
+
// Sem augmentation no barrel (ver `augmentation_isolation.spec.ts`): a sessão é lida sem tipo.
|
|
6
|
+
const session = ctx.session;
|
|
7
|
+
const service = await ctx.containerResolver.make('authkit.server');
|
|
8
|
+
session.put(ACCOUNT_SESSION_KEY, accountId);
|
|
9
|
+
const account = await service.config.accountStore.findById(accountId);
|
|
10
|
+
if (account)
|
|
11
|
+
await syncAdonisAuthLogin(ctx, service.config, account);
|
|
12
|
+
}
|
|
13
|
+
/** Desliga a sessão de conta e sincroniza `ctx.auth`. Não regenera a sessão. */
|
|
14
|
+
export async function signOutAccountSession(ctx) {
|
|
15
|
+
const session = ctx.session;
|
|
16
|
+
const service = await ctx.containerResolver.make('authkit.server');
|
|
17
|
+
session.forget(ACCOUNT_SESSION_KEY);
|
|
18
|
+
await syncAdonisAuthLogout(ctx, service.config);
|
|
19
|
+
}
|
|
@@ -8,8 +8,11 @@ import type { SettingsCapability } from '../runtime_settings.js';
|
|
|
8
8
|
export interface AdminActor {
|
|
9
9
|
actorId: string | null;
|
|
10
10
|
ip: string | null;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Marca metadata da auditoria — 'admin-api' nas escritas via REST, 'admin' no console HTML,
|
|
13
|
+
* 'mcp' nas ações dos dev tools (endpoint MCP do Agora).
|
|
14
|
+
*/
|
|
15
|
+
source?: 'admin-api' | 'admin' | 'mcp';
|
|
13
16
|
}
|
|
14
17
|
/** Resultado da deleção via admin: false quando o store não suporta hard delete. */
|
|
15
18
|
export type DeleteUserResult = {
|
|
@@ -6,6 +6,7 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
6
6
|
* em create/regenerate. Audita create/update/delete.
|
|
7
7
|
*/
|
|
8
8
|
export default class ApiClientsController {
|
|
9
|
+
#private;
|
|
9
10
|
index(ctx: HttpContext): Promise<{
|
|
10
11
|
data: {
|
|
11
12
|
clientId: string;
|
|
@@ -19,6 +20,8 @@ export default class ApiClientsController {
|
|
|
19
20
|
backchannelLogoutSessionRequired: boolean;
|
|
20
21
|
clientName: string | null;
|
|
21
22
|
logoUri: string | null;
|
|
23
|
+
disabled: boolean;
|
|
24
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
22
25
|
}[];
|
|
23
26
|
canList: boolean;
|
|
24
27
|
}>;
|
|
@@ -34,6 +37,8 @@ export default class ApiClientsController {
|
|
|
34
37
|
backchannelLogoutSessionRequired: boolean;
|
|
35
38
|
clientName: string | null;
|
|
36
39
|
logoUri: string | null;
|
|
40
|
+
disabled: boolean;
|
|
41
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
37
42
|
}>;
|
|
38
43
|
store(ctx: HttpContext): Promise<void | {
|
|
39
44
|
clientId: string;
|
|
@@ -51,11 +56,43 @@ export default class ApiClientsController {
|
|
|
51
56
|
backchannelLogoutSessionRequired: boolean;
|
|
52
57
|
clientName: string | null;
|
|
53
58
|
logoUri: string | null;
|
|
59
|
+
disabled: boolean;
|
|
60
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
54
61
|
}>;
|
|
55
62
|
regenerateSecret(ctx: HttpContext): Promise<void | {
|
|
56
63
|
clientId: any;
|
|
57
64
|
clientSecret: string;
|
|
58
65
|
}>;
|
|
66
|
+
disable(ctx: HttpContext): Promise<void | {
|
|
67
|
+
clientId: string;
|
|
68
|
+
confidential: boolean;
|
|
69
|
+
grantTypes: string[];
|
|
70
|
+
redirectUris: string[];
|
|
71
|
+
postLogoutRedirectUris: string[];
|
|
72
|
+
tokenEndpointAuthMethod: string;
|
|
73
|
+
applicationType: import("../client_metadata.js").ApplicationType;
|
|
74
|
+
backchannelLogoutUri: string | null;
|
|
75
|
+
backchannelLogoutSessionRequired: boolean;
|
|
76
|
+
clientName: string | null;
|
|
77
|
+
logoUri: string | null;
|
|
78
|
+
disabled: boolean;
|
|
79
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
80
|
+
}>;
|
|
81
|
+
enable(ctx: HttpContext): Promise<void | {
|
|
82
|
+
clientId: string;
|
|
83
|
+
confidential: boolean;
|
|
84
|
+
grantTypes: string[];
|
|
85
|
+
redirectUris: string[];
|
|
86
|
+
postLogoutRedirectUris: string[];
|
|
87
|
+
tokenEndpointAuthMethod: string;
|
|
88
|
+
applicationType: import("../client_metadata.js").ApplicationType;
|
|
89
|
+
backchannelLogoutUri: string | null;
|
|
90
|
+
backchannelLogoutSessionRequired: boolean;
|
|
91
|
+
clientName: string | null;
|
|
92
|
+
logoUri: string | null;
|
|
93
|
+
disabled: boolean;
|
|
94
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
95
|
+
}>;
|
|
59
96
|
destroy(ctx: HttpContext): Promise<{
|
|
60
97
|
clientId: any;
|
|
61
98
|
deleted: boolean;
|
|
@@ -85,11 +85,16 @@ export default class ApiClientsController {
|
|
|
85
85
|
const id = ctx.request.param('id');
|
|
86
86
|
try {
|
|
87
87
|
const secret = await svc.regenerateSecret(id);
|
|
88
|
+
const tenant = (await svc.find(id))?.serviceTenant;
|
|
88
89
|
await service.config.audit?.record({
|
|
89
90
|
type: 'client.updated',
|
|
90
91
|
clientId: id,
|
|
91
92
|
ip: ctx.request.ip?.() ?? null,
|
|
92
|
-
metadata: {
|
|
93
|
+
metadata: {
|
|
94
|
+
actor: 'admin-api',
|
|
95
|
+
action: 'regenerate_secret',
|
|
96
|
+
...(tenant ? { partition: tenant.partition } : {}),
|
|
97
|
+
},
|
|
93
98
|
});
|
|
94
99
|
return { clientId: id, clientSecret: secret };
|
|
95
100
|
}
|
|
@@ -97,6 +102,31 @@ export default class ApiClientsController {
|
|
|
97
102
|
return ctx.response.conflict(apiError('cannot_regenerate', e.message));
|
|
98
103
|
}
|
|
99
104
|
}
|
|
105
|
+
async disable(ctx) {
|
|
106
|
+
return this.#setDisabled(ctx, true);
|
|
107
|
+
}
|
|
108
|
+
async enable(ctx) {
|
|
109
|
+
return this.#setDisabled(ctx, false);
|
|
110
|
+
}
|
|
111
|
+
/** Disabled clients get `invalid_client` from the next request on. */
|
|
112
|
+
async #setDisabled(ctx, disabled) {
|
|
113
|
+
const { service, svc } = await clientsService(ctx);
|
|
114
|
+
const id = ctx.request.param('id');
|
|
115
|
+
if (!(await svc.setDisabled(id, disabled))) {
|
|
116
|
+
return ctx.response.notFound(apiError('not_found', 'Client não encontrado.'));
|
|
117
|
+
}
|
|
118
|
+
const client = await svc.find(id);
|
|
119
|
+
await service.config.audit?.record({
|
|
120
|
+
type: disabled ? 'client.disabled' : 'client.enabled',
|
|
121
|
+
clientId: id,
|
|
122
|
+
ip: ctx.request.ip?.() ?? null,
|
|
123
|
+
metadata: {
|
|
124
|
+
actor: 'admin-api',
|
|
125
|
+
...(client?.serviceTenant ? { partition: client.serviceTenant.partition } : {}),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
return clientDto(client);
|
|
129
|
+
}
|
|
100
130
|
async destroy(ctx) {
|
|
101
131
|
const { service, svc } = await clientsService(ctx);
|
|
102
132
|
const id = ctx.request.param('id');
|
|
@@ -40,6 +40,8 @@ export declare function clientDto(client: AdminClient): {
|
|
|
40
40
|
backchannelLogoutSessionRequired: boolean;
|
|
41
41
|
clientName: string | null;
|
|
42
42
|
logoUri: string | null;
|
|
43
|
+
disabled: boolean;
|
|
44
|
+
serviceTenant: import("../../service_tenants/client_metadata.ts").ServiceTenantInfo | null;
|
|
43
45
|
};
|
|
44
46
|
/** Inclui o secret (mostrado UMA vez) em create/regenerate. */
|
|
45
47
|
export declare function createdClientDto(created: CreatedClient): {
|
|
@@ -67,7 +69,7 @@ export declare function grantDto(grant: AdminGrant): {
|
|
|
67
69
|
};
|
|
68
70
|
export declare function auditDto(event: StoredAuditEvent): {
|
|
69
71
|
id: string;
|
|
70
|
-
type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.locked" | "account.signed_out_all" | "agent.allowed" | "agent.blocked" | "agent.delegation_approved" | "agent.delegation_denied" | "agent.grant_revoked" | "agent.grant_revoked_by_admin" | "agent.registry_removed" | "bot_protection.rejected" | "client.created" | "client.deleted" | "client.secret_regenerated" | "client.updated" | "email.change_requested" | "email.changed" | "email_change.cancelled" | "email_change.confirmed" | "email_change.requested" | "email_verification.consumed" | "email_verification.issued" | "grant.revoked_by_user" | "impersonation" | "impersonation.panel_viewed" | "impersonation.stopped" | "keys.rotated" | "login.failure" | "login.magic_link_sent" | "login.new_device" | "login.new_ip_notified" | "login.otp_failed" | "login.otp_invalidated" | "login.otp_sent" | "login.otp_verified" | "login.success" | "maintenance.disabled" | "maintenance.enabled" | "mfa.disabled" | "mfa.enabled" | "mfa.recovery_codes_regenerated" | "organization.created" | "organization.deactivated" | "organization.deleted" | "organization.invitation_accepted" | "organization.invitation_revoked" | "organization.invitation_sent" | "organization.member_added" | "organization.member_removed" | "organization.member_role_changed" | "organization.member_role_updated" | "organization.switched" | "organization.updated" | "otp.locked" | "otp.unlock_failed" | "otp.unlocked" | "passkey.registered" | "passkey.removed" | "password.changed" | "password.expired_change_forced" | "password.rehashed" | "password_reset.consumed" | "password_reset.issued" | "pat.issued" | "pat.revoked" | "pat.used" | "profile.updated" | "roles_catalog.updated" | "security_notice.sent" | "session.revoked" | "session.revoked_all" | "session.single_enforced" | "settings.updated" | "signup" | "sudo.confirmed" | "trusted_device.revoked" | "user.created" | "user.deleted" | "user.disabled" | "user.enabled" | "user.password_reset_sent";
|
|
72
|
+
type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.extension.action" | "account.extension.redirect" | "account.locked" | "account.signed_out_all" | "agent.allowed" | "agent.blocked" | "agent.delegation_approved" | "agent.delegation_denied" | "agent.grant_revoked" | "agent.grant_revoked_by_admin" | "agent.registry_removed" | "bot_protection.rejected" | "client.created" | "client.deleted" | "client.disabled" | "client.enabled" | "client.secret_regenerated" | "client.updated" | "email.change_requested" | "email.changed" | "email_change.cancelled" | "email_change.confirmed" | "email_change.requested" | "email_verification.consumed" | "email_verification.issued" | "grant.revoked_by_user" | "impersonation" | "impersonation.panel_viewed" | "impersonation.stopped" | "keys.rotated" | "login.failure" | "login.magic_link_sent" | "login.new_device" | "login.new_ip_notified" | "login.otp_failed" | "login.otp_invalidated" | "login.otp_sent" | "login.otp_verified" | "login.success" | "maintenance.disabled" | "maintenance.enabled" | "mfa.disabled" | "mfa.enabled" | "mfa.recovery_codes_regenerated" | "organization.created" | "organization.deactivated" | "organization.deleted" | "organization.invitation_accepted" | "organization.invitation_revoked" | "organization.invitation_sent" | "organization.member_added" | "organization.member_removed" | "organization.member_role_changed" | "organization.member_role_updated" | "organization.switched" | "organization.updated" | "otp.locked" | "otp.unlock_failed" | "otp.unlocked" | "passkey.registered" | "passkey.removed" | "password.changed" | "password.expired_change_forced" | "password.rehashed" | "password_reset.consumed" | "password_reset.issued" | "pat.issued" | "pat.revoked" | "pat.used" | "profile.updated" | "roles_catalog.updated" | "security_notice.sent" | "service_tenant.token_issued" | "session.revoked" | "session.revoked_all" | "session.single_enforced" | "settings.updated" | "signup" | "sudo.confirmed" | "trusted_device.revoked" | "user.created" | "user.deleted" | "user.disabled" | "user.enabled" | "user.password_reset_sent";
|
|
71
73
|
accountId: string | null;
|
|
72
74
|
email: string | null;
|
|
73
75
|
clientId: string | null;
|
|
@@ -32,6 +32,8 @@ export function clientDto(client) {
|
|
|
32
32
|
backchannelLogoutSessionRequired: client.backchannelLogoutSessionRequired ?? false,
|
|
33
33
|
clientName: client.clientName,
|
|
34
34
|
logoUri: client.logoUri,
|
|
35
|
+
disabled: client.disabled,
|
|
36
|
+
serviceTenant: client.serviceTenant,
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
/** Inclui o secret (mostrado UMA vez) em create/regenerate. */
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ConsoleAuthorize, type ConsoleAuthorizeDecision } from '../console_authorize.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether the app runs in production. Unknown (the provider did not register the
|
|
4
|
+
* app) counts as production, so an omitted `admin.authorize` fails closed.
|
|
5
|
+
*/
|
|
6
|
+
export declare function appInProduction(): boolean;
|
|
7
|
+
/** The resolved `admin.authorize` checker (memoized per configured value). Never throws. */
|
|
8
|
+
export declare function adminAuthorizeChecker(authorize: ConsoleAuthorize): (ctx: any) => Promise<ConsoleAuthorizeDecision>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getBootedApp } from '../../services/booted_app.js';
|
|
2
|
+
import { resolveConsoleAuthorize, } from '../console_authorize.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether the app runs in production. Unknown (the provider did not register the
|
|
5
|
+
* app) counts as production, so an omitted `admin.authorize` fails closed.
|
|
6
|
+
*/
|
|
7
|
+
export function appInProduction() {
|
|
8
|
+
try {
|
|
9
|
+
return getBootedApp().inProduction !== false;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** One checker per configured `admin.authorize`, so a lazy import runs once. */
|
|
16
|
+
const adminAuthorizeCheckers = new WeakMap();
|
|
17
|
+
/** The resolved `admin.authorize` checker (memoized per configured value). Never throws. */
|
|
18
|
+
export function adminAuthorizeChecker(authorize) {
|
|
19
|
+
let checker = adminAuthorizeCheckers.get(authorize);
|
|
20
|
+
if (!checker) {
|
|
21
|
+
checker = resolveConsoleAuthorize(authorize);
|
|
22
|
+
adminAuthorizeCheckers.set(authorize, checker);
|
|
23
|
+
}
|
|
24
|
+
return checker;
|
|
25
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OidcService } from '../provider/oidc_service.js';
|
|
2
|
+
import { type ServiceTenantInfo } from '../service_tenants/client_metadata.js';
|
|
2
3
|
import { type ApplicationType } from './client_metadata.js';
|
|
3
4
|
/** Métodos de autenticação no token endpoint suportados pelo formulário admin. */
|
|
4
5
|
export type TokenEndpointAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none';
|
|
@@ -30,6 +31,15 @@ export interface ClientInput {
|
|
|
30
31
|
* `backchannelLogoutUri` não está definido.
|
|
31
32
|
*/
|
|
32
33
|
backchannelLogoutSessionRequired?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Makes the client a service tenant: confidential, `client_credentials` only, no
|
|
36
|
+
* redirect URIs; its tokens carry `partition` and `capabilities`. On update, `null`
|
|
37
|
+
* turns it back into a regular client; absent keeps the current value.
|
|
38
|
+
*/
|
|
39
|
+
serviceTenant?: {
|
|
40
|
+
partition: string;
|
|
41
|
+
capabilities?: string[];
|
|
42
|
+
} | null;
|
|
33
43
|
}
|
|
34
44
|
/** Client persistido, apresentado ao console admin. */
|
|
35
45
|
export interface AdminClient {
|
|
@@ -49,6 +59,10 @@ export interface AdminClient {
|
|
|
49
59
|
clientName: string | null;
|
|
50
60
|
/** `logo_uri` registrado, ou `null`. */
|
|
51
61
|
logoUri: string | null;
|
|
62
|
+
/** Disabled clients get no tokens (`invalid_client`) until enabled again. */
|
|
63
|
+
disabled: boolean;
|
|
64
|
+
/** Service-tenant settings, or `null` for a regular client. */
|
|
65
|
+
serviceTenant: ServiceTenantInfo | null;
|
|
52
66
|
}
|
|
53
67
|
/** Resultado de uma criação: o client + o secret em claro (mostrado UMA vez). */
|
|
54
68
|
export interface CreatedClient {
|
|
@@ -94,6 +108,12 @@ export declare class AdminClientsService {
|
|
|
94
108
|
* PRESERVANDO o client_secret existente. Lança se o client não existe.
|
|
95
109
|
*/
|
|
96
110
|
update(clientId: string, input: Partial<ClientInput>): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Disables or re-enables a client. A disabled client gets `invalid_client` on every
|
|
113
|
+
* endpoint from the next request on (tokens already issued stay valid until they
|
|
114
|
+
* expire). Returns `false` when the client does not exist.
|
|
115
|
+
*/
|
|
116
|
+
setDisabled(clientId: string, disabled: boolean): Promise<boolean>;
|
|
97
117
|
/**
|
|
98
118
|
* Regenera o client_secret de um client confidencial, preservando o resto da
|
|
99
119
|
* metadata. Retorna o novo secret em claro (mostrado UMA vez). Lança se o
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { CLIENT_DISABLED_KEY, isClientDisabled, normalizeServiceTenant, SERVICE_TENANT_KEY, serviceTenantOf, } from '../service_tenants/client_metadata.js';
|
|
2
3
|
import { assertClientMetadata, ClientMetadataError, } from './client_metadata.js';
|
|
3
4
|
import { CONSOLE_SSO_CLIENT_ID } from './console_sso.js';
|
|
4
5
|
/** Métodos públicos (sem segredo) — espelham os "non-secret" do oidc-provider. */
|
|
@@ -111,14 +112,37 @@ export class AdminClientsService {
|
|
|
111
112
|
logoUri: input.logoUri === null
|
|
112
113
|
? undefined
|
|
113
114
|
: (input.logoUri ?? existing.logo_uri),
|
|
115
|
+
serviceTenant: input.serviceTenant === undefined
|
|
116
|
+
? serviceTenantOf(existing)
|
|
117
|
+
: input.serviceTenant,
|
|
114
118
|
};
|
|
115
119
|
const confidential = !PUBLIC_AUTH_METHODS.has(merged.tokenEndpointAuthMethod);
|
|
116
120
|
// Mantém o secret atual se continua confidencial; se virou public, remove-o.
|
|
117
121
|
const clientSecret = confidential ? (previousSecret ?? randomId()) : undefined;
|
|
118
122
|
const payload = this.#buildPayload(clientId, merged, clientSecret);
|
|
123
|
+
if (isClientDisabled(existing))
|
|
124
|
+
payload[CLIENT_DISABLED_KEY] = true;
|
|
119
125
|
await this.#adapter.upsert(clientId, payload, 0);
|
|
120
126
|
await this.oidc.evictDynamicClientCache();
|
|
121
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
|
+
}
|
|
122
146
|
/**
|
|
123
147
|
* Regenera o client_secret de um client confidencial, preservando o resto da
|
|
124
148
|
* metadata. Retorna o novo secret em claro (mostrado UMA vez). Lança se o
|
|
@@ -148,6 +172,8 @@ export class AdminClientsService {
|
|
|
148
172
|
* `client_metadata.ts`). Lança `ClientMetadataError`.
|
|
149
173
|
*/
|
|
150
174
|
#assertValid(input) {
|
|
175
|
+
if (input.serviceTenant)
|
|
176
|
+
this.#assertServiceTenant(input);
|
|
151
177
|
assertClientMetadata({
|
|
152
178
|
applicationType: input.applicationType ?? 'web',
|
|
153
179
|
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
@@ -156,6 +182,27 @@ export class AdminClientsService {
|
|
|
156
182
|
postLogoutRedirectUris: input.postLogoutRedirectUris,
|
|
157
183
|
});
|
|
158
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
|
+
}
|
|
159
206
|
/**
|
|
160
207
|
* Monta o payload na forma snake_case que o oidc-provider espera/persiste —
|
|
161
208
|
* verificada contra o que o registro dinâmico (RFC 7591) grava. As chaves de
|
|
@@ -193,6 +240,12 @@ export class AdminClientsService {
|
|
|
193
240
|
payload.backchannel_logout_session_required = input.backchannelLogoutSessionRequired;
|
|
194
241
|
}
|
|
195
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
|
+
}
|
|
196
249
|
return payload;
|
|
197
250
|
}
|
|
198
251
|
/** Projeta um payload persistido para a forma exibida no console admin. */
|
|
@@ -209,6 +262,8 @@ export class AdminClientsService {
|
|
|
209
262
|
applicationType: p.application_type === 'native' ? 'native' : 'web',
|
|
210
263
|
clientName: typeof p.client_name === 'string' && p.client_name ? p.client_name : null,
|
|
211
264
|
logoUri: typeof p.logo_uri === 'string' && p.logo_uri ? p.logo_uri : null,
|
|
265
|
+
disabled: isClientDisabled(p),
|
|
266
|
+
serviceTenant: serviceTenantOf(p),
|
|
212
267
|
};
|
|
213
268
|
if (p.backchannel_logout_uri) {
|
|
214
269
|
result.backchannelLogoutUri = p.backchannel_logout_uri;
|
|
@@ -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;
|