@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.
- package/CHANGELOG.md +3654 -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 +5 -3
- package/build/index.js +4 -5
- package/build/providers/authkit_server_provider.js +8 -0
- 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/console_authorize.d.ts +56 -0
- package/build/src/console_authorize.js +85 -0
- package/build/src/define_config.d.ts +26 -14
- package/build/src/define_config.js +5 -3
- package/build/src/doctor/checks.d.ts +10 -5
- package/build/src/doctor/checks.js +52 -8
- 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 +28 -6
- 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_console_warning.d.ts +8 -0
- package/build/src/host/admin_console_warning.js +18 -0
- package/build/src/host/admin_validators.d.ts +15 -11
- package/build/src/host/admin_validators.js +16 -9
- package/build/src/host/auth_host_config.d.ts +5 -0
- 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/console_denied_page.d.ts +6 -0
- package/build/src/host/console_denied_page.js +18 -0
- package/build/src/host/console_sso.d.ts +24 -0
- package/build/src/host/console_sso.js +70 -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/account_session_controller.js +10 -1
- package/build/src/host/controllers/agent_consent_controller.js +23 -5
- package/build/src/host/controllers/console_sso_controller.d.ts +12 -0
- package/build/src/host/controllers/console_sso_controller.js +61 -0
- package/build/src/host/controllers/interaction_controller.js +9 -3
- 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 +8 -0
- package/build/src/host/register_auth_host.js +116 -11
- 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 +6 -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 +12 -11
- 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
|
@@ -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,
|
|
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
|
|
358
|
-
*
|
|
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 `
|
|
1235
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
45
|
-
*
|
|
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
|
|
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:
|
|
52
|
-
*
|
|
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
|
|
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: '
|
|
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)
|
|
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.
|
|
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 {
|
|
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.
|