@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.
Files changed (86) hide show
  1. package/CHANGELOG.md +3637 -0
  2. package/build/commands/clients_create.d.ts +1 -0
  3. package/build/commands/clients_create.js +15 -2
  4. package/build/commands/commands.json +7 -0
  5. package/build/host/ui/admin.html +1 -1
  6. package/build/host/views/account/apps.edge +8 -3
  7. package/build/host/views/partials/styles.edge +1 -1
  8. package/build/index.d.ts +3 -3
  9. package/build/index.js +3 -5
  10. package/build/src/accounts/lucid_account_store.d.ts +0 -18
  11. package/build/src/accounts/lucid_account_store.js +7 -48
  12. package/build/src/accounts/lucid_store/mfa.js +1 -1
  13. package/build/src/accounts/lucid_store/organizations.d.ts +1 -1
  14. package/build/src/accounts/lucid_store/password_hygiene.d.ts +2 -3
  15. package/build/src/accounts/lucid_store/password_hygiene.js +2 -3
  16. package/build/src/accounts/lucid_store/shared.d.ts +0 -7
  17. package/build/src/accounts/lucid_store/shared.js +0 -14
  18. package/build/src/accounts/lucid_stores.d.ts +1 -1
  19. package/build/src/adapters/redis_adapter.js +12 -2
  20. package/build/src/define_config.d.ts +6 -12
  21. package/build/src/define_config.js +3 -2
  22. package/build/src/doctor/checks.d.ts +3 -4
  23. package/build/src/doctor/checks.js +5 -7
  24. package/build/src/host/account_api/account_api_controller.d.ts +2 -1
  25. package/build/src/host/account_api/account_api_controller.js +5 -11
  26. package/build/src/host/admin_api/api_clients_controller.d.ts +9 -3
  27. package/build/src/host/admin_api/dto.d.ts +3 -1
  28. package/build/src/host/admin_api/dto.js +3 -1
  29. package/build/src/host/admin_clients_service.d.ts +13 -5
  30. package/build/src/host/admin_clients_service.js +18 -5
  31. package/build/src/host/admin_console/admin_shell_controller.d.ts +16 -1
  32. package/build/src/host/admin_console/admin_shell_controller.js +21 -7
  33. package/build/src/host/admin_console/console_clients_controller.d.ts +21 -2
  34. package/build/src/host/admin_console/console_clients_controller.js +9 -0
  35. package/build/src/host/admin_console/console_impersonation_controller.js +3 -8
  36. package/build/src/host/admin_console/console_overview_controller.d.ts +2 -2
  37. package/build/src/host/admin_console/console_overview_controller.js +4 -2
  38. package/build/src/host/admin_validators.d.ts +15 -11
  39. package/build/src/host/admin_validators.js +16 -9
  40. package/build/src/host/client_names.d.ts +9 -2
  41. package/build/src/host/client_names.js +11 -3
  42. package/build/src/host/consent_params.d.ts +7 -0
  43. package/build/src/host/consent_params.js +27 -0
  44. package/build/src/host/controllers/account_apps_controller.js +4 -3
  45. package/build/src/host/controllers/account_confirm_controller.d.ts +0 -7
  46. package/build/src/host/controllers/account_confirm_controller.js +0 -6
  47. package/build/src/host/controllers/account_orgs_controller.d.ts +0 -50
  48. package/build/src/host/controllers/account_orgs_controller.js +7 -113
  49. package/build/src/host/controllers/account_security_controller.js +7 -11
  50. package/build/src/host/controllers/agent_consent_controller.js +23 -5
  51. package/build/src/host/controllers/interaction_controller.js +6 -2
  52. package/build/src/host/controllers/poppy_consent_controller.js +33 -7
  53. package/build/src/host/controllers/registration_controller.js +7 -2
  54. package/build/src/host/i18n.d.ts +0 -4
  55. package/build/src/host/i18n.js +0 -6
  56. package/build/src/host/impersonation.d.ts +7 -15
  57. package/build/src/host/impersonation.js +7 -16
  58. package/build/src/host/interaction_recovery.js +4 -1
  59. package/build/src/host/login_attempt.js +5 -1
  60. package/build/src/host/middleware/account_auth.js +6 -0
  61. package/build/src/host/rate_limit.js +5 -1
  62. package/build/src/host/register_auth_host.d.ts +6 -0
  63. package/build/src/host/register_auth_host.js +32 -8
  64. package/build/src/host/renderers/inertia_renderer.d.ts +9 -21
  65. package/build/src/host/renderers/inertia_renderer.js +5 -6
  66. package/build/src/host/screen_props.d.ts +309 -0
  67. package/build/src/host/screen_props.js +11 -0
  68. package/build/src/host/sudo_mode.d.ts +5 -11
  69. package/build/src/host/sudo_mode.js +5 -11
  70. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +67 -0
  71. package/build/src/host/ui-dist/index.html +1 -1
  72. package/build/src/password/password_manager.js +5 -0
  73. package/build/src/provider/build_provider.js +0 -34
  74. package/build/src/provider/oidc_service.d.ts +0 -2
  75. package/build/src/provider/oidc_service.js +1 -12
  76. package/build/src/provider/token_exchange.d.ts +1 -3
  77. package/build/src/provider/token_exchange.js +1 -1
  78. package/build/stubs/config/authkit_react.stub +3 -3
  79. package/package.json +9 -8
  80. package/skills/authkit-interactions/SKILL.md +32 -5
  81. package/stubs/config/authkit_react.stub +3 -3
  82. package/build/src/commands/import_clients.d.ts +0 -40
  83. package/build/src/commands/import_clients.js +0 -81
  84. package/build/src/host/ui-dist/assets/index-DUVBDAyh.js +0 -67
  85. package/build/src/mixins/with_mfa.d.ts +0 -35
  86. 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 { clientDisplayNames } from '../client_names.js';
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 names = await clientDisplayNames(service, grants.map((g) => g.clientId).filter((id) => !!id));
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: names.get(g.clientId) ?? g.clientId,
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. Alias consolidado dos /account/orgs/json existentes. */
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
- grants: string[];
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
- grants: string[];
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
- grants: string[];
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
- grants: string[];
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
- grants: client.grants,
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
- grants: string[];
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 PRESERVANDO um `clientSecret` fornecido externamente (em vez de
77
- * gerar um aleatório). Útil para migração de clients estáticos do config, onde o
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 PRESERVANDO um `clientSecret` fornecido externamente (em vez de
64
- * gerar um aleatório). Útil para migração de clients estáticos do config, onde o
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 ?? existing.backchannel_logout_uri,
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
- grants: p.grant_types ?? ['authorization_code', 'refresh_token'],
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 injeta `window.__AUTHKIT__` e reescreve o placeholder de base dinâmica
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
- * Injects `window.__AUTHKIT__` into the HTML shell right after `<body>`.
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 script right after <body>.
65
- const script = `<script>window.__AUTHKIT__=${JSON.stringify(config)};</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 injeta `window.__AUTHKIT__` e reescreve o placeholder de base dinâmica
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
- grants: string[];
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
- grants: string[];
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 de RUNTIME. `cfg.clients` é sempre `[]` num host real — 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
- grants: c.grants,
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 na config
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: any;
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 na config
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 clientsCount = cfg.clients.length;
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}). `grants` é aceito como alias de `grantTypes`
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
- grants?: ("authorization_code" | "client_credentials" | "refresh_token" | "urn:ietf:params:oauth:grant-type:token-exchange")[] | null | undefined;
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
- grants?: ("authorization_code" | "client_credentials" | "refresh_token" | "urn:ietf:params:oauth:grant-type:token-exchange")[] | undefined;
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
- grants?: ("authorization_code" | "client_credentials" | "refresh_token" | "urn:ietf:params:oauth:grant-type:token-exchange")[] | undefined;
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}). `grants` é aceito como alias de `grantTypes`
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
- backchannelLogoutUri: vine.string().trim().url(URL_OPTS).optional(),
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 ?? v.grants ?? [],
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
- const grants = v.grantTypes ?? v.grants;
136
- if (grants !== undefined)
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.trim() || undefined;
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
- }, clientIds: string[]): Promise<Map<string, string>>;
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 {};