@adonis-agora/authkit-server 0.90.2 → 1.0.0-rc.1

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 (99) hide show
  1. package/CHANGELOG.md +3654 -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 +5 -3
  9. package/build/index.js +4 -5
  10. package/build/providers/authkit_server_provider.js +8 -0
  11. package/build/src/accounts/lucid_account_store.d.ts +0 -18
  12. package/build/src/accounts/lucid_account_store.js +7 -48
  13. package/build/src/accounts/lucid_store/mfa.js +1 -1
  14. package/build/src/accounts/lucid_store/organizations.d.ts +1 -1
  15. package/build/src/accounts/lucid_store/password_hygiene.d.ts +2 -3
  16. package/build/src/accounts/lucid_store/password_hygiene.js +2 -3
  17. package/build/src/accounts/lucid_store/shared.d.ts +0 -7
  18. package/build/src/accounts/lucid_store/shared.js +0 -14
  19. package/build/src/accounts/lucid_stores.d.ts +1 -1
  20. package/build/src/adapters/redis_adapter.js +12 -2
  21. package/build/src/console_authorize.d.ts +56 -0
  22. package/build/src/console_authorize.js +85 -0
  23. package/build/src/define_config.d.ts +26 -14
  24. package/build/src/define_config.js +5 -3
  25. package/build/src/doctor/checks.d.ts +10 -5
  26. package/build/src/doctor/checks.js +52 -8
  27. package/build/src/host/account_api/account_api_controller.d.ts +2 -1
  28. package/build/src/host/account_api/account_api_controller.js +5 -11
  29. package/build/src/host/admin_api/api_clients_controller.d.ts +9 -3
  30. package/build/src/host/admin_api/dto.d.ts +3 -1
  31. package/build/src/host/admin_api/dto.js +3 -1
  32. package/build/src/host/admin_clients_service.d.ts +13 -5
  33. package/build/src/host/admin_clients_service.js +28 -6
  34. package/build/src/host/admin_console/admin_shell_controller.d.ts +16 -1
  35. package/build/src/host/admin_console/admin_shell_controller.js +21 -7
  36. package/build/src/host/admin_console/console_clients_controller.d.ts +21 -2
  37. package/build/src/host/admin_console/console_clients_controller.js +9 -0
  38. package/build/src/host/admin_console/console_impersonation_controller.js +3 -8
  39. package/build/src/host/admin_console/console_overview_controller.d.ts +2 -2
  40. package/build/src/host/admin_console/console_overview_controller.js +4 -2
  41. package/build/src/host/admin_console_warning.d.ts +8 -0
  42. package/build/src/host/admin_console_warning.js +18 -0
  43. package/build/src/host/admin_validators.d.ts +15 -11
  44. package/build/src/host/admin_validators.js +16 -9
  45. package/build/src/host/auth_host_config.d.ts +5 -0
  46. package/build/src/host/client_names.d.ts +9 -2
  47. package/build/src/host/client_names.js +11 -3
  48. package/build/src/host/consent_params.d.ts +7 -0
  49. package/build/src/host/consent_params.js +27 -0
  50. package/build/src/host/console_denied_page.d.ts +6 -0
  51. package/build/src/host/console_denied_page.js +18 -0
  52. package/build/src/host/console_sso.d.ts +24 -0
  53. package/build/src/host/console_sso.js +70 -0
  54. package/build/src/host/controllers/account_apps_controller.js +4 -3
  55. package/build/src/host/controllers/account_confirm_controller.d.ts +0 -7
  56. package/build/src/host/controllers/account_confirm_controller.js +0 -6
  57. package/build/src/host/controllers/account_orgs_controller.d.ts +0 -50
  58. package/build/src/host/controllers/account_orgs_controller.js +7 -113
  59. package/build/src/host/controllers/account_security_controller.js +7 -11
  60. package/build/src/host/controllers/account_session_controller.js +10 -1
  61. package/build/src/host/controllers/agent_consent_controller.js +23 -5
  62. package/build/src/host/controllers/console_sso_controller.d.ts +12 -0
  63. package/build/src/host/controllers/console_sso_controller.js +61 -0
  64. package/build/src/host/controllers/interaction_controller.js +9 -3
  65. package/build/src/host/controllers/poppy_consent_controller.js +33 -7
  66. package/build/src/host/controllers/registration_controller.js +7 -2
  67. package/build/src/host/i18n.d.ts +0 -4
  68. package/build/src/host/i18n.js +0 -6
  69. package/build/src/host/impersonation.d.ts +7 -15
  70. package/build/src/host/impersonation.js +7 -16
  71. package/build/src/host/interaction_recovery.js +4 -1
  72. package/build/src/host/login_attempt.js +5 -1
  73. package/build/src/host/middleware/account_auth.js +6 -0
  74. package/build/src/host/rate_limit.js +5 -1
  75. package/build/src/host/register_auth_host.d.ts +8 -0
  76. package/build/src/host/register_auth_host.js +116 -11
  77. package/build/src/host/renderers/inertia_renderer.d.ts +9 -21
  78. package/build/src/host/renderers/inertia_renderer.js +5 -6
  79. package/build/src/host/screen_props.d.ts +309 -0
  80. package/build/src/host/screen_props.js +11 -0
  81. package/build/src/host/sudo_mode.d.ts +5 -11
  82. package/build/src/host/sudo_mode.js +5 -11
  83. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +67 -0
  84. package/build/src/host/ui-dist/index.html +1 -1
  85. package/build/src/password/password_manager.js +5 -0
  86. package/build/src/provider/build_provider.js +6 -34
  87. package/build/src/provider/oidc_service.d.ts +0 -2
  88. package/build/src/provider/oidc_service.js +1 -12
  89. package/build/src/provider/token_exchange.d.ts +1 -3
  90. package/build/src/provider/token_exchange.js +1 -1
  91. package/build/stubs/config/authkit_react.stub +3 -3
  92. package/package.json +12 -11
  93. package/skills/authkit-interactions/SKILL.md +32 -5
  94. package/stubs/config/authkit_react.stub +3 -3
  95. package/build/src/commands/import_clients.d.ts +0 -40
  96. package/build/src/commands/import_clients.js +0 -81
  97. package/build/src/host/ui-dist/assets/index-DUVBDAyh.js +0 -67
  98. package/build/src/mixins/with_mfa.d.ts +0 -35
  99. package/build/src/mixins/with_mfa.js +0 -21
@@ -0,0 +1,85 @@
1
+ import { createHash, timingSafeEqual } from 'node:crypto';
2
+ function isAuthorizerClass(value) {
3
+ return (typeof value === 'function' &&
4
+ typeof value.prototype?.authorize === 'function');
5
+ }
6
+ function isLazyModule(value) {
7
+ return (typeof value === 'object' &&
8
+ value !== null &&
9
+ 'default' in value &&
10
+ isAuthorizerClass(value.default));
11
+ }
12
+ export function normalizeConsoleAuthorizeResult(result) {
13
+ if (result === true)
14
+ return { allowed: true };
15
+ if (typeof result === 'object' && result !== null) {
16
+ const r = result;
17
+ if (typeof r.redirectTo === 'string' && r.redirectTo.length > 0) {
18
+ return { allowed: false, status: 401, redirectTo: r.redirectTo };
19
+ }
20
+ if (r.allowed === true)
21
+ return { allowed: true };
22
+ if (r.allowed === false && r.reason === 'unauthenticated') {
23
+ return { allowed: false, status: 401 };
24
+ }
25
+ }
26
+ // `false`, `{ allowed: false }` and anything unrecognized deny.
27
+ return { allowed: false, status: 403 };
28
+ }
29
+ async function runClass(Authorizer, ctx) {
30
+ const instance = (await ctx.containerResolver.make(Authorizer));
31
+ return instance.authorize(ctx);
32
+ }
33
+ /**
34
+ * Turns a `ConsoleAuthorize` into a checker returning a normalized decision.
35
+ * A class is built through the container (so `@inject()` works); a lazy import
36
+ * runs once and its class is remembered. A thrown error is a denial (403).
37
+ */
38
+ export function resolveConsoleAuthorize(authorize) {
39
+ let lazyClass;
40
+ return async (ctx) => {
41
+ try {
42
+ if (isAuthorizerClass(authorize)) {
43
+ return normalizeConsoleAuthorizeResult(await runClass(authorize, ctx));
44
+ }
45
+ if (lazyClass)
46
+ return normalizeConsoleAuthorizeResult(await runClass(lazyClass, ctx));
47
+ const result = await authorize(ctx);
48
+ if (isLazyModule(result)) {
49
+ lazyClass = result.default;
50
+ return normalizeConsoleAuthorizeResult(await runClass(lazyClass, ctx));
51
+ }
52
+ return normalizeConsoleAuthorizeResult(result);
53
+ }
54
+ catch (error) {
55
+ ctx.logger?.warn?.({ err: error }, 'console authorize threw; access denied');
56
+ return { allowed: false, status: 403 };
57
+ }
58
+ };
59
+ }
60
+ /**
61
+ * A `ConsoleAuthorize` that lets in requests carrying `Authorization: Bearer <token>`.
62
+ * The comparison is timing-safe. Anything else is `{ allowed: false, reason: 'unauthenticated' }`.
63
+ * Throws when `token` is empty, so a missing env var fails at config time.
64
+ *
65
+ * ```ts
66
+ * authorize: authorizeByToken({ token: env.get('CONSOLE_TOKEN') })
67
+ * ```
68
+ */
69
+ export function authorizeByToken(options) {
70
+ const token = options?.token;
71
+ if (typeof token !== 'string' || token.length === 0) {
72
+ throw new Error('authorizeByToken: `token` is empty. Set it (e.g. env CONSOLE_TOKEN).');
73
+ }
74
+ const expected = createHash('sha256').update(token).digest();
75
+ return (ctx) => {
76
+ const header = ctx.request.header('authorization');
77
+ const match = typeof header === 'string' ? /^Bearer\s+(.+)$/i.exec(header.trim()) : null;
78
+ if (match) {
79
+ const given = createHash('sha256').update(match[1]).digest();
80
+ if (timingSafeEqual(given, expected))
81
+ return { allowed: true };
82
+ }
83
+ return { allowed: false, reason: 'unauthenticated' };
84
+ };
85
+ }
@@ -1,9 +1,10 @@
1
- import type { AccessTokenFormat, AccessTokensConfig, ClientConfig, JwksConfig, KeystoreStoreConfig, ObservabilityConfig, TtlConfig } from '@adonis-agora/authkit-core';
1
+ import type { AccessTokenFormat, AccessTokensConfig, JwksConfig, KeystoreStoreConfig, ObservabilityConfig, TtlConfig } from '@adonis-agora/authkit-core';
2
2
  import type { HttpContext } from '@adonisjs/core/http';
3
3
  import type { AccountStore, AuthAccount } from './accounts/account_store.js';
4
4
  import { type AdapterFactory, adapters, type OidcAdapterClass } from './adapters/factory.js';
5
5
  import { type PersonalAgentsConfigInput, type ResolvedPersonalAgentsConfig } from './agents/config.js';
6
6
  import type { AuditSink } from './audit/audit_sink.js';
7
+ import type { ConsoleAuthorize } from './console_authorize.js';
7
8
  import { type EventsConfigInput } from './events/dispatcher.js';
8
9
  import { type BotProtectionConfigInput, type ResolvedBotProtectionConfig } from './host/bot_protection.js';
9
10
  import { type BrandingConfig } from './host/branding.js';
@@ -14,6 +15,7 @@ import type { ResolveGeo } from './host/geo.js';
14
15
  import { type AuthMessages, type I18nConfig } from './host/i18n.js';
15
16
  import { type OtpLoginConfigInput, type ResolvedOtpLoginConfig } from './host/otp_login.js';
16
17
  import type { AuthHostOptions } from './host/register_auth_host.js';
18
+ import type { AuthkitScreenRenderer } from './host/screen_props.js';
17
19
  import type { SudoMethod } from './host/sudo/types.js';
18
20
  import { type ResolvedTrustedDevicesConfig, type TrustedDevicesConfigInput } from './host/trusted_device.js';
19
21
  import type { WhatsappCodeSenderBinding } from './host/whatsapp_code_sender.js';
@@ -354,8 +356,8 @@ export declare function resolveNotifications(): ResolvedNotificationsConfig;
354
356
  *
355
357
  * Quando habilitado, o oidc-provider expõe o endpoint de registro (`/reg`) e os
356
358
  * clients criados ali são PERSISTIDOS pelo MESMO adapter usado para os demais
357
- * artefatos OIDC. Assim, clients dinâmicos coexistem com os `clients` estáticos
358
- * da config e ficam disponíveis para uma futura UI admin que gerencia clients no DB.
359
+ * artefatos OIDC, junto dos clients criados pelo console admin, pela Admin API e
360
+ * pelo `node ace authkit:clients:create`.
359
361
  */
360
362
  export interface DynamicRegistrationConfigInput {
361
363
  /** Liga o endpoint de registro dinâmico. Default: false (comportamento atual). */
@@ -707,12 +709,21 @@ export interface AdminConfigInput {
707
709
  * default é decisão de major, mesma regra de `impersonation`).
708
710
  */
709
711
  impersonateAdmins?: boolean;
712
+ /**
713
+ * Access check for the console, run after `roles` (both must pass), on the shell
714
+ * and on every `{prefix}/api/*` route. An inline function, an authorizer class
715
+ * (`@inject()` works), a lazy import (`() => import('#policies/...')`) or
716
+ * `authorizeByToken({ token })`. Omitted: open outside production, 403 in production.
717
+ */
718
+ authorize?: ConsoleAuthorize;
710
719
  }
711
720
  export interface ResolvedAdminConfig {
712
721
  enabled: boolean;
713
722
  roles: string[];
714
723
  impersonation: boolean;
715
724
  impersonateAdmins: boolean;
725
+ /** `admin.authorize` as configured, or `null` when omitted. */
726
+ authorize: ConsoleAuthorize | null;
716
727
  }
717
728
  export declare function resolveAdmin(input?: AdminConfigInput): ResolvedAdminConfig;
718
729
  /**
@@ -872,13 +883,6 @@ export interface AuthServerConfigInput {
872
883
  session?: {
873
884
  adapter?: AdapterFactory;
874
885
  };
875
- /**
876
- * Clientes OIDC pré-carregados no provider ao subir. Útil para testes e
877
- * migrações pontuais. Para uso em produção, gerencie clients via console admin
878
- * ou Admin API (`node ace authkit:clients:create`).
879
- * @internal
880
- */
881
- clients?: ClientConfig[];
882
886
  /**
883
887
  * Config de JWKS, ou `'auto'` (recomendado p/ deploys efêmeros): se a env
884
888
  * `AUTHKIT_JWKS` (JSON `{"keys":[...]}`) estiver presente, usa-a inline
@@ -1231,8 +1235,16 @@ export interface AuthServerConfigInput {
1231
1235
  * expiração), e o "Sair" do console encerra também a sessão do IdP. Contas
1232
1236
  * desabilitadas não entram. Operações sensíveis continuam pedindo sudo.
1233
1237
  *
1234
- * Default `false`: o console só aceita a própria sessão (`POST /account/login`),
1235
- * como sempre.
1238
+ * Default `true`: o console é mais um app do IdP. Sem sessão, `/account/*` e o
1239
+ * console admin mandam o visitante para o login do IdP (`/auth/console/sso`,
1240
+ * client interno `authkit-console`) e voltam com o console aberto. Um único
1241
+ * login, como no Keycloak, Okta e Google; o que protege as operações
1242
+ * sensíveis é o sudo, não um segundo login.
1243
+ *
1244
+ * `false`: o console só aceita a própria sessão (`POST /account/login`, por
1245
+ * senha). Com `authMethods: { password: false }` isso deixa o console sem
1246
+ * nenhum jeito de entrar, então o boot recusa a combinação, a menos que
1247
+ * `routes.accountLoginUrl` aponte para um login do próprio host.
1236
1248
  */
1237
1249
  accountSession?: {
1238
1250
  acceptIdpSession?: boolean;
@@ -1256,7 +1268,6 @@ export interface ResolvedServerConfig {
1256
1268
  * `session.adapter` no input é a MESMA classe do `AdapterClass`.
1257
1269
  */
1258
1270
  SessionAdapterClass: OidcAdapterClass;
1259
- clients: ClientConfig[];
1260
1271
  jwks: {
1261
1272
  keys: Record<string, any>[];
1262
1273
  };
@@ -1296,7 +1307,8 @@ export interface ResolvedServerConfig {
1296
1307
  mountPath: string;
1297
1308
  /** Destino default pós-confirmação do console (sudo mode) e demais fallbacks de conta. Default: '/account/security'. */
1298
1309
  accountHome?: string;
1299
- render?: AuthHostRenderer;
1310
+ /** O renderer das telas, tipado por view (ver `AuthkitScreenProps`). */
1311
+ render?: AuthkitScreenRenderer;
1300
1312
  /** Recuperação de sessão de interaction perdida resolvida (default `{ mode: 'screen' }`). */
1301
1313
  interactionRecovery: ResolvedInteractionRecoveryConfig;
1302
1314
  /** Branding resolvido: sempre presente (default neutro quando o host não declara `branding`). */
@@ -193,6 +193,7 @@ export function resolveAdmin(input) {
193
193
  // registrado). Ver o docblock de `AdminConfigInput.impersonation`.
194
194
  impersonation: input?.impersonation !== false,
195
195
  impersonateAdmins: input?.impersonateAdmins !== false,
196
+ authorize: input?.authorize ?? null,
196
197
  };
197
198
  }
198
199
  /** Lê API keys de `AUTHKIT_ADMIN_API_KEY` (uma ou várias, separadas por vírgula). */
@@ -387,7 +388,6 @@ export function defineConfig(config) {
387
388
  issuer: config.issuer,
388
389
  AdapterClass,
389
390
  SessionAdapterClass,
390
- clients: config.clients ?? [],
391
391
  jwks: jwks,
392
392
  jwksConfig,
393
393
  ttl: {
@@ -419,7 +419,9 @@ export function defineConfig(config) {
419
419
  // importa `edge.js` eagerly, então é seguro construir mesmo em hosts
420
420
  // sem o peer opcional instalado (só quebraria se a request realmente
421
421
  // chegasse sem Edge configurado, exatamente como hoje).
422
- render: config.render ?? edgeRenderer(),
422
+ // O renderer do host recebe `(ctx, view, props)`; aqui ele é tipado por view para que
423
+ // os controllers não possam divergir de `AuthkitScreenProps`.
424
+ render: (config.render ?? edgeRenderer()),
423
425
  // Recuperação de sessão de interaction perdida: default gracioso 'screen'
424
426
  // (tela themeável `session-expired`); 'redirect' opt-in para 302 ao login.
425
427
  interactionRecovery: {
@@ -495,7 +497,7 @@ export function defineConfig(config) {
495
497
  lockedRouteOptions: deriveLockedRouteOptions(config),
496
498
  // Opt-in: ausente = authkit nunca toca `ctx.auth` (comportamento de sempre).
497
499
  adonisAuth: config.adonisAuth,
498
- accountSession: { acceptIdpSession: config.accountSession?.acceptIdpSession === true },
500
+ accountSession: { acceptIdpSession: config.accountSession?.acceptIdpSession !== false },
499
501
  };
500
502
  });
501
503
  }
@@ -41,13 +41,12 @@ export declare function checkConfigResolves(input: DoctorInput): Finding;
41
41
  /** issuer é uma URL válida e seu pathname casa com o mountPath. */
42
42
  export declare function checkIssuer(input: DoctorInput): Finding[];
43
43
  /**
44
- * Clients: verifica que não há clients estáticos no config (clients são 100%
45
- * runtime via console admin / Admin API).
44
+ * Clients: vivem no adapter, gerenciados pelo console admin / Admin API /
45
+ * `node ace authkit:clients:create`.
46
46
  */
47
47
  export declare function checkClients(input: DoctorInput): Finding;
48
48
  /**
49
- * Aviso de adapter volátil. Como clients são 100% runtime (sem config estático),
50
- * um adapter volátil (Redis sem persistência, in-memory) pode perder os clients
49
+ * Aviso de adapter volátil. Como os clients vivem no adapter, um adapter volátil (Redis sem persistência, in-memory) pode perder os clients
51
50
  * num restart sem aviso prévio.
52
51
  *
53
52
  * Detecção de volatilidade: o `AdapterClass` é inspecionado estruturalmente:
@@ -88,7 +87,7 @@ export declare function checkOtpLockout(input: DoctorInput): Finding | null;
88
87
  export declare function checkSudoMode(_input: DoctorInput): Finding | null;
89
88
  /** rateLimit ligado mas @adonisjs/limiter ausente → warn. */
90
89
  export declare function checkRateLimit(input: DoctorInput): Finding;
91
- /** admin.enabled mas sem roles → warn. Reporta o modo de UI ativo. */
90
+ /** admin.enabled mas sem roles, ou sem `admin.authorize` → warn. Reporta o modo de UI ativo. */
92
91
  export declare function checkAdmin(input: DoctorInput): Finding | null;
93
92
  /** requireVerifiedEmail ligado mas o store não sabe checar verificação → warn. */
94
93
  export declare function checkRequireVerifiedEmail(input: DoctorInput): Finding | null;
@@ -215,6 +214,12 @@ export declare function checkPasskeyAutofill(input: DoctorInput): Finding | null
215
214
  * de TERCEIROS precisa saber que existe uma lista para fechar.
216
215
  */
217
216
  export declare function checkFirstPartyClients(input: DoctorInput): Finding | null;
217
+ /**
218
+ * Como se entra no console de conta e no console admin. O default é o login do
219
+ * IdP (SSO, `accountSession.acceptIdpSession`); desligado, o console só tem o
220
+ * login próprio por senha.
221
+ */
222
+ export declare function checkConsoleSignIn(input: DoctorInput): Finding | null;
218
223
  /**
219
224
  * Finding da idade da chave de assinatura managed. `ageDays === null` (sem
220
225
  * keystore em arquivo/cofre) → no-op `ok`. Acima de `maxAgeDays` → `warn`.
@@ -48,22 +48,20 @@ export function checkIssuer(input) {
48
48
  return findings;
49
49
  }
50
50
  /**
51
- * Clients: verifica que não há clients estáticos no config (clients são 100%
52
- * runtime via console admin / Admin API).
51
+ * Clients: vivem no adapter, gerenciados pelo console admin / Admin API /
52
+ * `node ace authkit:clients:create`.
53
53
  */
54
54
  export function checkClients(input) {
55
55
  const cfg = input.authkitConfig;
56
56
  if (!cfg)
57
57
  return { level: 'error', message: 'no config to validate clients.' };
58
- // clients sempre é [] no config resolvido — info apenas.
59
58
  return {
60
59
  level: 'ok',
61
60
  message: 'clients are managed at runtime via admin console / Admin API — use `node ace authkit:clients:create` to add clients.',
62
61
  };
63
62
  }
64
63
  /**
65
- * Aviso de adapter volátil. Como clients são 100% runtime (sem config estático),
66
- * um adapter volátil (Redis sem persistência, in-memory) pode perder os clients
64
+ * Aviso de adapter volátil. Como os clients vivem no adapter, um adapter volátil (Redis sem persistência, in-memory) pode perder os clients
67
65
  * num restart sem aviso prévio.
68
66
  *
69
67
  * Detecção de volatilidade: o `AdapterClass` é inspecionado estruturalmente:
@@ -98,9 +96,9 @@ export function checkAdapterVolatility(input) {
98
96
  // Adapter desconhecido ou volátil (Redis, memory, custom) — warn informativo.
99
97
  return {
100
98
  level: 'warn',
101
- message: 'No static clients in config: if the OIDC adapter is volatile (Redis without AOF/RDB, in-memory, etc.), ' +
99
+ message: 'Clients live in the OIDC adapter: if it is volatile (Redis without AOF/RDB, in-memory, etc.), ' +
102
100
  'clients managed via console/API will be lost on restart. ' +
103
- 'Use a persistent adapter (DatabaseAdapter) or keep static clients as a fallback.',
101
+ 'Use a persistent adapter (DatabaseAdapter).',
104
102
  };
105
103
  }
106
104
  /** accountStore presente + quais capacidades implementa. */
@@ -313,7 +311,7 @@ export function checkRateLimit(input) {
313
311
  }
314
312
  return { level: 'ok', message: 'rate-limiting on and @adonisjs/limiter available.' };
315
313
  }
316
- /** admin.enabled mas sem roles → warn. Reporta o modo de UI ativo. */
314
+ /** admin.enabled mas sem roles, ou sem `admin.authorize` → warn. Reporta o modo de UI ativo. */
317
315
  export function checkAdmin(input) {
318
316
  const admin = input.authkitConfig?.admin;
319
317
  if (!admin || admin.enabled !== true)
@@ -325,6 +323,12 @@ export function checkAdmin(input) {
325
323
  message: 'admin console on, but no `admin.roles` — nobody will have access (the default ["ADMIN"] was not resolved here).',
326
324
  };
327
325
  }
326
+ if (!admin.authorize) {
327
+ return {
328
+ level: 'warn',
329
+ message: `admin console on for roles: ${roles.join(', ')}, but no \`admin.authorize\` — it is open outside production and denies every request in production. Set \`admin.authorize\` in config/authkit.ts.`,
330
+ };
331
+ }
328
332
  return {
329
333
  level: 'ok',
330
334
  message: `admin console on for roles: ${roles.join(', ')} (React SPA self-contained — JSON API under {prefix}/api/*).`,
@@ -910,6 +914,43 @@ export function checkFirstPartyClients(input) {
910
914
  message: `firstPartyClients: ${allowlist.length} client(s) receive the roles/organization claims (${allowlist.join(', ')}).`,
911
915
  };
912
916
  }
917
+ /**
918
+ * Como se entra no console de conta e no console admin. O default é o login do
919
+ * IdP (SSO, `accountSession.acceptIdpSession`); desligado, o console só tem o
920
+ * login próprio por senha.
921
+ */
922
+ export function checkConsoleSignIn(input) {
923
+ const cfg = input.authkitConfig;
924
+ if (!cfg)
925
+ return null;
926
+ const sso = cfg.accountSession?.acceptIdpSession !== false;
927
+ const passwordOff = cfg.authMethods?.password === false;
928
+ const routes = typeof cfg.routes === 'object' && cfg.routes ? cfg.routes : undefined;
929
+ if (!sso && passwordOff && !routes?.accountLoginUrl) {
930
+ return {
931
+ level: 'warn',
932
+ message: 'The account and admin consoles have no way to sign in unless `registerAuthHost` gets an ' +
933
+ '`accountLoginUrl`: `accountSession.acceptIdpSession` is false and password login is off ' +
934
+ '(`authMethods: { password: false }`), and route registration refuses to start without ' +
935
+ 'one. Remove `acceptIdpSession: false` so the consoles sign in through the IdP, or point ' +
936
+ '`accountLoginUrl` at a login route of your app.',
937
+ };
938
+ }
939
+ if (!sso) {
940
+ return {
941
+ level: 'warn',
942
+ message: 'The consoles use their own password login (`accountSession.acceptIdpSession: false`): ' +
943
+ 'users sign in twice, and passwordless accounts cannot open them. The default (SSO through ' +
944
+ 'the IdP login) is recommended; sensitive operations still ask for sudo.',
945
+ };
946
+ }
947
+ return {
948
+ level: 'ok',
949
+ message: 'The consoles sign in through the IdP login (SSO). Sensitive operations ask for sudo. ' +
950
+ 'Make sure admin accounts have MFA enrolled (or require it for everyone with ' +
951
+ '`mfa.requiredFactors`).',
952
+ };
953
+ }
913
954
  /**
914
955
  * Finding da idade da chave de assinatura managed. `ageDays === null` (sem
915
956
  * keystore em arquivo/cofre) → no-op `ok`. Acima de `maxAgeDays` → `warn`.
@@ -1009,6 +1050,9 @@ export function runAllChecks(input) {
1009
1050
  const firstPartyClients = checkFirstPartyClients(input);
1010
1051
  if (firstPartyClients)
1011
1052
  findings.push(firstPartyClients);
1053
+ const consoleSignIn = checkConsoleSignIn(input);
1054
+ if (consoleSignIn)
1055
+ findings.push(consoleSignIn);
1012
1056
  return findings;
1013
1057
  }
1014
1058
  /** Há algum finding de nível 'error'? (define o exit code). */
@@ -144,6 +144,7 @@ export default class AccountApiController {
144
144
  apps: {
145
145
  clientId: string;
146
146
  name: string;
147
+ logoUrl: string | null;
147
148
  accessTokens: number;
148
149
  refreshTokens: number;
149
150
  }[];
@@ -263,7 +264,7 @@ export default class AccountApiController {
263
264
  ok: boolean;
264
265
  revoked: string;
265
266
  }>;
266
- /** Lista orgs do usuário logado. Alias consolidado dos /account/orgs/json existentes. */
267
+ /** Lista orgs do usuário logado. */
267
268
  listOrgs(ctx: HttpContext): Promise<{
268
269
  supported: boolean;
269
270
  activeOrgId: any;
@@ -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.