@adonis-agora/authkit-server 1.0.0-rc.1 → 1.0.0-rc.3
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 +34 -0
- package/README.md +4 -5
- package/build/commands/clients_create.d.ts +4 -0
- package/build/commands/clients_create.js +97 -0
- package/build/commands/commands.json +14 -0
- package/build/index.d.ts +8 -11
- package/build/index.js +5 -9
- package/build/providers/authkit_server_provider.js +6 -8
- 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 +6 -1
- package/build/src/define_config.d.ts +16 -12
- package/build/src/define_config.js +11 -7
- 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/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_clients_service.d.ts +20 -0
- package/build/src/host/admin_clients_service.js +55 -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_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 +6 -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 +3 -14
- package/build/src/host/i18n.js +15 -13
- package/build/src/host/register_auth_host.js +30 -61
- package/build/src/host/ui-dist/assets/index-u0bMxkLg.js +67 -0
- package/build/src/host/ui-dist/index.html +1 -1
- 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 +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/package.json +2 -2
- 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
|
@@ -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.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. */
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -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
|
|
@@ -17,6 +17,19 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
|
|
|
17
17
|
backchannelLogoutSessionRequired: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineBoolean>;
|
|
18
18
|
clientName: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
|
|
19
19
|
logoUri: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
|
|
20
|
+
serviceTenant: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").NullableModifier<import("@vinejs/vine").VineObject<{
|
|
21
|
+
partition: import("@vinejs/vine").VineString;
|
|
22
|
+
capabilities: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").VineArray<import("@vinejs/vine").VineString>>;
|
|
23
|
+
}, {
|
|
24
|
+
capabilities?: string[] | null | undefined;
|
|
25
|
+
partition: string;
|
|
26
|
+
}, {
|
|
27
|
+
capabilities?: string[] | undefined;
|
|
28
|
+
partition: string;
|
|
29
|
+
}, {
|
|
30
|
+
capabilities?: string[] | undefined;
|
|
31
|
+
partition: string;
|
|
32
|
+
}>>>;
|
|
20
33
|
}, {
|
|
21
34
|
applicationType?: "native" | "web" | null | undefined;
|
|
22
35
|
backchannelLogoutSessionRequired?: string | number | boolean | null | undefined;
|
|
@@ -27,6 +40,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
|
|
|
27
40
|
logoUri?: string | null | undefined;
|
|
28
41
|
postLogoutRedirectUris?: string[] | null | undefined;
|
|
29
42
|
redirectUris?: string[] | null | undefined;
|
|
43
|
+
serviceTenant?: {
|
|
44
|
+
capabilities?: string[] | null | undefined;
|
|
45
|
+
partition: string;
|
|
46
|
+
} | null | undefined;
|
|
30
47
|
tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | null | undefined;
|
|
31
48
|
}, {
|
|
32
49
|
applicationType?: "native" | "web" | undefined;
|
|
@@ -38,6 +55,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
|
|
|
38
55
|
logoUri?: string | null | undefined;
|
|
39
56
|
postLogoutRedirectUris?: string[] | undefined;
|
|
40
57
|
redirectUris?: string[] | undefined;
|
|
58
|
+
serviceTenant?: {
|
|
59
|
+
capabilities?: string[] | undefined;
|
|
60
|
+
partition: string;
|
|
61
|
+
} | null | undefined;
|
|
41
62
|
tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | undefined;
|
|
42
63
|
}, {
|
|
43
64
|
applicationType?: "native" | "web" | undefined;
|
|
@@ -49,6 +70,10 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
|
|
|
49
70
|
logoUri?: string | null | undefined;
|
|
50
71
|
postLogoutRedirectUris?: string[] | undefined;
|
|
51
72
|
redirectUris?: string[] | undefined;
|
|
73
|
+
serviceTenant?: {
|
|
74
|
+
capabilities?: string[] | undefined;
|
|
75
|
+
partition: string;
|
|
76
|
+
} | null | undefined;
|
|
52
77
|
tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | undefined;
|
|
53
78
|
}>, Record<string, any> | undefined>;
|
|
54
79
|
/** Forma validada de um client (saída do {@link clientInputValidator}). */
|
|
@@ -63,6 +88,10 @@ export type ClientInputFields = {
|
|
|
63
88
|
backchannelLogoutSessionRequired?: boolean;
|
|
64
89
|
clientName?: string | null;
|
|
65
90
|
logoUri?: string | null;
|
|
91
|
+
serviceTenant?: {
|
|
92
|
+
partition: string;
|
|
93
|
+
capabilities?: string[];
|
|
94
|
+
} | null;
|
|
66
95
|
};
|
|
67
96
|
/**
|
|
68
97
|
* Mapeia o input validado para um {@link ClientInput} COMPLETO (create), com defaults.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import vine from '@vinejs/vine';
|
|
2
|
+
import { CAPABILITY_PATTERN, MAX_CAPABILITIES, PARTITION_PATTERN, } from '../service_tenants/client_metadata.js';
|
|
2
3
|
import { APPLICATION_TYPES, redirectUriProblem } from './client_metadata.js';
|
|
3
4
|
import { normalizeEmailIdentifier } from './email_identifier.js';
|
|
4
5
|
/**
|
|
@@ -103,6 +104,18 @@ export const clientInputValidator = vine.compile(vine.object({
|
|
|
103
104
|
// `null` (update) removes them.
|
|
104
105
|
clientName: vine.string().trim().maxLength(200).nullable().optional(),
|
|
105
106
|
logoUri: vine.string().trim().url(URL_OPTS).nullable().optional(),
|
|
107
|
+
// Service tenant (`client_credentials` + partition claim). `null` (update) turns it
|
|
108
|
+
// back into a regular client.
|
|
109
|
+
serviceTenant: vine
|
|
110
|
+
.object({
|
|
111
|
+
partition: vine.string().trim().regex(PARTITION_PATTERN),
|
|
112
|
+
capabilities: vine
|
|
113
|
+
.array(vine.string().trim().regex(CAPABILITY_PATTERN))
|
|
114
|
+
.maxLength(MAX_CAPABILITIES)
|
|
115
|
+
.optional(),
|
|
116
|
+
})
|
|
117
|
+
.nullable()
|
|
118
|
+
.optional(),
|
|
106
119
|
}));
|
|
107
120
|
/**
|
|
108
121
|
* Mapeia o input validado para um {@link ClientInput} COMPLETO (create), com defaults.
|
|
@@ -111,6 +124,21 @@ export const clientInputValidator = vine.compile(vine.object({
|
|
|
111
124
|
*/
|
|
112
125
|
export function clientCreateInput(v) {
|
|
113
126
|
const applicationType = v.applicationType ?? 'web';
|
|
127
|
+
// A service tenant only uses client_credentials: that is its default grant.
|
|
128
|
+
if (v.serviceTenant) {
|
|
129
|
+
return {
|
|
130
|
+
clientId: v.clientId?.trim() || undefined,
|
|
131
|
+
applicationType,
|
|
132
|
+
redirectUris: v.redirectUris ?? [],
|
|
133
|
+
postLogoutRedirectUris: v.postLogoutRedirectUris ?? [],
|
|
134
|
+
grantTypes: v.grantTypes ?? ['client_credentials'],
|
|
135
|
+
tokenEndpointAuthMethod: v.tokenEndpointAuthMethod ?? 'client_secret_basic',
|
|
136
|
+
backchannelLogoutUri: v.backchannelLogoutUri || undefined,
|
|
137
|
+
clientName: v.clientName || undefined,
|
|
138
|
+
logoUri: v.logoUri || undefined,
|
|
139
|
+
serviceTenant: v.serviceTenant,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
114
142
|
return {
|
|
115
143
|
clientId: v.clientId?.trim() || undefined,
|
|
116
144
|
applicationType,
|
|
@@ -148,6 +176,8 @@ export function clientPartialInput(v) {
|
|
|
148
176
|
out.clientName = v.clientName?.trim() || null;
|
|
149
177
|
if (v.logoUri !== undefined)
|
|
150
178
|
out.logoUri = v.logoUri?.trim() || null;
|
|
179
|
+
if (v.serviceTenant !== undefined)
|
|
180
|
+
out.serviceTenant = v.serviceTenant;
|
|
151
181
|
return out;
|
|
152
182
|
}
|
|
153
183
|
// ─── Usuários ───────────────────────────────────────────────────────────────
|