@adonis-agora/authkit-server 0.90.2 → 1.0.0-rc.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/CHANGELOG.md +3637 -0
- package/build/commands/clients_create.d.ts +1 -0
- package/build/commands/clients_create.js +15 -2
- package/build/commands/commands.json +7 -0
- package/build/host/ui/admin.html +1 -1
- package/build/host/views/account/apps.edge +8 -3
- package/build/host/views/partials/styles.edge +1 -1
- package/build/index.d.ts +3 -3
- package/build/index.js +3 -5
- package/build/src/accounts/lucid_account_store.d.ts +0 -18
- package/build/src/accounts/lucid_account_store.js +7 -48
- package/build/src/accounts/lucid_store/mfa.js +1 -1
- package/build/src/accounts/lucid_store/organizations.d.ts +1 -1
- package/build/src/accounts/lucid_store/password_hygiene.d.ts +2 -3
- package/build/src/accounts/lucid_store/password_hygiene.js +2 -3
- package/build/src/accounts/lucid_store/shared.d.ts +0 -7
- package/build/src/accounts/lucid_store/shared.js +0 -14
- package/build/src/accounts/lucid_stores.d.ts +1 -1
- package/build/src/adapters/redis_adapter.js +12 -2
- package/build/src/define_config.d.ts +6 -12
- package/build/src/define_config.js +3 -2
- package/build/src/doctor/checks.d.ts +3 -4
- package/build/src/doctor/checks.js +5 -7
- package/build/src/host/account_api/account_api_controller.d.ts +2 -1
- package/build/src/host/account_api/account_api_controller.js +5 -11
- package/build/src/host/admin_api/api_clients_controller.d.ts +9 -3
- package/build/src/host/admin_api/dto.d.ts +3 -1
- package/build/src/host/admin_api/dto.js +3 -1
- package/build/src/host/admin_clients_service.d.ts +13 -5
- package/build/src/host/admin_clients_service.js +18 -5
- package/build/src/host/admin_console/admin_shell_controller.d.ts +16 -1
- package/build/src/host/admin_console/admin_shell_controller.js +21 -7
- package/build/src/host/admin_console/console_clients_controller.d.ts +21 -2
- package/build/src/host/admin_console/console_clients_controller.js +9 -0
- package/build/src/host/admin_console/console_impersonation_controller.js +3 -8
- package/build/src/host/admin_console/console_overview_controller.d.ts +2 -2
- package/build/src/host/admin_console/console_overview_controller.js +4 -2
- package/build/src/host/admin_validators.d.ts +15 -11
- package/build/src/host/admin_validators.js +16 -9
- package/build/src/host/client_names.d.ts +9 -2
- package/build/src/host/client_names.js +11 -3
- package/build/src/host/consent_params.d.ts +7 -0
- package/build/src/host/consent_params.js +27 -0
- package/build/src/host/controllers/account_apps_controller.js +4 -3
- package/build/src/host/controllers/account_confirm_controller.d.ts +0 -7
- package/build/src/host/controllers/account_confirm_controller.js +0 -6
- package/build/src/host/controllers/account_orgs_controller.d.ts +0 -50
- package/build/src/host/controllers/account_orgs_controller.js +7 -113
- package/build/src/host/controllers/account_security_controller.js +7 -11
- package/build/src/host/controllers/agent_consent_controller.js +23 -5
- package/build/src/host/controllers/interaction_controller.js +6 -2
- package/build/src/host/controllers/poppy_consent_controller.js +33 -7
- package/build/src/host/controllers/registration_controller.js +7 -2
- package/build/src/host/i18n.d.ts +0 -4
- package/build/src/host/i18n.js +0 -6
- package/build/src/host/impersonation.d.ts +7 -15
- package/build/src/host/impersonation.js +7 -16
- package/build/src/host/interaction_recovery.js +4 -1
- package/build/src/host/login_attempt.js +5 -1
- package/build/src/host/middleware/account_auth.js +6 -0
- package/build/src/host/rate_limit.js +5 -1
- package/build/src/host/register_auth_host.d.ts +6 -0
- package/build/src/host/register_auth_host.js +32 -8
- package/build/src/host/renderers/inertia_renderer.d.ts +9 -21
- package/build/src/host/renderers/inertia_renderer.js +5 -6
- package/build/src/host/screen_props.d.ts +309 -0
- package/build/src/host/screen_props.js +11 -0
- package/build/src/host/sudo_mode.d.ts +5 -11
- package/build/src/host/sudo_mode.js +5 -11
- package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +67 -0
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/password/password_manager.js +5 -0
- package/build/src/provider/build_provider.js +0 -34
- package/build/src/provider/oidc_service.d.ts +0 -2
- package/build/src/provider/oidc_service.js +1 -12
- package/build/src/provider/token_exchange.d.ts +1 -3
- package/build/src/provider/token_exchange.js +1 -1
- package/build/stubs/config/authkit_react.stub +3 -3
- package/package.json +9 -8
- package/skills/authkit-interactions/SKILL.md +32 -5
- package/stubs/config/authkit_react.stub +3 -3
- package/build/src/commands/import_clients.d.ts +0 -40
- package/build/src/commands/import_clients.js +0 -81
- package/build/src/host/ui-dist/assets/index-DUVBDAyh.js +0 -67
- package/build/src/mixins/with_mfa.d.ts +0 -35
- package/build/src/mixins/with_mfa.js +0 -21
|
@@ -27,7 +27,7 @@
|
|
|
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 {
|
|
30
|
+
import { clientDisplayInfo } from '../client_names.js';
|
|
31
31
|
import '../augmentations.js';
|
|
32
32
|
import { supportsAccountSecurity, supportsLoginMethodsPreference, supportsMagicLink, supportsOrganizations, supportsPasskeys, supportsProfile, supportsRecoveryCodeCount, supportsRecoveryCodeRegeneration, } from '../../accounts/account_store.js';
|
|
33
33
|
import { personalAgentsFor } from '../../agents/runtime.js';
|
|
@@ -403,13 +403,6 @@ export default class AccountApiController {
|
|
|
403
403
|
oldEmail: account?.email ?? '',
|
|
404
404
|
});
|
|
405
405
|
}
|
|
406
|
-
else if (cfg.mail?.onEmailVerification) {
|
|
407
|
-
await cfg.mail.onEmailVerification({
|
|
408
|
-
email: newEmail,
|
|
409
|
-
verifyUrl: confirmUrl,
|
|
410
|
-
token: issued.token,
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
406
|
else {
|
|
414
407
|
await sendEmailChangeConfirmationEmail(ctx, { email: newEmail, confirmUrl });
|
|
415
408
|
}
|
|
@@ -577,14 +570,15 @@ export default class AccountApiController {
|
|
|
577
570
|
return { supported: false, apps: [] };
|
|
578
571
|
}
|
|
579
572
|
const grants = await adminSessions.listGrants(userId);
|
|
580
|
-
const
|
|
573
|
+
const info = await clientDisplayInfo(service, grants.map((g) => g.clientId).filter((id) => !!id));
|
|
581
574
|
return {
|
|
582
575
|
supported: true,
|
|
583
576
|
apps: grants
|
|
584
577
|
.filter((g) => !!g.clientId)
|
|
585
578
|
.map((g) => ({
|
|
586
579
|
clientId: g.clientId,
|
|
587
|
-
name:
|
|
580
|
+
name: info.get(g.clientId)?.name ?? g.clientId,
|
|
581
|
+
logoUrl: info.get(g.clientId)?.logoUrl ?? null,
|
|
588
582
|
accessTokens: g.accessTokens,
|
|
589
583
|
refreshTokens: g.refreshTokens,
|
|
590
584
|
})),
|
|
@@ -934,7 +928,7 @@ export default class AccountApiController {
|
|
|
934
928
|
return { ok: true, revoked: patId };
|
|
935
929
|
}
|
|
936
930
|
// ─── GET /account/api/orgs ───────────────────────────────────────────────
|
|
937
|
-
/** Lista orgs do usuário logado.
|
|
931
|
+
/** Lista orgs do usuário logado. */
|
|
938
932
|
async listOrgs(ctx) {
|
|
939
933
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
940
934
|
const cfg = service.config;
|
|
@@ -10,26 +10,30 @@ export default class ApiClientsController {
|
|
|
10
10
|
data: {
|
|
11
11
|
clientId: string;
|
|
12
12
|
confidential: boolean;
|
|
13
|
-
|
|
13
|
+
grantTypes: string[];
|
|
14
14
|
redirectUris: string[];
|
|
15
15
|
postLogoutRedirectUris: string[];
|
|
16
16
|
tokenEndpointAuthMethod: string;
|
|
17
17
|
applicationType: import("../client_metadata.js").ApplicationType;
|
|
18
18
|
backchannelLogoutUri: string | null;
|
|
19
19
|
backchannelLogoutSessionRequired: boolean;
|
|
20
|
+
clientName: string | null;
|
|
21
|
+
logoUri: string | null;
|
|
20
22
|
}[];
|
|
21
23
|
canList: boolean;
|
|
22
24
|
}>;
|
|
23
25
|
show(ctx: HttpContext): Promise<void | {
|
|
24
26
|
clientId: string;
|
|
25
27
|
confidential: boolean;
|
|
26
|
-
|
|
28
|
+
grantTypes: string[];
|
|
27
29
|
redirectUris: string[];
|
|
28
30
|
postLogoutRedirectUris: string[];
|
|
29
31
|
tokenEndpointAuthMethod: string;
|
|
30
32
|
applicationType: import("../client_metadata.js").ApplicationType;
|
|
31
33
|
backchannelLogoutUri: string | null;
|
|
32
34
|
backchannelLogoutSessionRequired: boolean;
|
|
35
|
+
clientName: string | null;
|
|
36
|
+
logoUri: string | null;
|
|
33
37
|
}>;
|
|
34
38
|
store(ctx: HttpContext): Promise<void | {
|
|
35
39
|
clientId: string;
|
|
@@ -38,13 +42,15 @@ export default class ApiClientsController {
|
|
|
38
42
|
update(ctx: HttpContext): Promise<void | {
|
|
39
43
|
clientId: string;
|
|
40
44
|
confidential: boolean;
|
|
41
|
-
|
|
45
|
+
grantTypes: string[];
|
|
42
46
|
redirectUris: string[];
|
|
43
47
|
postLogoutRedirectUris: string[];
|
|
44
48
|
tokenEndpointAuthMethod: string;
|
|
45
49
|
applicationType: import("../client_metadata.js").ApplicationType;
|
|
46
50
|
backchannelLogoutUri: string | null;
|
|
47
51
|
backchannelLogoutSessionRequired: boolean;
|
|
52
|
+
clientName: string | null;
|
|
53
|
+
logoUri: string | null;
|
|
48
54
|
}>;
|
|
49
55
|
regenerateSecret(ctx: HttpContext): Promise<void | {
|
|
50
56
|
clientId: any;
|
|
@@ -31,13 +31,15 @@ export declare function userDtoWithRoles(cfg: Parameters<typeof resolveAccountRo
|
|
|
31
31
|
export declare function clientDto(client: AdminClient): {
|
|
32
32
|
clientId: string;
|
|
33
33
|
confidential: boolean;
|
|
34
|
-
|
|
34
|
+
grantTypes: string[];
|
|
35
35
|
redirectUris: string[];
|
|
36
36
|
postLogoutRedirectUris: string[];
|
|
37
37
|
tokenEndpointAuthMethod: string;
|
|
38
38
|
applicationType: import("../client_metadata.ts").ApplicationType;
|
|
39
39
|
backchannelLogoutUri: string | null;
|
|
40
40
|
backchannelLogoutSessionRequired: boolean;
|
|
41
|
+
clientName: string | null;
|
|
42
|
+
logoUri: string | null;
|
|
41
43
|
};
|
|
42
44
|
/** Inclui o secret (mostrado UMA vez) em create/regenerate. */
|
|
43
45
|
export declare function createdClientDto(created: CreatedClient): {
|
|
@@ -23,13 +23,15 @@ export function clientDto(client) {
|
|
|
23
23
|
return {
|
|
24
24
|
clientId: client.clientId,
|
|
25
25
|
confidential: client.confidential,
|
|
26
|
-
|
|
26
|
+
grantTypes: client.grantTypes,
|
|
27
27
|
redirectUris: client.redirectUris,
|
|
28
28
|
postLogoutRedirectUris: client.postLogoutRedirectUris,
|
|
29
29
|
tokenEndpointAuthMethod: client.tokenEndpointAuthMethod,
|
|
30
30
|
applicationType: client.applicationType,
|
|
31
31
|
backchannelLogoutUri: client.backchannelLogoutUri ?? null,
|
|
32
32
|
backchannelLogoutSessionRequired: client.backchannelLogoutSessionRequired ?? false,
|
|
33
|
+
clientName: client.clientName,
|
|
34
|
+
logoUri: client.logoUri,
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
/** Inclui o secret (mostrado UMA vez) em create/regenerate. */
|
|
@@ -18,8 +18,13 @@ export interface ClientInput {
|
|
|
18
18
|
/**
|
|
19
19
|
* Endpoint de OIDC Back-Channel Logout do RP. Quando definido, o IdP envia um
|
|
20
20
|
* `logout_token` para esta URI ao encerrar a sessão/grant do usuário (RFC 7644).
|
|
21
|
+
* Num update, `null` remove a URI; ausente (`undefined`) preserva a atual.
|
|
21
22
|
*/
|
|
22
|
-
backchannelLogoutUri?: string;
|
|
23
|
+
backchannelLogoutUri?: string | null;
|
|
24
|
+
/** `client_name`: the name people see on consent and authorized-app screens. `null` removes it. */
|
|
25
|
+
clientName?: string | null;
|
|
26
|
+
/** `logo_uri`: the client's logo URL. `null` removes it. */
|
|
27
|
+
logoUri?: string | null;
|
|
23
28
|
/**
|
|
24
29
|
* Quando `true`, exige que o `logout_token` inclua a claim `sid`. Ignorado quando
|
|
25
30
|
* `backchannelLogoutUri` não está definido.
|
|
@@ -30,7 +35,7 @@ export interface ClientInput {
|
|
|
30
35
|
export interface AdminClient {
|
|
31
36
|
clientId: string;
|
|
32
37
|
confidential: boolean;
|
|
33
|
-
|
|
38
|
+
grantTypes: string[];
|
|
34
39
|
redirectUris: string[];
|
|
35
40
|
postLogoutRedirectUris: string[];
|
|
36
41
|
tokenEndpointAuthMethod: string;
|
|
@@ -40,6 +45,10 @@ export interface AdminClient {
|
|
|
40
45
|
backchannelLogoutUri?: string;
|
|
41
46
|
/** Exige sid no logout_token (opcional). */
|
|
42
47
|
backchannelLogoutSessionRequired?: boolean;
|
|
48
|
+
/** `client_name` registrado, ou `null`. */
|
|
49
|
+
clientName: string | null;
|
|
50
|
+
/** `logo_uri` registrado, ou `null`. */
|
|
51
|
+
logoUri: string | null;
|
|
43
52
|
}
|
|
44
53
|
/** Resultado de uma criação: o client + o secret em claro (mostrado UMA vez). */
|
|
45
54
|
export interface CreatedClient {
|
|
@@ -73,9 +82,8 @@ export declare class AdminClientsService {
|
|
|
73
82
|
*/
|
|
74
83
|
create(input: ClientInput): Promise<CreatedClient>;
|
|
75
84
|
/**
|
|
76
|
-
* Cria um client
|
|
77
|
-
*
|
|
78
|
-
* secret original deve ser preservado para não quebrar apps já configurados.
|
|
85
|
+
* Cria um client com um `clientSecret` fornecido externamente (em vez de gerar
|
|
86
|
+
* um aleatório) — ex.: para manter o secret que os RPs já usam.
|
|
79
87
|
*
|
|
80
88
|
* Para clients públicos (auth method `none`), `providedSecret` é ignorado.
|
|
81
89
|
* Lança se o `clientId` já existir no adapter — chame `find` antes.
|
|
@@ -60,9 +60,8 @@ export class AdminClientsService {
|
|
|
60
60
|
return { clientId, clientSecret };
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* Cria um client
|
|
64
|
-
*
|
|
65
|
-
* secret original deve ser preservado para não quebrar apps já configurados.
|
|
63
|
+
* Cria um client com um `clientSecret` fornecido externamente (em vez de gerar
|
|
64
|
+
* um aleatório) — ex.: para manter o secret que os RPs já usam.
|
|
66
65
|
*
|
|
67
66
|
* Para clients públicos (auth method `none`), `providedSecret` é ignorado.
|
|
68
67
|
* Lança se o `clientId` já existir no adapter — chame `find` antes.
|
|
@@ -98,9 +97,17 @@ export class AdminClientsService {
|
|
|
98
97
|
existing.token_endpoint_auth_method ??
|
|
99
98
|
'client_secret_basic',
|
|
100
99
|
applicationType: input.applicationType ?? existing.application_type ?? 'web',
|
|
101
|
-
backchannelLogoutUri: input.backchannelLogoutUri
|
|
100
|
+
backchannelLogoutUri: input.backchannelLogoutUri === null
|
|
101
|
+
? undefined
|
|
102
|
+
: (input.backchannelLogoutUri ?? existing.backchannel_logout_uri),
|
|
102
103
|
backchannelLogoutSessionRequired: input.backchannelLogoutSessionRequired ??
|
|
103
104
|
existing.backchannel_logout_session_required,
|
|
105
|
+
clientName: input.clientName === null
|
|
106
|
+
? undefined
|
|
107
|
+
: (input.clientName ?? existing.client_name),
|
|
108
|
+
logoUri: input.logoUri === null
|
|
109
|
+
? undefined
|
|
110
|
+
: (input.logoUri ?? existing.logo_uri),
|
|
104
111
|
};
|
|
105
112
|
const confidential = !PUBLIC_AUTH_METHODS.has(merged.tokenEndpointAuthMethod);
|
|
106
113
|
// Mantém o secret atual se continua confidencial; se virou public, remove-o.
|
|
@@ -173,6 +180,10 @@ export class AdminClientsService {
|
|
|
173
180
|
// sempre (sem `application_type` o oidc-provider assume `web`).
|
|
174
181
|
if (input.applicationType === 'native')
|
|
175
182
|
payload.application_type = 'native';
|
|
183
|
+
if (input.clientName)
|
|
184
|
+
payload.client_name = input.clientName;
|
|
185
|
+
if (input.logoUri)
|
|
186
|
+
payload.logo_uri = input.logoUri;
|
|
176
187
|
if (input.backchannelLogoutUri) {
|
|
177
188
|
payload.backchannel_logout_uri = input.backchannelLogoutUri;
|
|
178
189
|
if (input.backchannelLogoutSessionRequired !== undefined) {
|
|
@@ -188,11 +199,13 @@ export class AdminClientsService {
|
|
|
188
199
|
const result = {
|
|
189
200
|
clientId: row.clientId,
|
|
190
201
|
confidential: !!p.client_secret,
|
|
191
|
-
|
|
202
|
+
grantTypes: p.grant_types ?? ['authorization_code', 'refresh_token'],
|
|
192
203
|
redirectUris: p.redirect_uris ?? [],
|
|
193
204
|
postLogoutRedirectUris: p.post_logout_redirect_uris ?? [],
|
|
194
205
|
tokenEndpointAuthMethod: authMethod,
|
|
195
206
|
applicationType: p.application_type === 'native' ? 'native' : 'web',
|
|
207
|
+
clientName: typeof p.client_name === 'string' && p.client_name ? p.client_name : null,
|
|
208
|
+
logoUri: typeof p.logo_uri === 'string' && p.logo_uri ? p.logo_uri : null,
|
|
196
209
|
};
|
|
197
210
|
if (p.backchannel_logout_uri) {
|
|
198
211
|
result.backchannelLogoutUri = p.backchannel_logout_uri;
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
/** Id of the JSON data block the SPA reads its config from. */
|
|
3
|
+
export declare const ADMIN_CONFIG_ELEMENT_ID = "authkit-admin-config";
|
|
4
|
+
/**
|
|
5
|
+
* Serializes the config for an HTML data block: `<`, `>`, `&` and the JS line
|
|
6
|
+
* separators are escaped so no value can close the `<script>` element.
|
|
7
|
+
*/
|
|
8
|
+
export declare function serializeConfigBlock(config: Record<string, unknown>): string;
|
|
9
|
+
/**
|
|
10
|
+
* Embeds the config as a `<script type="application/json">` data block right
|
|
11
|
+
* after `<body>`. A data block is never executed, so a strict CSP
|
|
12
|
+
* (`script-src 'self' 'nonce-…'`) leaves it alone; the SPA parses it.
|
|
13
|
+
* Also rewrites `/__AUTHKIT_BASE__/` → actual adminBase so all asset URLs
|
|
14
|
+
* produced by Vite (base: '/__AUTHKIT_BASE__/') resolve correctly at runtime.
|
|
15
|
+
*/
|
|
16
|
+
export declare function prepareShell(html: string, config: Record<string, unknown>): string;
|
|
2
17
|
/**
|
|
3
18
|
* Controla o serving do console admin React (Missão B).
|
|
4
19
|
*
|
|
@@ -6,7 +21,7 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
6
21
|
* `serveAsset` — serve arquivos estáticos do build (JS/CSS/fonts/images) com cache.
|
|
7
22
|
*
|
|
8
23
|
* O shell HTML é o index.html produzido pelo Vite build em `build/host/ui-dist/`.
|
|
9
|
-
* O servidor
|
|
24
|
+
* O servidor embute a config num data block JSON (`#authkit-admin-config`) e reescreve o placeholder de base dinâmica
|
|
10
25
|
* `/__AUTHKIT_BASE__/` → `${adminBase}/assets/` para que todos os imports de assets
|
|
11
26
|
* Vite resolvam corretamente independente do prefixo admin configurado.
|
|
12
27
|
*
|
|
@@ -50,19 +50,33 @@ then restart the server.</p>
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
// ─── Config injection ─────────────────────────────────────────────────────────
|
|
53
|
+
/** Id of the JSON data block the SPA reads its config from. */
|
|
54
|
+
export const ADMIN_CONFIG_ELEMENT_ID = 'authkit-admin-config';
|
|
53
55
|
/**
|
|
54
|
-
*
|
|
56
|
+
* Serializes the config for an HTML data block: `<`, `>`, `&` and the JS line
|
|
57
|
+
* separators are escaped so no value can close the `<script>` element.
|
|
58
|
+
*/
|
|
59
|
+
export function serializeConfigBlock(config) {
|
|
60
|
+
return JSON.stringify(config)
|
|
61
|
+
.replace(/</g, '\\u003c')
|
|
62
|
+
.replace(/>/g, '\\u003e')
|
|
63
|
+
.replace(/&/g, '\\u0026')
|
|
64
|
+
.replace(/\u2028/g, '\\u2028')
|
|
65
|
+
.replace(/\u2029/g, '\\u2029');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Embeds the config as a `<script type="application/json">` data block right
|
|
69
|
+
* after `<body>`. A data block is never executed, so a strict CSP
|
|
70
|
+
* (`script-src 'self' 'nonce-…'`) leaves it alone; the SPA parses it.
|
|
55
71
|
* Also rewrites `/__AUTHKIT_BASE__/` → actual adminBase so all asset URLs
|
|
56
72
|
* produced by Vite (base: '/__AUTHKIT_BASE__/') resolve correctly at runtime.
|
|
57
73
|
*/
|
|
58
|
-
function prepareShell(html, config) {
|
|
74
|
+
export function prepareShell(html, config) {
|
|
59
75
|
const adminBase = config.adminBase;
|
|
60
76
|
// 1. Rewrite the Vite placeholder base in ALL occurrences (src/href attributes).
|
|
61
|
-
// Vite puts `/__AUTHKIT_BASE__/assets/foo.js` so we rewrite `/__AUTHKIT_BASE__/`
|
|
62
|
-
// to `${adminBase}/` so asset URLs become `${adminBase}/assets/foo.js`.
|
|
63
77
|
const rewritten = html.replaceAll('/__AUTHKIT_BASE__/', `${adminBase}/`);
|
|
64
|
-
// 2. Inject config
|
|
65
|
-
const script = `<script
|
|
78
|
+
// 2. Inject the config data block right after <body>.
|
|
79
|
+
const script = `<script type="application/json" id="${ADMIN_CONFIG_ELEMENT_ID}">${serializeConfigBlock(config)}</script>`;
|
|
66
80
|
const bodyIdx = rewritten.indexOf('<body');
|
|
67
81
|
if (bodyIdx === -1)
|
|
68
82
|
return script + rewritten;
|
|
@@ -97,7 +111,7 @@ const MIME = {
|
|
|
97
111
|
* `serveAsset` — serve arquivos estáticos do build (JS/CSS/fonts/images) com cache.
|
|
98
112
|
*
|
|
99
113
|
* O shell HTML é o index.html produzido pelo Vite build em `build/host/ui-dist/`.
|
|
100
|
-
* O servidor
|
|
114
|
+
* O servidor embute a config num data block JSON (`#authkit-admin-config`) e reescreve o placeholder de base dinâmica
|
|
101
115
|
* `/__AUTHKIT_BASE__/` → `${adminBase}/assets/` para que todos os imports de assets
|
|
102
116
|
* Vite resolvam corretamente independente do prefixo admin configurado.
|
|
103
117
|
*
|
|
@@ -5,6 +5,7 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
5
5
|
*
|
|
6
6
|
* GET {prefix}/api/clients → lista clients dinâmicos
|
|
7
7
|
* POST {prefix}/api/clients → criar client (secret retornado UMA vez)
|
|
8
|
+
* GET {prefix}/api/clients/:id → um client (404 se não existir)
|
|
8
9
|
* PATCH {prefix}/api/clients/:id → atualizar metadata
|
|
9
10
|
* DELETE {prefix}/api/clients/:id → remover client
|
|
10
11
|
* POST {prefix}/api/clients/:id/regenerate-secret → gerar novo secret
|
|
@@ -15,16 +16,32 @@ export default class ConsoleClientsController {
|
|
|
15
16
|
data: {
|
|
16
17
|
clientId: string;
|
|
17
18
|
confidential: boolean;
|
|
18
|
-
|
|
19
|
+
grantTypes: string[];
|
|
19
20
|
redirectUris: string[];
|
|
20
21
|
postLogoutRedirectUris: string[];
|
|
21
22
|
tokenEndpointAuthMethod: string;
|
|
22
23
|
applicationType: import("../client_metadata.js").ApplicationType;
|
|
23
24
|
backchannelLogoutUri: string | null;
|
|
24
25
|
backchannelLogoutSessionRequired: boolean;
|
|
26
|
+
clientName: string | null;
|
|
27
|
+
logoUri: string | null;
|
|
25
28
|
}[];
|
|
26
29
|
canList: boolean;
|
|
27
30
|
}>;
|
|
31
|
+
/** GET {prefix}/api/clients/:id */
|
|
32
|
+
show(ctx: HttpContext): Promise<void | {
|
|
33
|
+
clientId: string;
|
|
34
|
+
confidential: boolean;
|
|
35
|
+
grantTypes: string[];
|
|
36
|
+
redirectUris: string[];
|
|
37
|
+
postLogoutRedirectUris: string[];
|
|
38
|
+
tokenEndpointAuthMethod: string;
|
|
39
|
+
applicationType: import("../client_metadata.js").ApplicationType;
|
|
40
|
+
backchannelLogoutUri: string | null;
|
|
41
|
+
backchannelLogoutSessionRequired: boolean;
|
|
42
|
+
clientName: string | null;
|
|
43
|
+
logoUri: string | null;
|
|
44
|
+
}>;
|
|
28
45
|
/** POST {prefix}/api/clients */
|
|
29
46
|
store(ctx: HttpContext): Promise<void | {
|
|
30
47
|
clientId: string;
|
|
@@ -34,13 +51,15 @@ export default class ConsoleClientsController {
|
|
|
34
51
|
update(ctx: HttpContext): Promise<void | {
|
|
35
52
|
clientId: string;
|
|
36
53
|
confidential: boolean;
|
|
37
|
-
|
|
54
|
+
grantTypes: string[];
|
|
38
55
|
redirectUris: string[];
|
|
39
56
|
postLogoutRedirectUris: string[];
|
|
40
57
|
tokenEndpointAuthMethod: string;
|
|
41
58
|
applicationType: import("../client_metadata.js").ApplicationType;
|
|
42
59
|
backchannelLogoutUri: string | null;
|
|
43
60
|
backchannelLogoutSessionRequired: boolean;
|
|
61
|
+
clientName: string | null;
|
|
62
|
+
logoUri: string | null;
|
|
44
63
|
}>;
|
|
45
64
|
/** DELETE {prefix}/api/clients/:id */
|
|
46
65
|
destroy(ctx: HttpContext): Promise<void | {
|
|
@@ -15,6 +15,7 @@ function invalidMetadata(ctx, err) {
|
|
|
15
15
|
*
|
|
16
16
|
* GET {prefix}/api/clients → lista clients dinâmicos
|
|
17
17
|
* POST {prefix}/api/clients → criar client (secret retornado UMA vez)
|
|
18
|
+
* GET {prefix}/api/clients/:id → um client (404 se não existir)
|
|
18
19
|
* PATCH {prefix}/api/clients/:id → atualizar metadata
|
|
19
20
|
* DELETE {prefix}/api/clients/:id → remover client
|
|
20
21
|
* POST {prefix}/api/clients/:id/regenerate-secret → gerar novo secret
|
|
@@ -30,6 +31,14 @@ export default class ConsoleClientsController {
|
|
|
30
31
|
const clients = await svc.list();
|
|
31
32
|
return { data: clients.map(clientDto), canList: true };
|
|
32
33
|
}
|
|
34
|
+
/** GET {prefix}/api/clients/:id */
|
|
35
|
+
async show(ctx) {
|
|
36
|
+
const service = await ctx.containerResolver.make('authkit.server');
|
|
37
|
+
const client = await new AdminClientsService(service).find(ctx.request.param('id'));
|
|
38
|
+
if (!client)
|
|
39
|
+
return ctx.response.notFound(apiError('not_found', 'Client não encontrado.'));
|
|
40
|
+
return clientDto(client);
|
|
41
|
+
}
|
|
33
42
|
/** POST {prefix}/api/clients */
|
|
34
43
|
async store(ctx) {
|
|
35
44
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
@@ -49,11 +49,7 @@ export default class ConsoleImpersonationController {
|
|
|
49
49
|
if (!account) {
|
|
50
50
|
return ctx.response.notFound(apiError('not_found', 'Usuário não encontrado.'));
|
|
51
51
|
}
|
|
52
|
-
// Clients
|
|
53
|
-
// vivem no adapter, criados pelo console/Admin API/`authkit:clients:create`.
|
|
54
|
-
// Ler só o config fazia o painel devolver 404 SEMPRE, que é por que o botão
|
|
55
|
-
// do console nunca funcionou. O fallback para o config cobre hosts legados
|
|
56
|
-
// com clients estáticos e adapters que não enumeram.
|
|
52
|
+
// Clients vivem no adapter, criados pelo console/Admin API/`authkit:clients:create`.
|
|
57
53
|
const panel = buildImpersonationPanel(cfg, targetId, await listRuntimeClients(service));
|
|
58
54
|
if (!panel) {
|
|
59
55
|
return ctx.response.notFound(apiError('no_token_exchange_client', 'Nenhum client habilitado ao grant token-exchange encontrado.'));
|
|
@@ -83,8 +79,7 @@ export default class ConsoleImpersonationController {
|
|
|
83
79
|
}
|
|
84
80
|
/**
|
|
85
81
|
* Clients persistidos que podem hospedar o token-exchange. Best-effort: um
|
|
86
|
-
* adapter sem `list` (ou que falhe) devolve lista vazia e o
|
|
87
|
-
* `buildImpersonationPanel` cai no `cfg.clients`.
|
|
82
|
+
* adapter sem `list` (ou que falhe) devolve lista vazia e o painel responde 404.
|
|
88
83
|
*/
|
|
89
84
|
async function listRuntimeClients(service) {
|
|
90
85
|
try {
|
|
@@ -93,7 +88,7 @@ async function listRuntimeClients(service) {
|
|
|
93
88
|
return [];
|
|
94
89
|
return (await clients.list()).map((c) => ({
|
|
95
90
|
clientId: c.clientId,
|
|
96
|
-
|
|
91
|
+
grantTypes: c.grantTypes,
|
|
97
92
|
confidential: c.confidential,
|
|
98
93
|
}));
|
|
99
94
|
}
|
|
@@ -5,7 +5,7 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
5
5
|
*
|
|
6
6
|
* Métricas-resumo do IdP para o dashboard do console React. Reutiliza
|
|
7
7
|
* `computeAdminStats` (mesma fonte do console Edge) e adiciona:
|
|
8
|
-
* - `clientsCount` — número de clients
|
|
8
|
+
* - `clientsCount` — número de clients no adapter (0 quando o adapter não enumera)
|
|
9
9
|
* - `auditTotal` — total de eventos de auditoria (0 quando sink não suporta)
|
|
10
10
|
* - `recentEvents` — últimos 5 eventos de auditoria ([] quando não suporta)
|
|
11
11
|
*/
|
|
@@ -20,7 +20,7 @@ export default class ConsoleOverviewController {
|
|
|
20
20
|
signUpsPerDay: import("../admin_stats_service.js").DailyPoint[];
|
|
21
21
|
windowDays: number;
|
|
22
22
|
auditSupported: boolean;
|
|
23
|
-
clientsCount:
|
|
23
|
+
clientsCount: number;
|
|
24
24
|
auditTotal: any;
|
|
25
25
|
recentEvents: any;
|
|
26
26
|
}>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { LIST_FIRST_PAGE } from '../../pagination.js';
|
|
3
|
+
import { AdminClientsService } from '../admin_clients_service.js';
|
|
3
4
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
4
5
|
import { computeAdminStats } from '../admin_stats_service.js';
|
|
5
6
|
/**
|
|
@@ -7,7 +8,7 @@ import { computeAdminStats } from '../admin_stats_service.js';
|
|
|
7
8
|
*
|
|
8
9
|
* Métricas-resumo do IdP para o dashboard do console React. Reutiliza
|
|
9
10
|
* `computeAdminStats` (mesma fonte do console Edge) e adiciona:
|
|
10
|
-
* - `clientsCount` — número de clients
|
|
11
|
+
* - `clientsCount` — número de clients no adapter (0 quando o adapter não enumera)
|
|
11
12
|
* - `auditTotal` — total de eventos de auditoria (0 quando sink não suporta)
|
|
12
13
|
* - `recentEvents` — últimos 5 eventos de auditoria ([] quando não suporta)
|
|
13
14
|
*/
|
|
@@ -17,7 +18,8 @@ export default class ConsoleOverviewController {
|
|
|
17
18
|
const cfg = service.config;
|
|
18
19
|
const sessions = new AdminSessionsService(service);
|
|
19
20
|
const stats = await computeAdminStats(cfg, sessions);
|
|
20
|
-
const
|
|
21
|
+
const clients = new AdminClientsService(service);
|
|
22
|
+
const clientsCount = clients.canList ? (await clients.list()).length : 0;
|
|
21
23
|
const recentResult = typeof cfg.audit?.list === 'function'
|
|
22
24
|
? await cfg.audit.list({ page: LIST_FIRST_PAGE, size: 5 })
|
|
23
25
|
: null;
|
|
@@ -4,8 +4,7 @@ import { type ApplicationType } from './client_metadata.js';
|
|
|
4
4
|
* Input de client OIDC — serve create E update (PATCH). Tudo opcional: no create
|
|
5
5
|
* o controller aplica defaults (ver {@link clientCreateInput}); no update os campos
|
|
6
6
|
* ausentes (undefined) são preservados pelo `AdminClientsService.update` (merge,
|
|
7
|
-
* ver {@link clientPartialInput}).
|
|
8
|
-
* (o DTO de saída usa `grants`).
|
|
7
|
+
* ver {@link clientPartialInput}).
|
|
9
8
|
*/
|
|
10
9
|
export declare const clientInputValidator: import("@vinejs/vine").VineValidator<import("@vinejs/vine").VineObject<{
|
|
11
10
|
clientId: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineString>;
|
|
@@ -13,37 +12,41 @@ export declare const clientInputValidator: import("@vinejs/vine").VineValidator<
|
|
|
13
12
|
redirectUris: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").VineArray<import("@vinejs/vine").VineString>>;
|
|
14
13
|
postLogoutRedirectUris: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").VineArray<import("@vinejs/vine").VineString>>;
|
|
15
14
|
grantTypes: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").VineArray<import("@vinejs/vine").VineEnum<readonly ["authorization_code", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:token-exchange"]>>>;
|
|
16
|
-
grants: import("@vinejs/vine").OptionalModifier<import("@vinejs/vine").VineArray<import("@vinejs/vine").VineEnum<readonly ["authorization_code", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:token-exchange"]>>>;
|
|
17
15
|
tokenEndpointAuthMethod: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineEnum<readonly ["client_secret_basic", "client_secret_post", "none"]>>;
|
|
18
|
-
backchannelLogoutUri: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineString
|
|
16
|
+
backchannelLogoutUri: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
|
|
19
17
|
backchannelLogoutSessionRequired: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine").VineBoolean>;
|
|
18
|
+
clientName: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
|
|
19
|
+
logoUri: import("@vinejs/vine/schema/base/literal").OptionalModifier<import("@vinejs/vine/schema/base/literal").NullableModifier<import("@vinejs/vine").VineString>>;
|
|
20
20
|
}, {
|
|
21
21
|
applicationType?: "native" | "web" | null | undefined;
|
|
22
22
|
backchannelLogoutSessionRequired?: string | number | boolean | null | undefined;
|
|
23
23
|
backchannelLogoutUri?: string | null | undefined;
|
|
24
24
|
clientId?: string | null | undefined;
|
|
25
|
+
clientName?: string | null | undefined;
|
|
25
26
|
grantTypes?: ("authorization_code" | "client_credentials" | "refresh_token" | "urn:ietf:params:oauth:grant-type:token-exchange")[] | null | undefined;
|
|
26
|
-
|
|
27
|
+
logoUri?: string | null | undefined;
|
|
27
28
|
postLogoutRedirectUris?: string[] | null | undefined;
|
|
28
29
|
redirectUris?: string[] | null | undefined;
|
|
29
30
|
tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | null | undefined;
|
|
30
31
|
}, {
|
|
31
32
|
applicationType?: "native" | "web" | undefined;
|
|
32
33
|
backchannelLogoutSessionRequired?: boolean | undefined;
|
|
33
|
-
backchannelLogoutUri?: string | undefined;
|
|
34
|
+
backchannelLogoutUri?: string | null | undefined;
|
|
34
35
|
clientId?: string | undefined;
|
|
36
|
+
clientName?: string | null | undefined;
|
|
35
37
|
grantTypes?: ("authorization_code" | "client_credentials" | "refresh_token" | "urn:ietf:params:oauth:grant-type:token-exchange")[] | undefined;
|
|
36
|
-
|
|
38
|
+
logoUri?: string | null | undefined;
|
|
37
39
|
postLogoutRedirectUris?: string[] | undefined;
|
|
38
40
|
redirectUris?: string[] | undefined;
|
|
39
41
|
tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | undefined;
|
|
40
42
|
}, {
|
|
41
43
|
applicationType?: "native" | "web" | undefined;
|
|
42
44
|
backchannelLogoutSessionRequired?: boolean | undefined;
|
|
43
|
-
backchannelLogoutUri?: string | undefined;
|
|
45
|
+
backchannelLogoutUri?: string | null | undefined;
|
|
44
46
|
clientId?: string | undefined;
|
|
47
|
+
clientName?: string | null | undefined;
|
|
45
48
|
grantTypes?: ("authorization_code" | "client_credentials" | "refresh_token" | "urn:ietf:params:oauth:grant-type:token-exchange")[] | undefined;
|
|
46
|
-
|
|
49
|
+
logoUri?: string | null | undefined;
|
|
47
50
|
postLogoutRedirectUris?: string[] | undefined;
|
|
48
51
|
redirectUris?: string[] | undefined;
|
|
49
52
|
tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none" | undefined;
|
|
@@ -55,10 +58,11 @@ export type ClientInputFields = {
|
|
|
55
58
|
redirectUris?: string[];
|
|
56
59
|
postLogoutRedirectUris?: string[];
|
|
57
60
|
grantTypes?: string[];
|
|
58
|
-
grants?: string[];
|
|
59
61
|
tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
|
|
60
|
-
backchannelLogoutUri?: string;
|
|
62
|
+
backchannelLogoutUri?: string | null;
|
|
61
63
|
backchannelLogoutSessionRequired?: boolean;
|
|
64
|
+
clientName?: string | null;
|
|
65
|
+
logoUri?: string | null;
|
|
62
66
|
};
|
|
63
67
|
/**
|
|
64
68
|
* Mapeia o input validado para um {@link ClientInput} COMPLETO (create), com defaults.
|
|
@@ -83,8 +83,7 @@ const grantTypeArray = vine.array(vine.enum(ALLOWED_GRANT_TYPES));
|
|
|
83
83
|
* Input de client OIDC — serve create E update (PATCH). Tudo opcional: no create
|
|
84
84
|
* o controller aplica defaults (ver {@link clientCreateInput}); no update os campos
|
|
85
85
|
* ausentes (undefined) são preservados pelo `AdminClientsService.update` (merge,
|
|
86
|
-
* ver {@link clientPartialInput}).
|
|
87
|
-
* (o DTO de saída usa `grants`).
|
|
86
|
+
* ver {@link clientPartialInput}).
|
|
88
87
|
*/
|
|
89
88
|
export const clientInputValidator = vine.compile(vine.object({
|
|
90
89
|
clientId: vine.string().trim().optional(),
|
|
@@ -96,11 +95,14 @@ export const clientInputValidator = vine.compile(vine.object({
|
|
|
96
95
|
postLogoutRedirectUris: redirectUriArray.optional(),
|
|
97
96
|
// grant_types restrito à allowlist; `implicit` e desconhecidos viram 422 (M10).
|
|
98
97
|
grantTypes: grantTypeArray.optional(),
|
|
99
|
-
grants: grantTypeArray.optional(),
|
|
100
98
|
tokenEndpointAuthMethod: authMethod.optional(),
|
|
101
99
|
// backchannel_logout_uri também precisa ser URI absoluta (L10).
|
|
102
|
-
|
|
100
|
+
// `null` (update) remove a URI.
|
|
101
|
+
backchannelLogoutUri: vine.string().trim().url(URL_OPTS).nullable().optional(),
|
|
103
102
|
backchannelLogoutSessionRequired: vine.boolean().optional(),
|
|
103
|
+
// `null` (update) removes them.
|
|
104
|
+
clientName: vine.string().trim().maxLength(200).nullable().optional(),
|
|
105
|
+
logoUri: vine.string().trim().url(URL_OPTS).nullable().optional(),
|
|
104
106
|
}));
|
|
105
107
|
/**
|
|
106
108
|
* Mapeia o input validado para um {@link ClientInput} COMPLETO (create), com defaults.
|
|
@@ -114,10 +116,12 @@ export function clientCreateInput(v) {
|
|
|
114
116
|
applicationType,
|
|
115
117
|
redirectUris: v.redirectUris ?? [],
|
|
116
118
|
postLogoutRedirectUris: v.postLogoutRedirectUris ?? [],
|
|
117
|
-
grantTypes: v.grantTypes ??
|
|
119
|
+
grantTypes: v.grantTypes ?? [],
|
|
118
120
|
tokenEndpointAuthMethod: v.tokenEndpointAuthMethod ?? (applicationType === 'native' ? 'none' : 'client_secret_basic'),
|
|
119
121
|
backchannelLogoutUri: v.backchannelLogoutUri || undefined,
|
|
120
122
|
backchannelLogoutSessionRequired: v.backchannelLogoutSessionRequired,
|
|
123
|
+
clientName: v.clientName || undefined,
|
|
124
|
+
logoUri: v.logoUri || undefined,
|
|
121
125
|
};
|
|
122
126
|
}
|
|
123
127
|
/**
|
|
@@ -132,15 +136,18 @@ export function clientPartialInput(v) {
|
|
|
132
136
|
out.redirectUris = v.redirectUris;
|
|
133
137
|
if (v.postLogoutRedirectUris !== undefined)
|
|
134
138
|
out.postLogoutRedirectUris = v.postLogoutRedirectUris;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
out.grantTypes = grants;
|
|
139
|
+
if (v.grantTypes !== undefined)
|
|
140
|
+
out.grantTypes = v.grantTypes;
|
|
138
141
|
if (v.tokenEndpointAuthMethod !== undefined)
|
|
139
142
|
out.tokenEndpointAuthMethod = v.tokenEndpointAuthMethod;
|
|
140
143
|
if (v.backchannelLogoutUri !== undefined)
|
|
141
|
-
out.backchannelLogoutUri = v.backchannelLogoutUri
|
|
144
|
+
out.backchannelLogoutUri = v.backchannelLogoutUri?.trim() || null;
|
|
142
145
|
if (v.backchannelLogoutSessionRequired !== undefined)
|
|
143
146
|
out.backchannelLogoutSessionRequired = v.backchannelLogoutSessionRequired;
|
|
147
|
+
if (v.clientName !== undefined)
|
|
148
|
+
out.clientName = v.clientName?.trim() || null;
|
|
149
|
+
if (v.logoUri !== undefined)
|
|
150
|
+
out.logoUri = v.logoUri?.trim() || null;
|
|
144
151
|
return out;
|
|
145
152
|
}
|
|
146
153
|
// ─── Usuários ───────────────────────────────────────────────────────────────
|
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
* Nome de exibição de cada client: o `client_name` que ele registrou (um cliente MCP registra o
|
|
3
3
|
* seu, ex.: "Claude Code"), senão o próprio `client_id`. Client que sumiu do adapter → o id.
|
|
4
4
|
*/
|
|
5
|
-
export declare function clientDisplayNames(service:
|
|
5
|
+
export declare function clientDisplayNames(service: ClientLookup, clientIds: string[]): Promise<Map<string, string>>;
|
|
6
|
+
type ClientLookup = {
|
|
6
7
|
provider: {
|
|
7
8
|
Client: {
|
|
8
9
|
find(id: string): Promise<any>;
|
|
9
10
|
};
|
|
10
11
|
};
|
|
11
|
-
}
|
|
12
|
+
};
|
|
13
|
+
/** Nome de exibição e logo (`logo_uri` registrado, ou `null`) de cada client. */
|
|
14
|
+
export declare function clientDisplayInfo(service: ClientLookup, clientIds: string[]): Promise<Map<string, {
|
|
15
|
+
name: string;
|
|
16
|
+
logoUrl: string | null;
|
|
17
|
+
}>>;
|
|
18
|
+
export {};
|