@adonis-agora/authkit-server 0.74.0 → 0.76.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/README.md +3 -0
- package/build/host/views/account/apps.edge +30 -0
- package/build/host/views/agents/consent.edge +60 -0
- package/build/host/views/agents/done.edge +24 -0
- package/build/host/views/consent.edge +9 -1
- package/build/host/views/partials/styles.edge +1 -1
- package/build/index.d.ts +9 -0
- package/build/index.js +4 -0
- package/build/providers/authkit_server_provider.js +75 -50
- package/build/src/agents/agent_identity.d.ts +33 -0
- package/build/src/agents/agent_identity.js +156 -0
- package/build/src/agents/config.d.ts +99 -0
- package/build/src/agents/config.js +101 -0
- package/build/src/agents/delegation_service.d.ts +154 -0
- package/build/src/agents/delegation_service.js +394 -0
- package/build/src/agents/delegation_store.d.ts +93 -0
- package/build/src/agents/delegation_store.js +222 -0
- package/build/src/agents/middleware.d.ts +73 -0
- package/build/src/agents/middleware.js +113 -0
- package/build/src/agents/protocol.d.ts +62 -0
- package/build/src/agents/protocol.js +51 -0
- package/build/src/agents/runtime.d.ts +36 -0
- package/build/src/agents/runtime.js +65 -0
- package/build/src/agents/signer.d.ts +34 -0
- package/build/src/agents/signer.js +70 -0
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/audit/audit_sink.js +5 -0
- package/build/src/controllers/authorization_server_metadata_controller.d.ts +10 -0
- package/build/src/controllers/authorization_server_metadata_controller.js +20 -0
- package/build/src/define_config.d.ts +34 -3
- package/build/src/define_config.js +35 -1
- package/build/src/host/access_token_verifier.d.ts +7 -0
- package/build/src/host/access_token_verifier.js +8 -0
- package/build/src/host/account_api/account_api_controller.d.ts +12 -0
- package/build/src/host/account_api/account_api_controller.js +40 -0
- package/build/src/host/admin_api/dto.d.ts +1 -1
- package/build/src/host/admin_sessions_service.d.ts +2 -0
- package/build/src/host/admin_sessions_service.js +18 -0
- package/build/src/host/auth_host_config.d.ts +4 -0
- package/build/src/host/bearer_account.d.ts +17 -0
- package/build/src/host/bearer_account.js +10 -0
- package/build/src/host/client_names.d.ts +11 -0
- package/build/src/host/client_names.js +12 -0
- package/build/src/host/console_session.js +3 -5
- package/build/src/host/controllers/account_apps_controller.d.ts +2 -0
- package/build/src/host/controllers/account_apps_controller.js +39 -3
- package/build/src/host/controllers/account_orgs_controller.js +2 -1
- package/build/src/host/controllers/account_session_controller.js +2 -1
- package/build/src/host/controllers/agent_consent_controller.d.ts +17 -0
- package/build/src/host/controllers/agent_consent_controller.js +138 -0
- package/build/src/host/controllers/agent_oauth_controller.d.ts +23 -0
- package/build/src/host/controllers/agent_oauth_controller.js +110 -0
- package/build/src/host/controllers/interaction_controller.js +28 -0
- package/build/src/host/csrf.d.ts +8 -18
- package/build/src/host/csrf.js +28 -2
- package/build/src/host/i18n.d.ts +54 -0
- package/build/src/host/i18n.js +54 -0
- package/build/src/host/impersonation_session.d.ts +5 -0
- package/build/src/host/impersonation_session.js +37 -4
- package/build/src/host/oidc_bearer_guard.js +10 -1
- package/build/src/host/redirect_exact.d.ts +12 -0
- package/build/src/host/redirect_exact.js +17 -0
- package/build/src/host/register_auth_host.js +48 -7
- package/build/src/host/request_url.d.ts +10 -0
- package/build/src/host/request_url.js +17 -0
- package/build/src/host/sudo/methods/magic_link.js +2 -1
- package/build/src/host/sudo/runtime.js +3 -2
- package/build/src/host/sudo_mode.js +4 -4
- package/build/src/mcp/mcp_oauth.d.ts +85 -0
- package/build/src/mcp/mcp_oauth.js +154 -0
- package/build/src/provider/build_provider.js +20 -1
- package/build/src/provider/oidc_service.d.ts +8 -0
- package/build/src/provider/oidc_service.js +11 -0
- package/build/src/provider/token_exchange.d.ts +26 -0
- package/build/src/provider/token_exchange.js +44 -3
- package/build/src/schema/ensure.js +92 -0
- package/build/stubs/ui/react/pages/consent.tsx +17 -1
- package/package.json +1 -1
- package/stubs/ui/react/pages/consent.tsx +17 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CompactSign, createLocalJWKSet, importJWK, SignJWT, } from 'jose';
|
|
2
|
+
/** O PACT só admite estes algoritmos nos tokens de delegação e recibos (§5.4). */
|
|
3
|
+
const DELEGATION_ALGS = ['ES256', 'RS256'];
|
|
4
|
+
function algOf(jwk) {
|
|
5
|
+
if (DELEGATION_ALGS.includes(jwk.alg))
|
|
6
|
+
return jwk.alg;
|
|
7
|
+
if (jwk.alg)
|
|
8
|
+
return null;
|
|
9
|
+
// JWKS inline sem `alg`: infere pelo tipo da chave.
|
|
10
|
+
if (jwk.kty === 'EC' && jwk.crv === 'P-256')
|
|
11
|
+
return 'ES256';
|
|
12
|
+
if (jwk.kty === 'RSA')
|
|
13
|
+
return 'RS256';
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
/** Lança quando o JWKS não tem nenhuma chave que possa assinar tokens de delegação. */
|
|
17
|
+
export function assertDelegationSigningKey(jwks) {
|
|
18
|
+
if (!jwks.keys.some((k) => algOf(k) !== null)) {
|
|
19
|
+
throw new Error('authkit: personalAgents.delegation exige uma chave ES256 ou RS256 no keystore (PACT §5.4). Configure `jwks.algorithm` como RS256 ou ES256.');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Signer sobre o keystore do IdP: a primeira chave ES256/RS256 do JWKS assina
|
|
24
|
+
* (a primeira é a corrente após uma rotação); o JWKS público inteiro verifica,
|
|
25
|
+
* então tokens assinados antes de uma rotação continuam válidos no período de
|
|
26
|
+
* graça. Lê a fonte a cada chamada — rotação ao vivo não exige restart.
|
|
27
|
+
*/
|
|
28
|
+
export function keystoreSigner(source) {
|
|
29
|
+
const imported = new Map();
|
|
30
|
+
let localSet = null;
|
|
31
|
+
const current = async () => {
|
|
32
|
+
const jwk = source.signingJwks.keys.find((k) => algOf(k) !== null);
|
|
33
|
+
if (!jwk) {
|
|
34
|
+
throw new Error('authkit: personal agents exigem uma chave ES256 ou RS256 no keystore (PACT §5.4). Configure `jwks.algorithm` como RS256 ou ES256.');
|
|
35
|
+
}
|
|
36
|
+
const alg = algOf(jwk);
|
|
37
|
+
const cacheKey = `${jwk.kid ?? ''}:${alg}`;
|
|
38
|
+
let key = imported.get(cacheKey);
|
|
39
|
+
if (!key) {
|
|
40
|
+
key = importJWK(jwk, alg);
|
|
41
|
+
imported.set(cacheKey, key);
|
|
42
|
+
}
|
|
43
|
+
return { key: await key, alg, kid: jwk.kid };
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
algorithms: [...DELEGATION_ALGS],
|
|
47
|
+
async signJwt(payload, { issuedAt, expiresIn }) {
|
|
48
|
+
const { key, alg, kid } = await current();
|
|
49
|
+
const iat = Math.floor(issuedAt.getTime() / 1000);
|
|
50
|
+
return new SignJWT(payload)
|
|
51
|
+
.setProtectedHeader({ alg, typ: 'JWT', ...(kid ? { kid } : {}) })
|
|
52
|
+
.setIssuedAt(iat)
|
|
53
|
+
.setExpirationTime(iat + expiresIn)
|
|
54
|
+
.sign(key);
|
|
55
|
+
},
|
|
56
|
+
async signJson(value) {
|
|
57
|
+
const { key, alg, kid } = await current();
|
|
58
|
+
return new CompactSign(new TextEncoder().encode(JSON.stringify(value)))
|
|
59
|
+
.setProtectedHeader({ alg, ...(kid ? { kid } : {}) })
|
|
60
|
+
.sign(key);
|
|
61
|
+
},
|
|
62
|
+
keySet() {
|
|
63
|
+
const jwks = source.publicJwks;
|
|
64
|
+
if (!localSet || localSet.jwks !== jwks) {
|
|
65
|
+
localSet = { jwks, set: createLocalJWKSet(jwks) };
|
|
66
|
+
}
|
|
67
|
+
return localSet.set;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* varre os `audit.record({ type: ... })` do `src/` e falha quando um evento
|
|
9
9
|
* emitido não está aqui.
|
|
10
10
|
*/
|
|
11
|
-
export declare const AUDIT_EVENT_TYPES: readonly ['login.success', 'login.failure', 'signup', 'password_reset.issued', 'password_reset.consumed', 'pat.issued', 'pat.revoked', 'pat.used', 'impersonation', 'impersonation.panel_viewed', 'impersonation.stopped', 'mfa.enabled', 'mfa.disabled', 'mfa.recovery_codes_regenerated', 'account.locked', 'passkey.registered', 'passkey.removed', 'email_verification.issued', 'email_verification.consumed', 'client.created', 'client.updated', 'client.deleted', 'session.revoked_all', 'password.changed', 'password.rehashed', 'email.change_requested', 'email.changed', 'login.new_ip_notified', 'login.new_device', 'login.otp_sent', 'login.otp_verified', 'login.otp_failed', 'login.otp_invalidated', 'bot_protection.rejected', 'grant.revoked_by_user', 'user.created', 'user.password_reset_sent', 'user.disabled', 'user.enabled', 'user.deleted', 'profile.updated', 'account.deleted', 'account.exported', 'keys.rotated', 'organization.created', 'organization.updated', 'organization.deleted', 'organization.member_added', 'organization.member_removed', 'organization.member_role_changed', 'organization.member_role_updated', 'organization.switched', 'organization.deactivated', 'organization.invitation_sent', 'organization.invitation_accepted', 'organization.invitation_revoked', 'email_change.requested', 'email_change.confirmed', 'email_change.cancelled', 'security_notice.sent', 'settings.updated', 'maintenance.enabled', 'maintenance.disabled', 'trusted_device.revoked', 'password.expired_change_forced', 'otp.locked', 'otp.unlocked', 'otp.unlock_failed', 'sudo.confirmed', 'session.single_enforced', 'account.expired_login_blocked', 'account.expiration_warned', 'login.magic_link_sent', 'session.revoked', 'account.signed_out_all', 'client.secret_regenerated', 'roles_catalog.updated'];
|
|
11
|
+
export declare const AUDIT_EVENT_TYPES: readonly ['login.success', 'login.failure', 'signup', 'password_reset.issued', 'password_reset.consumed', 'pat.issued', 'pat.revoked', 'pat.used', 'impersonation', 'impersonation.panel_viewed', 'impersonation.stopped', 'mfa.enabled', 'mfa.disabled', 'mfa.recovery_codes_regenerated', 'account.locked', 'passkey.registered', 'passkey.removed', 'email_verification.issued', 'email_verification.consumed', 'client.created', 'client.updated', 'client.deleted', 'session.revoked_all', 'password.changed', 'password.rehashed', 'email.change_requested', 'email.changed', 'login.new_ip_notified', 'login.new_device', 'login.otp_sent', 'login.otp_verified', 'login.otp_failed', 'login.otp_invalidated', 'bot_protection.rejected', 'grant.revoked_by_user', 'user.created', 'user.password_reset_sent', 'user.disabled', 'user.enabled', 'user.deleted', 'profile.updated', 'account.deleted', 'account.exported', 'keys.rotated', 'organization.created', 'organization.updated', 'organization.deleted', 'organization.member_added', 'organization.member_removed', 'organization.member_role_changed', 'organization.member_role_updated', 'organization.switched', 'organization.deactivated', 'organization.invitation_sent', 'organization.invitation_accepted', 'organization.invitation_revoked', 'email_change.requested', 'email_change.confirmed', 'email_change.cancelled', 'security_notice.sent', 'settings.updated', 'maintenance.enabled', 'maintenance.disabled', 'trusted_device.revoked', 'password.expired_change_forced', 'otp.locked', 'otp.unlocked', 'otp.unlock_failed', 'sudo.confirmed', 'session.single_enforced', 'account.expired_login_blocked', 'account.expiration_warned', 'login.magic_link_sent', 'session.revoked', 'account.signed_out_all', 'client.secret_regenerated', 'roles_catalog.updated', 'agent.delegation_approved', 'agent.delegation_denied', 'agent.grant_revoked'];
|
|
12
12
|
/** Tipos de eventos de auditoria relevantes para segurança emitidos pelo IdP. */
|
|
13
13
|
export type AuditEventType = (typeof AUDIT_EVENT_TYPES)[number];
|
|
14
14
|
/**
|
|
@@ -108,4 +108,9 @@ export const AUDIT_EVENT_TYPES = [
|
|
|
108
108
|
'account.signed_out_all',
|
|
109
109
|
'client.secret_regenerated',
|
|
110
110
|
'roles_catalog.updated',
|
|
111
|
+
// Personal agents (PACT §5): o usuário aprovou/negou a delegação pedida por um
|
|
112
|
+
// agente, ou revogou um grant no console. `clientId` = issuer do agente.
|
|
113
|
+
'agent.delegation_approved',
|
|
114
|
+
'agent.delegation_denied',
|
|
115
|
+
'agent.grant_revoked',
|
|
111
116
|
];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* Metadata do servidor de autorização no caminho da RFC 8414 §3.1: com o issuer em
|
|
4
|
+
* `https://app/oidc`, o documento fica em `https://app/.well-known/oauth-authorization-server/oidc`
|
|
5
|
+
* — onde os clientes MCP o procuram primeiro. O provider só o serve sob o próprio mount
|
|
6
|
+
* (`/oidc/.well-known/...`); aqui a requisição é reescrita para lá.
|
|
7
|
+
*/
|
|
8
|
+
export default class AuthorizationServerMetadataController {
|
|
9
|
+
handle(ctx: HttpContext): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata do servidor de autorização no caminho da RFC 8414 §3.1: com o issuer em
|
|
3
|
+
* `https://app/oidc`, o documento fica em `https://app/.well-known/oauth-authorization-server/oidc`
|
|
4
|
+
* — onde os clientes MCP o procuram primeiro. O provider só o serve sob o próprio mount
|
|
5
|
+
* (`/oidc/.well-known/...`); aqui a requisição é reescrita para lá.
|
|
6
|
+
*/
|
|
7
|
+
export default class AuthorizationServerMetadataController {
|
|
8
|
+
async handle(ctx) {
|
|
9
|
+
const service = await ctx.containerResolver.make('authkit.server');
|
|
10
|
+
const req = ctx.request.request;
|
|
11
|
+
const res = ctx.response.response;
|
|
12
|
+
const issuerPath = new URL(service.config.issuer).pathname.replace(/\/+$/, '');
|
|
13
|
+
req.url = `${issuerPath}/.well-known/oauth-authorization-server`;
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
res.on('finish', resolve);
|
|
16
|
+
res.on('close', resolve);
|
|
17
|
+
service.callback(req, res);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { AccessTokenFormat, AccessTokensConfig, ClientConfig, JwksConfig, K
|
|
|
2
2
|
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
3
|
import type { AccountStore, AuthAccount } from './accounts/account_store.js';
|
|
4
4
|
import { type AdapterFactory, adapters, type OidcAdapterClass } from './adapters/factory.js';
|
|
5
|
+
import { type PersonalAgentsConfigInput, type ResolvedPersonalAgentsConfig } from './agents/config.js';
|
|
5
6
|
import type { AuditSink } from './audit/audit_sink.js';
|
|
6
7
|
import { type EventsConfigInput } from './events/dispatcher.js';
|
|
7
8
|
import { type BotProtectionConfigInput, type ResolvedBotProtectionConfig } from './host/bot_protection.js';
|
|
@@ -13,6 +14,7 @@ import { type OtpLoginConfigInput, type ResolvedOtpLoginConfig } from './host/ot
|
|
|
13
14
|
import type { AuthHostOptions } from './host/register_auth_host.js';
|
|
14
15
|
import type { SudoMethod } from './host/sudo/types.js';
|
|
15
16
|
import { type ResolvedTrustedDevicesConfig, type TrustedDevicesConfigInput } from './host/trusted_device.js';
|
|
17
|
+
import { type McpOAuthConfigInput, type ResolvedMcpOAuthConfig } from './mcp/mcp_oauth.js';
|
|
16
18
|
import type { PatStore } from './pat/pat_store.js';
|
|
17
19
|
import { type RedirectUriPolicy, type ResolvedRedirectUriPolicy, type ValidateRegistrationHook } from './provider/registration_policy.js';
|
|
18
20
|
export type { AuthAccount };
|
|
@@ -622,11 +624,19 @@ export interface AdminConfigInput {
|
|
|
622
624
|
* não altera mais o valor.
|
|
623
625
|
*/
|
|
624
626
|
impersonation?: boolean;
|
|
627
|
+
/**
|
|
628
|
+
* Permite impersonar uma conta que também tem um dos `roles` de admin. Com
|
|
629
|
+
* `false`, o token-exchange recusa alvo admin (`invalid_grant`): um admin não
|
|
630
|
+
* assume a identidade de outro. Default: **`true`** (back-compat; virar o
|
|
631
|
+
* default é decisão de major, mesma regra de `impersonation`).
|
|
632
|
+
*/
|
|
633
|
+
impersonateAdmins?: boolean;
|
|
625
634
|
}
|
|
626
635
|
export interface ResolvedAdminConfig {
|
|
627
636
|
enabled: boolean;
|
|
628
637
|
roles: string[];
|
|
629
638
|
impersonation: boolean;
|
|
639
|
+
impersonateAdmins: boolean;
|
|
630
640
|
}
|
|
631
641
|
export declare function resolveAdmin(input?: AdminConfigInput): ResolvedAdminConfig;
|
|
632
642
|
/**
|
|
@@ -967,6 +977,14 @@ export interface AuthServerConfigInput {
|
|
|
967
977
|
dynamicRegistration?: DynamicRegistrationConfigInput;
|
|
968
978
|
/** Device Authorization Grant (RFC 8628). Default: desligado. */
|
|
969
979
|
deviceFlow?: DeviceFlowConfigInput;
|
|
980
|
+
/**
|
|
981
|
+
* Personal agents (PACT — https://openpactprotocol.org): plataformas de agente
|
|
982
|
+
* que falam com este app em nome de um usuário. Identidade por JWT assinado
|
|
983
|
+
* pelo agente (JWKS) e, com `delegation`, consentimento do usuário via device
|
|
984
|
+
* flow para o agente agir na conta dele dentro de scopes do app. Default:
|
|
985
|
+
* desligado.
|
|
986
|
+
*/
|
|
987
|
+
personalAgents?: PersonalAgentsConfigInput;
|
|
970
988
|
/** Uploads (avatar) via o `@adonisjs/drive` do app. Default: drive default, 5MB. */
|
|
971
989
|
uploads?: UploadsConfigInput;
|
|
972
990
|
/** DPoP — sender-constrained tokens (RFC 9449). Default: desligado. */
|
|
@@ -1016,6 +1034,14 @@ export interface AuthServerConfigInput {
|
|
|
1016
1034
|
* `resources` mapeia resource indicators (RFC 8707) para audiences/scopes/formato/TTL por API.
|
|
1017
1035
|
*/
|
|
1018
1036
|
accessTokens?: AccessTokensConfig;
|
|
1037
|
+
/**
|
|
1038
|
+
* Login OAuth de clientes MCP (Claude Code, Claude, ChatGPT, VS Code, Cursor): `true` liga o
|
|
1039
|
+
* registro dinâmico restrito aos redirects deles, o refresh token desses clientes e os
|
|
1040
|
+
* `resource` dos servidores MCP — os de `resources` e os que se registram em runtime (o MCP do
|
|
1041
|
+
* `@adonis-agora/agent`). Um `dynamicRegistration` declarado continua mandando no registro.
|
|
1042
|
+
* Ver `src/mcp/mcp_oauth.ts`. Default: desligado.
|
|
1043
|
+
*/
|
|
1044
|
+
mcp?: boolean | McpOAuthConfigInput;
|
|
1019
1045
|
/**
|
|
1020
1046
|
* Console admin do IdP (B6). Default: desligado.
|
|
1021
1047
|
*
|
|
@@ -1048,11 +1074,13 @@ export interface AuthServerConfigInput {
|
|
|
1048
1074
|
*/
|
|
1049
1075
|
resolveGeo?: ResolveGeo;
|
|
1050
1076
|
/**
|
|
1051
|
-
* Gestão automática do schema das
|
|
1077
|
+
* Gestão automática do schema das ONZE tabelas do authkit:
|
|
1052
1078
|
* `authkit_oidc_payloads`, `auth_settings`, `auth_password_history`,
|
|
1053
|
-
* `auth_mfa`, `auth_session_revocations
|
|
1079
|
+
* `auth_mfa`, `auth_session_revocations`, as três de organizations
|
|
1054
1080
|
* (`auth_organizations`, `auth_organization_members`,
|
|
1055
|
-
* `auth_organization_invitations`)
|
|
1081
|
+
* `auth_organization_invitations`) e as três de personal agents
|
|
1082
|
+
* (`auth_agent_device_codes`, `auth_agent_grants`,
|
|
1083
|
+
* `auth_agent_refresh_tokens`). Ver `TABLES` em `schema/ensure.ts` — a
|
|
1056
1084
|
* lista aqui existe para o leitor, mas quem manda é aquele array.
|
|
1057
1085
|
*
|
|
1058
1086
|
* FORA desta gestão: `authkit_keystore`, criada sob demanda pelo
|
|
@@ -1206,6 +1234,8 @@ export interface ResolvedServerConfig {
|
|
|
1206
1234
|
dynamicRegistration: ResolvedDynamicRegistrationConfig;
|
|
1207
1235
|
/** Device Authorization Grant resolvido (default desligado). */
|
|
1208
1236
|
deviceFlow: ResolvedDeviceFlowConfig;
|
|
1237
|
+
/** Personal agents resolvido (undefined = desligado). */
|
|
1238
|
+
personalAgents?: ResolvedPersonalAgentsConfig;
|
|
1209
1239
|
/** Uploads resolvido (avatar via drive do app; sempre presente). */
|
|
1210
1240
|
uploads: ResolvedUploadsConfig;
|
|
1211
1241
|
/** DPoP resolvido (default desligado). */
|
|
@@ -1228,6 +1258,7 @@ export interface ResolvedServerConfig {
|
|
|
1228
1258
|
registration: ResolvedRegistrationConfig;
|
|
1229
1259
|
/** Access Tokens resolvido (RFC 9068; default opaque). */
|
|
1230
1260
|
accessTokens: ResolvedAccessTokensConfig;
|
|
1261
|
+
mcp: ResolvedMcpOAuthConfig;
|
|
1231
1262
|
/** Console admin resolvido (sempre presente; default desligado). */
|
|
1232
1263
|
admin: ResolvedAdminConfig;
|
|
1233
1264
|
/** Admin REST API resolvida (sempre presente; default desligada). */
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { configProvider } from '@adonisjs/core';
|
|
2
2
|
import { adapters } from './adapters/factory.js';
|
|
3
|
+
import { resolvePersonalAgentsConfig, } from './agents/config.js';
|
|
4
|
+
import { assertDelegationSigningKey } from './agents/signer.js';
|
|
3
5
|
import { composeAuditSink, resolveEvents } from './events/dispatcher.js';
|
|
4
6
|
import { resolveBotProtection, } from './host/bot_protection.js';
|
|
5
7
|
import { resolveBranding } from './host/branding.js';
|
|
@@ -13,6 +15,7 @@ import { generateJwks } from './keys/jwks_manager.js';
|
|
|
13
15
|
import { KeystoreCodec } from './keys/keystore_codec.js';
|
|
14
16
|
import { loadEncryptionService } from './keys/keystore_crypto.js';
|
|
15
17
|
import { KeystoreManager, resolveKeystoreVault } from './keys/keystore_manager.js';
|
|
18
|
+
import { mcpClientRegistration, resolveMcpOAuth, } from './mcp/mcp_oauth.js';
|
|
16
19
|
import { OPEN_REGISTRATION_REDIRECT_POLICY, resolveRedirectUriPolicy, } from './provider/registration_policy.js';
|
|
17
20
|
export { adapters };
|
|
18
21
|
const RATE_LIMIT_DEFAULTS = {
|
|
@@ -81,6 +84,20 @@ export function resolveDynamicRegistration(input) {
|
|
|
81
84
|
validateRegistration: input?.validateRegistration,
|
|
82
85
|
};
|
|
83
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* O registro dinâmico que `mcp` liga quando o app não declarou o seu: aberto, só para os
|
|
89
|
+
* redirects dos clientes MCP, pedindo `offline_access` para quem quer refresh token.
|
|
90
|
+
*/
|
|
91
|
+
function mcpDynamicRegistration(mcp) {
|
|
92
|
+
const resolved = resolveMcpOAuth(mcp);
|
|
93
|
+
if (!resolved.enabled)
|
|
94
|
+
return undefined;
|
|
95
|
+
return {
|
|
96
|
+
enabled: true,
|
|
97
|
+
redirectUriPolicy: resolved.redirectUriPolicy,
|
|
98
|
+
validateRegistration: (metadata) => mcpClientRegistration(metadata),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
84
101
|
export function resolveDeviceFlow(input) {
|
|
85
102
|
return { enabled: input?.enabled ?? false };
|
|
86
103
|
}
|
|
@@ -174,6 +191,7 @@ export function resolveAdmin(input) {
|
|
|
174
191
|
// Default `true`: preserva o comportamento histórico (grant sempre
|
|
175
192
|
// registrado). Ver o docblock de `AdminConfigInput.impersonation`.
|
|
176
193
|
impersonation: input?.impersonation !== false,
|
|
194
|
+
impersonateAdmins: input?.impersonateAdmins !== false,
|
|
177
195
|
};
|
|
178
196
|
}
|
|
179
197
|
/** Lê API keys de `AUTHKIT_ADMIN_API_KEY` (uma ou várias, separadas por vírgula). */
|
|
@@ -316,6 +334,20 @@ export function defineConfig(config) {
|
|
|
316
334
|
else {
|
|
317
335
|
jwks = { keys: jwksConfig.keys ?? [] };
|
|
318
336
|
}
|
|
337
|
+
// Personal agents com delegação assinam tokens e recibos com este keystore,
|
|
338
|
+
// e o PACT só admite ES256/RS256 — falha no boot, não na primeira troca de token.
|
|
339
|
+
const personalAgents = resolvePersonalAgentsConfig(config.personalAgents);
|
|
340
|
+
if (personalAgents?.delegation)
|
|
341
|
+
assertDelegationSigningKey(jwks);
|
|
342
|
+
// Debaixo do mountPath, a rota curinga do oidc-provider (`${mount}/*`)
|
|
343
|
+
// engoliria as rotas dos agentes — e o `authkitCsrfExceptions` (que isenta
|
|
344
|
+
// tudo que contém o mountPath) tiraria o CSRF da tela de consentimento.
|
|
345
|
+
const oidcMount = (config.mountPath ?? '/oidc').replace(/\/+$/, '');
|
|
346
|
+
if (personalAgents &&
|
|
347
|
+
oidcMount &&
|
|
348
|
+
(personalAgents.prefix === oidcMount || personalAgents.prefix.startsWith(`${oidcMount}/`))) {
|
|
349
|
+
throw new Error(`authkit: personalAgents.prefix "${personalAgents.prefix}" não pode ficar debaixo do mountPath do OIDC ("${oidcMount}").`);
|
|
350
|
+
}
|
|
319
351
|
// BACKSTOP DE SUDO. Um host cujo `sudo.methods` não tem um único método
|
|
320
352
|
// satisfazível por conta sem senha fica bricado para TODA operação sob
|
|
321
353
|
// `requireSudo` — e hoje isso só se descobre quando um usuário não consegue
|
|
@@ -404,8 +436,9 @@ export function defineConfig(config) {
|
|
|
404
436
|
// top-level não os fornece — consumidor declara UMA vez. Top-level ainda vence.
|
|
405
437
|
mfaIssuer: effectiveMfaIssuer,
|
|
406
438
|
webauthn: resolveWebauthn(config.issuer, effectiveMfaIssuer, config.webauthn ?? config.accountStore?.__webauthn),
|
|
407
|
-
dynamicRegistration: resolveDynamicRegistration(config.dynamicRegistration),
|
|
439
|
+
dynamicRegistration: resolveDynamicRegistration(config.dynamicRegistration ?? mcpDynamicRegistration(config.mcp)),
|
|
408
440
|
deviceFlow: resolveDeviceFlow(config.deviceFlow),
|
|
441
|
+
personalAgents,
|
|
409
442
|
uploads: resolveUploads(config.uploads),
|
|
410
443
|
dpop: resolveDpop(config.dpop),
|
|
411
444
|
par: resolvePar(config.par),
|
|
@@ -423,6 +456,7 @@ export function defineConfig(config) {
|
|
|
423
456
|
: undefined,
|
|
424
457
|
registration: resolveRegistration(config.registration),
|
|
425
458
|
accessTokens: resolveAccessTokens(config.issuer, config.accessTokens),
|
|
459
|
+
mcp: resolveMcpOAuth(config.mcp),
|
|
426
460
|
admin: resolveAdmin(config.admin),
|
|
427
461
|
adminApi: resolveAdminApi(config.adminApi),
|
|
428
462
|
organizations: resolveOrganizations(config.organizations),
|
|
@@ -18,6 +18,13 @@ export interface VerifiedAccessToken {
|
|
|
18
18
|
exp: number | null;
|
|
19
19
|
/** Id do token (`jti`), quando conhecido. */
|
|
20
20
|
jti: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Quem está AGINDO quando o token é de impersonation (RFC 8693 `act.sub`, o
|
|
23
|
+
* admin que trocou o próprio token pelo do `sub`). `null` num token comum.
|
|
24
|
+
* É o que deixa o resource server tratar a request como impersonation
|
|
25
|
+
* (`impersonationState`/`realAccountId`) em vez de confundi-la com o alvo.
|
|
26
|
+
*/
|
|
27
|
+
actor: string | null;
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
23
30
|
* Estratégia de verificação de access token plugável no `oidcBearerGuard`.
|
|
@@ -10,6 +10,11 @@ function rejectedJwt(error) {
|
|
|
10
10
|
!(error instanceof joseErrors.JWKSTimeout) &&
|
|
11
11
|
!(error instanceof joseErrors.JWKSInvalid));
|
|
12
12
|
}
|
|
13
|
+
/** `act.sub` (RFC 8693 §4.1) de um payload/`extra`/introspecção, ou `null`. */
|
|
14
|
+
function actorOf(source) {
|
|
15
|
+
const act = source?.act;
|
|
16
|
+
return typeof act?.sub === 'string' && act.sub ? act.sub : null;
|
|
17
|
+
}
|
|
13
18
|
/** `header.payload.signature` em base64url — o formato compacto de um JWS. */
|
|
14
19
|
const JWS_COMPACT = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
15
20
|
function looksLikeJwt(token) {
|
|
@@ -44,6 +49,7 @@ function fromJwtPayload(payload) {
|
|
|
44
49
|
audience: toAudience(payload.aud),
|
|
45
50
|
exp: typeof payload.exp === 'number' ? payload.exp : null,
|
|
46
51
|
jti: typeof payload.jti === 'string' ? payload.jti : null,
|
|
52
|
+
actor: actorOf(payload),
|
|
47
53
|
};
|
|
48
54
|
}
|
|
49
55
|
/**
|
|
@@ -121,6 +127,7 @@ export function inProcessAccessTokenVerifier(resolveIssuer) {
|
|
|
121
127
|
audience: toAudience(at.aud),
|
|
122
128
|
exp: typeof at.exp === 'number' ? at.exp : null,
|
|
123
129
|
jti: typeof at.jti === 'string' ? at.jti : null,
|
|
130
|
+
actor: actorOf(at.extra),
|
|
124
131
|
};
|
|
125
132
|
},
|
|
126
133
|
async issue(accountId, options = {}) {
|
|
@@ -212,6 +219,7 @@ export function remoteAccessTokenVerifier(options) {
|
|
|
212
219
|
audience: toAudience(body.aud),
|
|
213
220
|
exp: typeof body.exp === 'number' ? body.exp : null,
|
|
214
221
|
jti: typeof body.jti === 'string' ? body.jti : null,
|
|
222
|
+
actor: actorOf(body),
|
|
215
223
|
};
|
|
216
224
|
};
|
|
217
225
|
return {
|
|
@@ -119,6 +119,7 @@ export default class AccountApiController {
|
|
|
119
119
|
grants: number;
|
|
120
120
|
accessTokens: number;
|
|
121
121
|
refreshTokens: number;
|
|
122
|
+
agentGrants?: number;
|
|
122
123
|
ok: boolean;
|
|
123
124
|
}>;
|
|
124
125
|
/**
|
|
@@ -133,6 +134,7 @@ export default class AccountApiController {
|
|
|
133
134
|
grants: number;
|
|
134
135
|
accessTokens: number;
|
|
135
136
|
refreshTokens: number;
|
|
137
|
+
agentGrants?: number;
|
|
136
138
|
ok: boolean;
|
|
137
139
|
signedOut: boolean;
|
|
138
140
|
}>;
|
|
@@ -141,6 +143,7 @@ export default class AccountApiController {
|
|
|
141
143
|
supported: boolean;
|
|
142
144
|
apps: {
|
|
143
145
|
clientId: string;
|
|
146
|
+
name: string;
|
|
144
147
|
accessTokens: number;
|
|
145
148
|
refreshTokens: number;
|
|
146
149
|
}[];
|
|
@@ -151,6 +154,15 @@ export default class AccountApiController {
|
|
|
151
154
|
grants: number;
|
|
152
155
|
accessTokens: number;
|
|
153
156
|
refreshTokens: number;
|
|
157
|
+
agentGrants?: number;
|
|
158
|
+
ok: boolean;
|
|
159
|
+
}>;
|
|
160
|
+
/** Personal agents com delegação ativa na conta (scopes aprovados). */
|
|
161
|
+
listAgents(ctx: HttpContext): Promise<void | {
|
|
162
|
+
agents: import("../../agents/delegation_service.ts").DelegationGrantSummary[];
|
|
163
|
+
}>;
|
|
164
|
+
/** Revoga a delegação de um personal agent (efeito imediato nos tokens). */
|
|
165
|
+
revokeAgent(ctx: HttpContext): Promise<void | {
|
|
154
166
|
ok: boolean;
|
|
155
167
|
}>;
|
|
156
168
|
/** Status completo do MFA: TOTP + passkeys + recovery. */
|
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
* GET /account/api/orgs/:id → detalhe de org (requer membership)
|
|
28
28
|
* GET /account/api/orgs/invitations → convites pendentes
|
|
29
29
|
*/
|
|
30
|
+
import { clientDisplayNames } from '../client_names.js';
|
|
30
31
|
import '../augmentations.js';
|
|
31
32
|
import { supportsAccountSecurity, supportsLoginMethodsPreference, supportsMagicLink, supportsOrganizations, supportsPasskeys, supportsProfile, supportsRecoveryCodeCount, supportsRecoveryCodeRegeneration, } from '../../accounts/account_store.js';
|
|
33
|
+
import { personalAgentsFor } from '../../agents/runtime.js';
|
|
32
34
|
import { PasswordPolicyError } from '../../password/password_manager.js';
|
|
33
35
|
import { accountPath } from '../account_paths.js';
|
|
34
36
|
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
@@ -575,12 +577,14 @@ export default class AccountApiController {
|
|
|
575
577
|
return { supported: false, apps: [] };
|
|
576
578
|
}
|
|
577
579
|
const grants = await adminSessions.listGrants(userId);
|
|
580
|
+
const names = await clientDisplayNames(service, grants.map((g) => g.clientId).filter((id) => !!id));
|
|
578
581
|
return {
|
|
579
582
|
supported: true,
|
|
580
583
|
apps: grants
|
|
581
584
|
.filter((g) => !!g.clientId)
|
|
582
585
|
.map((g) => ({
|
|
583
586
|
clientId: g.clientId,
|
|
587
|
+
name: names.get(g.clientId) ?? g.clientId,
|
|
584
588
|
accessTokens: g.accessTokens,
|
|
585
589
|
refreshTokens: g.refreshTokens,
|
|
586
590
|
})),
|
|
@@ -609,6 +613,42 @@ export default class AccountApiController {
|
|
|
609
613
|
});
|
|
610
614
|
return { ok: true, ...result };
|
|
611
615
|
}
|
|
616
|
+
// ─── GET /account/api/agents ─────────────────────────────────────────────
|
|
617
|
+
/** Personal agents com delegação ativa na conta (scopes aprovados). */
|
|
618
|
+
async listAgents(ctx) {
|
|
619
|
+
const runtime = await personalAgentsFor(ctx);
|
|
620
|
+
if (!runtime?.delegation) {
|
|
621
|
+
return ctx.response
|
|
622
|
+
.status(404)
|
|
623
|
+
.send(apiErr('capability_unsupported', 'Personal agent delegation is not enabled.'));
|
|
624
|
+
}
|
|
625
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
626
|
+
const grants = await runtime.delegation.listGrants(userId);
|
|
627
|
+
return { agents: grants };
|
|
628
|
+
}
|
|
629
|
+
// ─── DELETE /account/api/agents/:id ──────────────────────────────────────
|
|
630
|
+
/** Revoga a delegação de um personal agent (efeito imediato nos tokens). */
|
|
631
|
+
async revokeAgent(ctx) {
|
|
632
|
+
const runtime = await personalAgentsFor(ctx);
|
|
633
|
+
if (!runtime?.delegation) {
|
|
634
|
+
return ctx.response
|
|
635
|
+
.status(404)
|
|
636
|
+
.send(apiErr('capability_unsupported', 'Personal agent delegation is not enabled.'));
|
|
637
|
+
}
|
|
638
|
+
const service = await ctx.containerResolver.make('authkit.server');
|
|
639
|
+
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
640
|
+
const grantId = ctx.request.param('id');
|
|
641
|
+
if (!(await runtime.delegation.revokeGrant(userId, grantId))) {
|
|
642
|
+
return ctx.response.status(404).send(apiErr('not_found', 'Grant not found.'));
|
|
643
|
+
}
|
|
644
|
+
await service.config.audit?.record({
|
|
645
|
+
type: 'agent.grant_revoked',
|
|
646
|
+
accountId: userId,
|
|
647
|
+
ip: ctx.request.ip?.() ?? null,
|
|
648
|
+
metadata: { grantId },
|
|
649
|
+
});
|
|
650
|
+
return { ok: true };
|
|
651
|
+
}
|
|
612
652
|
// ─── GET /account/api/mfa ────────────────────────────────────────────────
|
|
613
653
|
/** Status completo do MFA: TOTP + passkeys + recovery. */
|
|
614
654
|
async mfaStatus(ctx) {
|
|
@@ -49,7 +49,7 @@ export declare function grantDto(grant: AdminGrant): {
|
|
|
49
49
|
};
|
|
50
50
|
export declare function auditDto(event: StoredAuditEvent): {
|
|
51
51
|
id: string;
|
|
52
|
-
type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.locked" | "account.signed_out_all" | "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";
|
|
52
|
+
type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.locked" | "account.signed_out_all" | "agent.delegation_approved" | "agent.delegation_denied" | "agent.grant_revoked" | "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";
|
|
53
53
|
accountId: string | null;
|
|
54
54
|
email: string | null;
|
|
55
55
|
clientId: string | null;
|
|
@@ -45,6 +45,8 @@ export interface RevokeResult {
|
|
|
45
45
|
grants: number;
|
|
46
46
|
accessTokens: number;
|
|
47
47
|
refreshTokens: number;
|
|
48
|
+
/** Delegações de personal agents revogadas junto (ausente quando nenhuma). */
|
|
49
|
+
agentGrants?: number;
|
|
48
50
|
}
|
|
49
51
|
export declare class AdminSessionsService {
|
|
50
52
|
#private;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getBootedApp } from '../../services/booted_app.js';
|
|
2
2
|
import { pickModelAdapterClass } from '../adapters/factory.js';
|
|
3
|
+
import { buildPersonalAgentsRuntime } from '../agents/runtime.js';
|
|
3
4
|
import { normalizeActiveOrg } from './active_org_cookie.js';
|
|
4
5
|
/**
|
|
5
6
|
* Serviço de inspeção/revogação das SESSÕES e GRANTS ativos de uma conta.
|
|
@@ -23,7 +24,9 @@ export class AdminSessionsService {
|
|
|
23
24
|
#accountStore;
|
|
24
25
|
/** Conexão Lucid das tabelas authkit (schema `auth`) — onde vive auth_session_revocations. */
|
|
25
26
|
#schemaConnection;
|
|
27
|
+
#oidc;
|
|
26
28
|
constructor(oidc) {
|
|
29
|
+
this.#oidc = oidc;
|
|
27
30
|
this.#AdapterClass = oidc.config.AdapterClass;
|
|
28
31
|
// `Session`/`Grant`/tokens vivem no adapter da sessão quando `session:`
|
|
29
32
|
// está configurado (default: o mesmo do `AdapterClass`). `??` cobre
|
|
@@ -81,6 +84,17 @@ export class AdminSessionsService {
|
|
|
81
84
|
await this.#logRevocationFailure(accountId, error);
|
|
82
85
|
}
|
|
83
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Revogação total também corta os personal agents: "sair de todas as sessões",
|
|
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. Sem `personalAgents.delegation` é no-op — e aí nem
|
|
91
|
+
* resolve o banco. Diferente do `recordSubRevocation`, uma falha AQUI propaga:
|
|
92
|
+
* não há outra camada que corte esses tokens.
|
|
93
|
+
*/
|
|
94
|
+
async #revokeAgentGrants(accountId) {
|
|
95
|
+
const runtime = await buildPersonalAgentsRuntime(this.#oidc, () => getBootedApp().container.make('lucid.db'));
|
|
96
|
+
return runtime?.delegation ? runtime.delegation.revokeAllGrants(accountId) : 0;
|
|
97
|
+
}
|
|
84
98
|
/**
|
|
85
99
|
* Loga (best-effort, sem nunca lançar) a falha de {@link recordSubRevocation}. Prefere o
|
|
86
100
|
* `logger` do container; se ele não puder ser resolvido (ex.: o próprio `getBootedApp()` foi o
|
|
@@ -240,11 +254,13 @@ export class AdminSessionsService {
|
|
|
240
254
|
// no default (`now`) — não há sessão nova a proteger nesse caminho.
|
|
241
255
|
const cutoff = typeof preserved?.loginTs === 'number' ? new Date((preserved.loginTs - 1) * 1000) : undefined;
|
|
242
256
|
await this.recordSubRevocation(accountId, cutoff);
|
|
257
|
+
const agentGrants = await this.#revokeAgentGrants(accountId);
|
|
243
258
|
return {
|
|
244
259
|
sessions: sessionsToRevoke.length,
|
|
245
260
|
grants: grants.length,
|
|
246
261
|
accessTokens,
|
|
247
262
|
refreshTokens,
|
|
263
|
+
...(agentGrants > 0 ? { agentGrants } : {}),
|
|
248
264
|
};
|
|
249
265
|
}
|
|
250
266
|
/**
|
|
@@ -277,11 +293,13 @@ export class AdminSessionsService {
|
|
|
277
293
|
}
|
|
278
294
|
// Revogação total → propaga p/ clients cookie-based via tabela compartilhada (instantâneo).
|
|
279
295
|
await this.recordSubRevocation(accountId);
|
|
296
|
+
const agentGrants = await this.#revokeAgentGrants(accountId);
|
|
280
297
|
return {
|
|
281
298
|
sessions: sessions.length,
|
|
282
299
|
grants: grants.length,
|
|
283
300
|
accessTokens,
|
|
284
301
|
refreshTokens,
|
|
302
|
+
...(agentGrants > 0 ? { agentGrants } : {}),
|
|
285
303
|
};
|
|
286
304
|
}
|
|
287
305
|
/**
|
|
@@ -35,6 +35,10 @@ export interface AuthHostRuntimeConfig {
|
|
|
35
35
|
* Ver `deriveLockedRouteOptions`.
|
|
36
36
|
*/
|
|
37
37
|
lockedRouteOptions?: PolicyRouteOption[];
|
|
38
|
+
/** Personal agents ligados → prefixo das rotas deles (`/agents`). */
|
|
39
|
+
personalAgents?: {
|
|
40
|
+
prefix: string;
|
|
41
|
+
};
|
|
38
42
|
/**
|
|
39
43
|
* API headless (Clerk-style). Presente quando o host a declarou — o provider o
|
|
40
44
|
* stash no boot para `registerAuthHost` montar as rotas sem reler o config inteiro.
|
|
@@ -7,3 +7,20 @@ export declare function clearBearerAccountId(ctx: object): void;
|
|
|
7
7
|
* `oidcBearerGuard` autenticou (ainda) a request.
|
|
8
8
|
*/
|
|
9
9
|
export declare function bearerAccountId(ctx: object): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Impersonation carregada pelo access token bearer desta request: o ator
|
|
12
|
+
* (`act.sub`, o admin) e a expiração do token. Gravada pelo `oidcBearerGuard`
|
|
13
|
+
* só quando o token é de impersonation; lida por `impersonationState` e
|
|
14
|
+
* `realAccountId` quando não há sessão.
|
|
15
|
+
*/
|
|
16
|
+
export interface BearerImpersonation {
|
|
17
|
+
actorId: string;
|
|
18
|
+
/** Epoch em segundos do `exp` do token, quando conhecido. */
|
|
19
|
+
exp: number | null;
|
|
20
|
+
/** `jti` do token trocado, quando conhecido (correlaciona com a auditoria). */
|
|
21
|
+
jti: string | null;
|
|
22
|
+
}
|
|
23
|
+
/** Registra que o token bearer desta request é de impersonation. */
|
|
24
|
+
export declare function setBearerImpersonation(ctx: object, value: BearerImpersonation): void;
|
|
25
|
+
/** Impersonation do token bearer desta request, ou `null`. */
|
|
26
|
+
export declare function bearerImpersonation(ctx: object): BearerImpersonation | null;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* barrel) depende dele.
|
|
12
12
|
*/
|
|
13
13
|
const bearerAccounts = new WeakMap();
|
|
14
|
+
const bearerImpersonations = new WeakMap();
|
|
14
15
|
/** Registra a conta autenticada via bearer nesta request. */
|
|
15
16
|
export function setBearerAccountId(ctx, accountId) {
|
|
16
17
|
bearerAccounts.set(ctx, accountId);
|
|
@@ -18,6 +19,7 @@ export function setBearerAccountId(ctx, accountId) {
|
|
|
18
19
|
/** Esquece a conta bearer desta request (falha de autenticação). */
|
|
19
20
|
export function clearBearerAccountId(ctx) {
|
|
20
21
|
bearerAccounts.delete(ctx);
|
|
22
|
+
bearerImpersonations.delete(ctx);
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* Id da conta autenticada via bearer nesta request, ou `null` quando nenhum
|
|
@@ -26,3 +28,11 @@ export function clearBearerAccountId(ctx) {
|
|
|
26
28
|
export function bearerAccountId(ctx) {
|
|
27
29
|
return bearerAccounts.get(ctx) ?? null;
|
|
28
30
|
}
|
|
31
|
+
/** Registra que o token bearer desta request é de impersonation. */
|
|
32
|
+
export function setBearerImpersonation(ctx, value) {
|
|
33
|
+
bearerImpersonations.set(ctx, value);
|
|
34
|
+
}
|
|
35
|
+
/** Impersonation do token bearer desta request, ou `null`. */
|
|
36
|
+
export function bearerImpersonation(ctx) {
|
|
37
|
+
return bearerImpersonations.get(ctx) ?? null;
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nome de exibição de cada client: o `client_name` que ele registrou (um cliente MCP registra o
|
|
3
|
+
* seu, ex.: "Claude Code"), senão o próprio `client_id`. Client que sumiu do adapter → o id.
|
|
4
|
+
*/
|
|
5
|
+
export declare function clientDisplayNames(service: {
|
|
6
|
+
provider: {
|
|
7
|
+
Client: {
|
|
8
|
+
find(id: string): Promise<any>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
}, clientIds: string[]): Promise<Map<string, string>>;
|