@adonis-agora/authkit-server 1.0.0-rc.7 → 1.0.0-rc.9
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 +19 -0
- package/build/host/views/account/apps.edge +1 -5
- package/build/host/views/account/extension.edge +77 -0
- package/build/host/views/account/mfa.edge +2 -0
- package/build/host/views/account/partials/extension_actions.edge +14 -0
- package/build/host/views/account/partials/extension_block.edge +197 -0
- package/build/host/views/account/partials/extension_value.edge +8 -0
- package/build/host/views/account/partials/nav.edge +14 -0
- package/build/host/views/account/security.edge +1 -5
- package/build/host/views/account/tokens.edge +2 -2
- package/build/host/views/partials/styles.edge +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.js +1 -0
- package/build/providers/authkit_server_provider.js +5 -0
- package/build/src/account/extension.d.ts +323 -0
- package/build/src/account/extension.js +36 -0
- package/build/src/account/http.d.ts +71 -0
- package/build/src/account/http.js +369 -0
- package/build/src/account/mount.d.ts +35 -0
- package/build/src/account/mount.js +99 -0
- package/build/src/account/registry.d.ts +48 -0
- package/build/src/account/registry.js +229 -0
- package/build/src/account/runtime.d.ts +277 -0
- package/build/src/account/runtime.js +623 -0
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/audit/audit_sink.js +4 -0
- package/build/src/console_switcher.d.ts +132 -0
- package/build/src/console_switcher.js +206 -0
- package/build/src/define_config.d.ts +18 -0
- package/build/src/define_config.js +4 -0
- package/build/src/host/admin_api/dto.d.ts +1 -1
- package/build/src/host/admin_console/admin_console_entry.d.ts +14 -0
- package/build/src/host/admin_console/admin_console_entry.js +80 -0
- package/build/src/host/admin_console/admin_shell_controller.js +8 -0
- package/build/src/host/admin_console/console_switcher_controller.d.ts +11 -0
- package/build/src/host/admin_console/console_switcher_controller.js +23 -0
- package/build/src/host/auth_host_config.d.ts +3 -0
- package/build/src/host/i18n.d.ts +36 -0
- package/build/src/host/i18n.js +38 -0
- package/build/src/host/rate_limit.d.ts +8 -0
- package/build/src/host/rate_limit.js +9 -0
- package/build/src/host/register_auth_host.d.ts +14 -0
- package/build/src/host/register_auth_host.js +140 -29
- package/build/src/host/renderers/edge_renderer.js +5 -0
- package/build/src/host/renderers/inertia_renderer.js +7 -1
- package/build/src/host/screen_props.d.ts +28 -0
- package/build/src/host/ui-dist/assets/index-Dq3dJVBv.js +67 -0
- package/build/src/host/ui-dist/assets/{index-DTSmD4RU.css → index-WNIyCEPA.css} +1 -1
- package/build/src/host/ui-dist/index.html +2 -2
- package/build/types.d.ts +3 -1
- package/package.json +3 -2
- package/build/src/host/ui-dist/assets/index-u0bMxkLg.js +0 -67
package/build/src/host/i18n.js
CHANGED
|
@@ -182,6 +182,25 @@ export const DEFAULT_MESSAGES = {
|
|
|
182
182
|
'account.delete.deleted': 'Your account has been permanently deleted.',
|
|
183
183
|
'account.delete.invalid_confirmation': 'Confirmation failed. Your account was not deleted.',
|
|
184
184
|
'account.delete.not_supported': 'Account deletion is not available in this installation.',
|
|
185
|
+
// Console de conta — páginas de extensions (account/extension).
|
|
186
|
+
'account.extension.logout': 'Log out',
|
|
187
|
+
'account.extension.nav_label': 'Account',
|
|
188
|
+
'account.extension.unknown_block': 'This section cannot be shown here.',
|
|
189
|
+
'account.extension.unsafe_link': 'This link was hidden because its address is not allowed.',
|
|
190
|
+
'account.extension.unsafe_script': 'This section was hidden because it loads a script from an address that is not allowed.',
|
|
191
|
+
'account.extension.load_failed': 'This page could not be loaded right now. Try again in a moment.',
|
|
192
|
+
'account.extension.next_page': 'Next page',
|
|
193
|
+
'account.extension.confirm_title': 'Confirm',
|
|
194
|
+
'account.extension.confirm_submit': 'Continue',
|
|
195
|
+
'account.extension.cancel': 'Cancel',
|
|
196
|
+
'account.extension.action_failed': 'That did not work. Try again in a moment.',
|
|
197
|
+
'account.extension.action_not_found': 'This action is not available.',
|
|
198
|
+
'account.extension.impersonation_refused': 'This action is not available while acting as another user.',
|
|
199
|
+
'account.extension.field_required': '{field} is required.',
|
|
200
|
+
'account.extension.field_too_long': '{field} is too long.',
|
|
201
|
+
'account.extension.field_invalid': '{field} has an invalid value.',
|
|
202
|
+
'account.extension.done': 'Done.',
|
|
203
|
+
'account.extension.provider_loading': 'Loading…',
|
|
185
204
|
// Console de conta — apps com acesso (account/apps).
|
|
186
205
|
'account.apps.page_title': 'Apps with access',
|
|
187
206
|
'account.apps.title': 'Apps with access',
|
|
@@ -1023,6 +1042,25 @@ export const PT_BR_MESSAGES = {
|
|
|
1023
1042
|
'account.delete.deleted': 'Sua conta foi deletada permanentemente.',
|
|
1024
1043
|
'account.delete.invalid_confirmation': 'Confirmação falhou. Sua conta NÃO foi deletada.',
|
|
1025
1044
|
'account.delete.not_supported': 'A deleção de conta não está disponível nesta instalação.',
|
|
1045
|
+
// Console de conta — páginas de extensions (account/extension).
|
|
1046
|
+
'account.extension.logout': 'Sair',
|
|
1047
|
+
'account.extension.nav_label': 'Conta',
|
|
1048
|
+
'account.extension.unknown_block': 'Esta seção não pode ser exibida aqui.',
|
|
1049
|
+
'account.extension.unsafe_link': 'Este link foi ocultado porque o endereço não é permitido.',
|
|
1050
|
+
'account.extension.unsafe_script': 'Esta seção foi ocultada porque carrega um script de um endereço não permitido.',
|
|
1051
|
+
'account.extension.load_failed': 'Não foi possível carregar esta página agora. Tente de novo em instantes.',
|
|
1052
|
+
'account.extension.next_page': 'Próxima página',
|
|
1053
|
+
'account.extension.confirm_title': 'Confirmar',
|
|
1054
|
+
'account.extension.confirm_submit': 'Continuar',
|
|
1055
|
+
'account.extension.cancel': 'Cancelar',
|
|
1056
|
+
'account.extension.action_failed': 'Não deu certo. Tente de novo em instantes.',
|
|
1057
|
+
'account.extension.action_not_found': 'Esta ação não está disponível.',
|
|
1058
|
+
'account.extension.impersonation_refused': 'Esta ação não está disponível enquanto você age como outro usuário.',
|
|
1059
|
+
'account.extension.field_required': '{field} é obrigatório.',
|
|
1060
|
+
'account.extension.field_too_long': '{field} é longo demais.',
|
|
1061
|
+
'account.extension.field_invalid': '{field} tem um valor inválido.',
|
|
1062
|
+
'account.extension.done': 'Feito.',
|
|
1063
|
+
'account.extension.provider_loading': 'Carregando…',
|
|
1026
1064
|
// Console de conta — apps com acesso (account/apps).
|
|
1027
1065
|
'account.apps.page_title': 'Apps com acesso',
|
|
1028
1066
|
'account.apps.title': 'Apps com acesso',
|
|
@@ -36,6 +36,14 @@ export interface AuthThrottles {
|
|
|
36
36
|
* anti-brute-force do código adivinhável, ANTES do lockout por interaction.
|
|
37
37
|
*/
|
|
38
38
|
otpLogin: ThrottleMiddleware;
|
|
39
|
+
/**
|
|
40
|
+
* Actions das páginas de extension do console de conta, keyed pela CONTA da sessão
|
|
41
|
+
* (bucket `authkit_account_actions`). `accountActions` responde 429 com a tela
|
|
42
|
+
* `throttled` (formulários); `accountActionsJson` responde o 429 cru (JSON API) —
|
|
43
|
+
* mesmo bucket, mesma contagem.
|
|
44
|
+
*/
|
|
45
|
+
accountActions: ThrottleMiddleware;
|
|
46
|
+
accountActionsJson: ThrottleMiddleware;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
49
|
* Service do `@adonisjs/limiter` resolvido de forma preguiçosa. Tipado como `any`
|
|
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
+
import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
9
10
|
import { brandFor } from './branding.js';
|
|
10
11
|
let limiterServicePromise;
|
|
11
12
|
/**
|
|
@@ -73,6 +74,12 @@ function buildThrottle(name, bucket, store, keyOf) {
|
|
|
73
74
|
export function createAuthThrottles(config) {
|
|
74
75
|
if (!config.enabled)
|
|
75
76
|
return undefined;
|
|
77
|
+
const accountActions = buildThrottle('authkit_account_actions', config.accountActions ?? { points: 30, duration: '1 min' }, config.store, (ctx) => {
|
|
78
|
+
const accountId = ctx.session?.get?.(ACCOUNT_SESSION_KEY);
|
|
79
|
+
return typeof accountId === 'string' && accountId !== ''
|
|
80
|
+
? `account:${accountId}`
|
|
81
|
+
: `account-ip:${ctx.request.ip?.() ?? 'unknown'}`;
|
|
82
|
+
});
|
|
76
83
|
return {
|
|
77
84
|
// Login/signup/forgot/reset: keyed por IP (default do HttpLimiter).
|
|
78
85
|
// `friendly`: rota de BROWSER — 429 vira a tela themeável `throttled`
|
|
@@ -102,6 +109,8 @@ export function createAuthThrottles(config) {
|
|
|
102
109
|
// Verificação de código OTP: keyed por IP (default), bucket próprio e mais
|
|
103
110
|
// apertado que o login. O namespace do nome mantém a contagem separada.
|
|
104
111
|
otpLogin: withFriendly429(buildThrottle('authkit_otp_login', config.otpLogin, config.store)),
|
|
112
|
+
accountActions: withFriendly429(accountActions),
|
|
113
|
+
accountActionsJson: accountActions,
|
|
105
114
|
};
|
|
106
115
|
}
|
|
107
116
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Router } from '@adonisjs/core/http';
|
|
2
|
+
import { type AccountExtensionRouteEntry } from '../account/mount.js';
|
|
2
3
|
import type { AuthSocialConfig, RateLimitConfigInput } from '../define_config.js';
|
|
3
4
|
import { type AccountPathKey, type AccountPathsOptions } from './account_paths.js';
|
|
4
5
|
import type { PolicyRouteOption } from './config_locks.js';
|
|
@@ -25,6 +26,14 @@ export declare function wantsJson(ctx: any): boolean;
|
|
|
25
26
|
* As roles permitidas são resolvidas em runtime do `authkit.server` (config lazy).
|
|
26
27
|
*/
|
|
27
28
|
export declare const adminGuard: (ctx: any, next: () => Promise<void>) => Promise<any>;
|
|
29
|
+
/**
|
|
30
|
+
* The admin console gate as the console switcher reads it: `{ allowed }`, never a response, never a
|
|
31
|
+
* throw. Used as the `authorize` of the admin console's `'agora.consoles'` entry.
|
|
32
|
+
*/
|
|
33
|
+
export declare function adminConsoleDecision(ctx: any): Promise<{
|
|
34
|
+
allowed: boolean;
|
|
35
|
+
reason?: 'unauthenticated' | 'forbidden';
|
|
36
|
+
}>;
|
|
28
37
|
/**
|
|
29
38
|
* Opções de montagem das rotas do host-kit.
|
|
30
39
|
*
|
|
@@ -304,6 +313,11 @@ export interface AuthHostRouteMap {
|
|
|
304
313
|
loginUrl: string;
|
|
305
314
|
/** Quais telas foram efetivamente montadas. */
|
|
306
315
|
screens: Record<keyof AccountScreensOptions, boolean>;
|
|
316
|
+
/**
|
|
317
|
+
* Account console extension pages (filled once every provider booted): id, path, label (a
|
|
318
|
+
* literal, or the message key) and order. `useAccountExtensions()` gives the localized nav.
|
|
319
|
+
*/
|
|
320
|
+
extensions: AccountExtensionRouteEntry[];
|
|
307
321
|
};
|
|
308
322
|
/** Console admin: prefixo resolvido, ou `null` quando não foi montado. */
|
|
309
323
|
admin: {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { getBootedApp } from '../../services/booted_app.js';
|
|
2
|
+
import { mountAccountExtensions } from '../account/mount.js';
|
|
3
|
+
import { registerAgoraConsole } from '../console_switcher.js';
|
|
1
4
|
import { resolveRateLimit } from '../define_config.js';
|
|
2
5
|
import { accountHome } from './account_home.js';
|
|
3
6
|
import { getAccountLoginUrl, setAccountLoginUrl } from './account_login_url.js';
|
|
@@ -6,6 +9,7 @@ import { resolveAccountRoles } from './account_roles.js';
|
|
|
6
9
|
import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
7
10
|
import { adminApiGuard } from './admin_api/admin_api_guard.js';
|
|
8
11
|
import { adminAuthorizeChecker, appInProduction } from './admin_authorize.js';
|
|
12
|
+
import { adminConsoleEntry } from './admin_console/admin_console_entry.js';
|
|
9
13
|
import { normalizeAdminApiPrefix, normalizeAdminPrefix, setAdminApiPrefix, setAdminPrefix, } from './admin_prefix.js';
|
|
10
14
|
import { getAuthHostConfig, markAuthHostAutoMounted, wasAuthHostAutoMounted, } from './auth_host_config.js';
|
|
11
15
|
import { consoleDeniedPage } from './console_denied_page.js';
|
|
@@ -86,6 +90,23 @@ async function checkAndRefreshIdle(ctx) {
|
|
|
86
90
|
return false;
|
|
87
91
|
}
|
|
88
92
|
}
|
|
93
|
+
/** Read-only twin of {@link checkAndRefreshIdle}: whether the idle timeout passed. Never writes. */
|
|
94
|
+
async function isIdleExpired(ctx) {
|
|
95
|
+
try {
|
|
96
|
+
const runtimeSettings = await resolveRuntimeSettings(ctx);
|
|
97
|
+
if (!runtimeSettings)
|
|
98
|
+
return false;
|
|
99
|
+
const policy = await resolveEffectiveSessionPolicy(runtimeSettings);
|
|
100
|
+
const idleMs = policy.idleTimeoutMinutes * 60 * 1000;
|
|
101
|
+
if (idleMs <= 0)
|
|
102
|
+
return false;
|
|
103
|
+
const lastSeen = ctx.session?.get(ACCOUNT_LAST_SEEN_KEY);
|
|
104
|
+
return lastSeen !== undefined && Date.now() - lastSeen > idleMs;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
89
110
|
/**
|
|
90
111
|
* Constrói a URL de redirect para o login da conta, preservando o destino
|
|
91
112
|
* original como `return_to` (URL-encoded). A URL de destino é o path + query
|
|
@@ -187,45 +208,65 @@ const accountGuard = async (ctx, next) => {
|
|
|
187
208
|
* As roles permitidas são resolvidas em runtime do `authkit.server` (config lazy).
|
|
188
209
|
*/
|
|
189
210
|
export const adminGuard = async (ctx, next) => {
|
|
211
|
+
const access = await evaluateAdminAccess(ctx);
|
|
212
|
+
switch (access.kind) {
|
|
213
|
+
case 'allowed':
|
|
214
|
+
return next();
|
|
215
|
+
case 'disabled':
|
|
216
|
+
// Defesa contra flag-drift: as rotas são montadas com `admin: true` em tempo de
|
|
217
|
+
// registro, ANTES de o config resolver. Se o config tiver `admin.enabled: false`,
|
|
218
|
+
// as rotas existem mas o console deve estar desligado — 404 (não vaza a existência).
|
|
219
|
+
return ctx.response.notFound();
|
|
220
|
+
case 'agent':
|
|
221
|
+
return ctx.response.status(403).send('');
|
|
222
|
+
case 'signed-out':
|
|
223
|
+
// `/account/login` é sempre o login da conta — NÃO muda com o prefixo admin.
|
|
224
|
+
return unauthenticated(ctx, buildLoginRedirect(ctx));
|
|
225
|
+
case 'idle':
|
|
226
|
+
return unauthenticated(ctx, buildLoginRedirect(ctx, 'reason=idle'));
|
|
227
|
+
case 'not-admin':
|
|
228
|
+
// Evita vazar a existência do console admin: redireciona para o accountHome
|
|
229
|
+
// (sem mostrar a URL do admin).
|
|
230
|
+
if (wantsJson(ctx)) {
|
|
231
|
+
return ctx.response
|
|
232
|
+
.status(403)
|
|
233
|
+
.send({ error: { code: 'forbidden', message: 'Admin role required.' } });
|
|
234
|
+
}
|
|
235
|
+
return redirectExact(ctx.response, accountHome(access.cfg));
|
|
236
|
+
case 'denied':
|
|
237
|
+
return consoleDenied(ctx, access.decision);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* The admin console gate: the session, then `admin.roles`, then `admin.authorize`.
|
|
242
|
+
*
|
|
243
|
+
* `probe: true` is the console switcher asking from another console: it reads the idle timeout
|
|
244
|
+
* without refreshing or ending the session.
|
|
245
|
+
*/
|
|
246
|
+
async function evaluateAdminAccess(ctx, options = {}) {
|
|
190
247
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
191
248
|
const cfg = service.config;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// as rotas existem mas o console deve estar desligado — 404 (não vaza a existência).
|
|
195
|
-
if (!cfg.admin.enabled) {
|
|
196
|
-
return ctx.response.notFound();
|
|
197
|
-
}
|
|
249
|
+
if (!cfg.admin.enabled)
|
|
250
|
+
return { kind: 'disabled' };
|
|
198
251
|
if (isAgentBrowser(ctx))
|
|
199
|
-
return
|
|
252
|
+
return { kind: 'agent' };
|
|
200
253
|
// Com `accountSession.acceptIdpSession`, a sessão do IdP também abre o console.
|
|
201
254
|
await ensureConsoleSession(ctx);
|
|
202
255
|
const accountId = ctx.session?.get(ACCOUNT_SESSION_KEY);
|
|
203
|
-
if (!accountId)
|
|
204
|
-
|
|
205
|
-
return unauthenticated(ctx, buildLoginRedirect(ctx));
|
|
206
|
-
}
|
|
256
|
+
if (!accountId)
|
|
257
|
+
return { kind: 'signed-out' };
|
|
207
258
|
// Idle timeout: também protege o console admin.
|
|
208
|
-
const idleExpired = await checkAndRefreshIdle(ctx);
|
|
209
|
-
if (idleExpired)
|
|
210
|
-
return
|
|
211
|
-
}
|
|
259
|
+
const idleExpired = options.probe ? await isIdleExpired(ctx) : await checkAndRefreshIdle(ctx);
|
|
260
|
+
if (idleExpired)
|
|
261
|
+
return { kind: 'idle' };
|
|
212
262
|
const allowed = cfg.admin.roles;
|
|
213
263
|
const account = await cfg.accountStore.findById(accountId);
|
|
214
264
|
// Resolve roles through the host's role authority (`resolveTokenRoles`) when set — the same source
|
|
215
265
|
// the token claim is minted from — so an app-role admin reaches the console. Falls back to the
|
|
216
266
|
// account's stored `globalRoles` when no hook is configured.
|
|
217
267
|
const roles = account ? await resolveAccountRoles(cfg, account) : [];
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// Evita vazar a existência do console admin: redireciona para o accountHome
|
|
221
|
-
// (sem mostrar a URL do admin).
|
|
222
|
-
if (wantsJson(ctx)) {
|
|
223
|
-
return ctx.response
|
|
224
|
-
.status(403)
|
|
225
|
-
.send({ error: { code: 'forbidden', message: 'Admin role required.' } });
|
|
226
|
-
}
|
|
227
|
-
return redirectExact(ctx.response, accountHome(cfg));
|
|
228
|
-
}
|
|
268
|
+
if (!roles.some((r) => allowed.includes(r)))
|
|
269
|
+
return { kind: 'not-admin', cfg };
|
|
229
270
|
// `admin.authorize`, after the roles: both must pass. Omitted, the console is open
|
|
230
271
|
// outside production and closed (403) in production.
|
|
231
272
|
const decision = cfg.admin.authorize
|
|
@@ -234,9 +275,45 @@ export const adminGuard = async (ctx, next) => {
|
|
|
234
275
|
? { allowed: false, status: 403 }
|
|
235
276
|
: { allowed: true };
|
|
236
277
|
if (!decision.allowed)
|
|
237
|
-
return
|
|
238
|
-
return
|
|
239
|
-
}
|
|
278
|
+
return { kind: 'denied', decision };
|
|
279
|
+
return { kind: 'allowed' };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The admin console gate as the console switcher reads it: `{ allowed }`, never a response, never a
|
|
283
|
+
* throw. Used as the `authorize` of the admin console's `'agora.consoles'` entry.
|
|
284
|
+
*/
|
|
285
|
+
export async function adminConsoleDecision(ctx) {
|
|
286
|
+
try {
|
|
287
|
+
const access = await evaluateAdminAccess(ctx, { probe: true });
|
|
288
|
+
if (access.kind === 'allowed')
|
|
289
|
+
return { allowed: true };
|
|
290
|
+
const unauthenticated = access.kind === 'signed-out' ||
|
|
291
|
+
access.kind === 'idle' ||
|
|
292
|
+
(access.kind === 'denied' && access.decision.status === 401);
|
|
293
|
+
return { allowed: false, reason: unauthenticated ? 'unauthenticated' : 'forbidden' };
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return { allowed: false, reason: 'forbidden' };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Registers the admin console in the shared `'agora.consoles'` registry, so every other
|
|
301
|
+
* `@adonis-agora` console's switcher can link to it. A no-op when the provider did not register the
|
|
302
|
+
* app (tests that mount routes on a bare router).
|
|
303
|
+
*/
|
|
304
|
+
function announceAdminConsole(adminPrefix) {
|
|
305
|
+
let app;
|
|
306
|
+
try {
|
|
307
|
+
app = getBootedApp();
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
void (async () => {
|
|
313
|
+
const logger = (await app.container.make('logger').catch(() => undefined));
|
|
314
|
+
await registerAgoraConsole(app, adminConsoleEntry(adminPrefix, adminConsoleDecision), logger);
|
|
315
|
+
})().catch(() => { });
|
|
316
|
+
}
|
|
240
317
|
/** The response for an `admin.authorize` denial: same rules in every `@adonis-agora` console. */
|
|
241
318
|
function consoleDenied(ctx, decision) {
|
|
242
319
|
const json = wantsJson(ctx);
|
|
@@ -286,6 +363,7 @@ const C = {
|
|
|
286
363
|
// Console React JSON API (session-authed, under {prefix}/api/*).
|
|
287
364
|
consoleShell: () => import('./admin_console/admin_shell_controller.js'),
|
|
288
365
|
consoleOverview: () => import('./admin_console/console_overview_controller.js'),
|
|
366
|
+
consoleSwitcher: () => import('./admin_console/console_switcher_controller.js'),
|
|
289
367
|
consoleUsers: () => import('./admin_console/console_users_controller.js'),
|
|
290
368
|
consoleSessions: () => import('./admin_console/console_sessions_controller.js'),
|
|
291
369
|
consoleClients: () => import('./admin_console/console_clients_controller.js'),
|
|
@@ -911,6 +989,32 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
911
989
|
router.post(`${apiBase}/mfa/passkeys/verify`, [C.accountMfaApi, 'passkeyRegisterVerify']);
|
|
912
990
|
})
|
|
913
991
|
.use([accountGuard]);
|
|
992
|
+
// Account console extensions (`@adonis-agora/authkit-server/account-extension`): pages other
|
|
993
|
+
// packages register from their provider's `boot()`. Same guard as the built-in screens; the
|
|
994
|
+
// page routes mount once every provider booted. `account: false` turns them off too.
|
|
995
|
+
const extensionEntries = [];
|
|
996
|
+
if (accountOpt !== false) {
|
|
997
|
+
const builtins = {};
|
|
998
|
+
if (mountSecurity)
|
|
999
|
+
builtins.security = securityPath;
|
|
1000
|
+
if (mountMfa)
|
|
1001
|
+
builtins.mfa = mfaPath;
|
|
1002
|
+
if (mountTokens)
|
|
1003
|
+
builtins.tokens = tokensPath;
|
|
1004
|
+
if (mountApps)
|
|
1005
|
+
builtins.apps = appsPath;
|
|
1006
|
+
if (mountOrgs)
|
|
1007
|
+
builtins.orgs = orgsPath;
|
|
1008
|
+
mountAccountExtensions(router, {
|
|
1009
|
+
guard: accountGuard,
|
|
1010
|
+
...(throttles
|
|
1011
|
+
? { throttle: { html: throttles.accountActions, json: throttles.accountActionsJson } }
|
|
1012
|
+
: {}),
|
|
1013
|
+
overrides: hostCfg?.accountConsole?.extensions,
|
|
1014
|
+
builtins,
|
|
1015
|
+
entries: extensionEntries,
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
914
1018
|
// API headless (Clerk-style) — montada fora do `accountGuard` (não depende da
|
|
915
1019
|
// sessão de CONTA), autenticada pelo resolver do host (`headless.resolveAccountId`).
|
|
916
1020
|
// `PUT` usa CSRF igual à API do console (middleware de shield do host).
|
|
@@ -936,6 +1040,8 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
936
1040
|
resolvedAdminPrefix = ap;
|
|
937
1041
|
// Persiste no singleton de processo para que controllers e views usem o mesmo prefixo.
|
|
938
1042
|
setAdminPrefix(ap);
|
|
1043
|
+
// Announce the console to the other `@adonis-agora` consoles (the switcher).
|
|
1044
|
+
announceAdminConsole(ap);
|
|
939
1045
|
router
|
|
940
1046
|
.group(() => {
|
|
941
1047
|
// Console admin (SPA React self-contained): todas as rotas GET servem o
|
|
@@ -949,6 +1055,10 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
949
1055
|
// ─── JSON API do console (session-authed via adminGuard upstream) ─────
|
|
950
1056
|
// GET {ap}/api/overview
|
|
951
1057
|
router.get(`${ap}/api/overview`, [C.consoleOverview, 'handle']);
|
|
1058
|
+
// Console switcher: the other `@adonis-agora` consoles this viewer may open, and the
|
|
1059
|
+
// global search across them (search BEFORE the list: fixed path first).
|
|
1060
|
+
router.get(`${ap}/api/consoles/search`, [C.consoleSwitcher, 'search']);
|
|
1061
|
+
router.get(`${ap}/api/consoles`, [C.consoleSwitcher, 'index']);
|
|
952
1062
|
// Usuários.
|
|
953
1063
|
router.get(`${ap}/api/users`, [C.consoleUsers, 'index']);
|
|
954
1064
|
router.get(`${ap}/api/users/:id`, [C.consoleUsers, 'show']);
|
|
@@ -1134,6 +1244,7 @@ export function registerAuthHost(router, opts = {}) {
|
|
|
1134
1244
|
mfa: mountMfa,
|
|
1135
1245
|
apps: mountApps,
|
|
1136
1246
|
},
|
|
1247
|
+
extensions: extensionEntries,
|
|
1137
1248
|
},
|
|
1138
1249
|
admin: resolvedAdminPrefix ? { prefix: resolvedAdminPrefix } : null,
|
|
1139
1250
|
adminApi: resolvedAdminApiPrefix ? { prefix: resolvedAdminApiPrefix } : null,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { accountNavFor } from '../../account/runtime.js';
|
|
1
2
|
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
2
3
|
import { accountPathsMap } from '../account_paths.js';
|
|
3
4
|
import { DEFAULT_MESSAGES, translate } from '../i18n.js';
|
|
@@ -40,9 +41,13 @@ export async function renderEdgeView(ctx, view, props) {
|
|
|
40
41
|
// `accountRoutes` — sem cada view conhecer o prefixo. Os action-subpaths
|
|
41
42
|
// fixos (`/password`, `/passkeys/verify`, ...) são concatenados na própria view.
|
|
42
43
|
const accountPaths = accountPathsMap();
|
|
44
|
+
// `accountNav` como prop global das telas do console de conta: telas embutidas montadas e
|
|
45
|
+
// páginas de extension visíveis para a conta (partial `authkit::account/partials/nav`).
|
|
46
|
+
const accountNav = props.accountNav ?? (await accountNavFor(ctx, view, props));
|
|
43
47
|
return ctx.view.render(`authkit::${view}`, {
|
|
44
48
|
loginUrl,
|
|
45
49
|
accountPaths,
|
|
50
|
+
accountNav,
|
|
46
51
|
...props,
|
|
47
52
|
t,
|
|
48
53
|
messages,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { accountNavFor } from '../../account/runtime.js';
|
|
1
2
|
import { DEFAULT_MESSAGES } from '../i18n.js';
|
|
2
3
|
import { renderEdgeView } from './edge_renderer.js';
|
|
3
4
|
/**
|
|
@@ -68,6 +69,11 @@ export function inertiaRenderer(opts) {
|
|
|
68
69
|
// Happy path: view vai ao Inertia.
|
|
69
70
|
// `messages` vai como shared prop para as páginas React traduzirem.
|
|
70
71
|
const messages = await resolveMessagesFromCtx(ctx);
|
|
71
|
-
|
|
72
|
+
const accountNav = props.accountNav ?? (await accountNavFor(ctx, view, props));
|
|
73
|
+
return ctx.inertia.render(`${opts.prefix}/${view}`, {
|
|
74
|
+
...(accountNav ? { accountNav } : {}),
|
|
75
|
+
...props,
|
|
76
|
+
messages,
|
|
77
|
+
});
|
|
72
78
|
};
|
|
73
79
|
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* typed per view, so a controller that drifts from these types fails the
|
|
9
9
|
* package's `tsc`.
|
|
10
10
|
*/
|
|
11
|
+
import type { ResolvedActionStep } from '../account/http.js';
|
|
12
|
+
import type { AccountNavEntry, ResolvedAccountPage } from '../account/runtime.js';
|
|
11
13
|
import type { OrgInvitation, OrgMember, OrgSummary } from '../accounts/account_store.js';
|
|
12
14
|
import type { PatRecord } from '../pat/pat_store.js';
|
|
13
15
|
import type { AccountConfirmProps, AccountEmailConfirmedProps, AccountLoginProps, AccountMfaProps, AccountSecurityProps } from './account_screen_props.js';
|
|
@@ -279,6 +281,31 @@ export interface AccountOrgsProps extends WithMessages {
|
|
|
279
281
|
invitation?: OrgInvitation | null;
|
|
280
282
|
token?: string;
|
|
281
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* `account/extension` — a page an account console extension added. `page` is the resolved page
|
|
286
|
+
* (texts translated, values formatted); `step` is set on the confirmation step of an action.
|
|
287
|
+
* `accountNav` (the console navigation) is added by the renderer to every account screen.
|
|
288
|
+
*/
|
|
289
|
+
export interface AccountExtensionProps extends WithMessages {
|
|
290
|
+
csrfToken: string;
|
|
291
|
+
page: ResolvedAccountPage;
|
|
292
|
+
step: ResolvedActionStep | null;
|
|
293
|
+
flash: {
|
|
294
|
+
tone: 'success' | 'danger';
|
|
295
|
+
text: string;
|
|
296
|
+
} | null;
|
|
297
|
+
/** The response's CSP nonce, for the scripts of `component` blocks. */
|
|
298
|
+
cspNonce: string | null;
|
|
299
|
+
accountNav?: AccountNavEntry[] | null;
|
|
300
|
+
/** `<html lang>` of the console locale. */
|
|
301
|
+
htmlLang?: string;
|
|
302
|
+
/** Edge only: the `<script>` tag of a `component` block's script (nonce included). */
|
|
303
|
+
scriptTag?: (script: {
|
|
304
|
+
src: string;
|
|
305
|
+
integrity: string | null;
|
|
306
|
+
module: boolean;
|
|
307
|
+
}) => string;
|
|
308
|
+
}
|
|
282
309
|
/** Props of every screen, by view name. */
|
|
283
310
|
export interface AuthkitScreenProps {
|
|
284
311
|
login: LoginProps;
|
|
@@ -302,6 +329,7 @@ export interface AuthkitScreenProps {
|
|
|
302
329
|
'account/orgs': AccountOrgsProps;
|
|
303
330
|
'account/confirm': AccountConfirmProps;
|
|
304
331
|
'account/email-confirmed': AccountEmailConfirmedProps;
|
|
332
|
+
'account/extension': AccountExtensionProps;
|
|
305
333
|
}
|
|
306
334
|
/** Every screen authkit-server renders. */
|
|
307
335
|
export type AuthkitScreen = keyof AuthkitScreenProps;
|