@adonis-agora/authkit-server 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/clients_create.js +4 -4
- package/build/commands/configure.js +3 -2
- package/build/commands/doctor.js +8 -5
- package/build/commands/eject.js +1 -1
- package/build/commands/expire_scan.js +7 -5
- package/build/commands/import_users.js +1 -1
- package/build/commands/keys_rotate.js +7 -4
- package/build/host/views/account/apps.edge +5 -5
- package/build/host/views/account/login.edge +1 -1
- package/build/host/views/account/mfa.edge +7 -7
- package/build/host/views/account/orgs.edge +5 -5
- package/build/host/views/account/security.edge +10 -10
- package/build/host/views/account/tokens.edge +5 -5
- package/build/index.d.ts +7 -7
- package/build/index.js +5 -5
- package/build/providers/authkit_server_provider.d.ts +2 -2
- package/build/providers/authkit_server_provider.js +20 -11
- package/build/services/main.d.ts +1 -1
- package/build/services/main.js +2 -2
- package/build/src/accounts/lucid_account_store.d.ts +3 -3
- package/build/src/accounts/lucid_account_store.js +6 -8
- package/build/src/accounts/lucid_store/core.js +1 -1
- package/build/src/accounts/lucid_store/mfa.js +1 -1
- package/build/src/accounts/lucid_store/organizations.js +2 -4
- package/build/src/accounts/lucid_store/password_hygiene.js +1 -4
- package/build/src/accounts/lucid_store/shared.d.ts +1 -1
- package/build/src/accounts/lucid_stores.d.ts +3 -3
- package/build/src/accounts/lucid_stores.js +2 -2
- package/build/src/adapters/database_adapter.js +3 -1
- package/build/src/adapters/factory.js +1 -1
- package/build/src/adapters/redis_adapter.js +2 -1
- package/build/src/commands/expire_scan_command.js +19 -5
- package/build/src/commands/settings_commands.js +22 -5
- package/build/src/define_config.d.ts +20 -20
- package/build/src/define_config.js +62 -67
- package/build/src/doctor/checks.js +45 -20
- package/build/src/events/dispatcher.d.ts +1 -1
- package/build/src/events/dispatcher.js +10 -10
- package/build/src/host/account_api/account_api_controller.js +95 -39
- package/build/src/host/account_deletion_ops.d.ts +4 -4
- package/build/src/host/account_deletion_ops.js +5 -5
- package/build/src/host/account_deletion_service.d.ts +2 -2
- package/build/src/host/account_deletion_service.js +2 -2
- package/build/src/host/account_export_service.d.ts +1 -1
- package/build/src/host/account_export_service.js +4 -6
- package/build/src/host/account_home.d.ts +0 -8
- package/build/src/host/account_home.js +6 -4
- package/build/src/host/account_login_url.d.ts +6 -3
- package/build/src/host/account_login_url.js +16 -8
- package/build/src/host/account_paths.d.ts +120 -0
- package/build/src/host/account_paths.js +161 -0
- package/build/src/host/admin_api/admin_orgs_service.d.ts +2 -2
- package/build/src/host/admin_api/admin_orgs_service.js +3 -2
- package/build/src/host/admin_api/admin_users_service.d.ts +12 -12
- package/build/src/host/admin_api/admin_users_service.js +20 -20
- package/build/src/host/admin_api/api_clients_controller.js +2 -2
- package/build/src/host/admin_api/api_misc_controller.js +2 -2
- package/build/src/host/admin_api/api_orgs_controller.js +3 -3
- package/build/src/host/admin_api/api_settings_controller.js +11 -5
- package/build/src/host/admin_api/api_users_controller.d.ts +2 -2
- package/build/src/host/admin_api/api_users_controller.js +37 -38
- package/build/src/host/admin_api/dto.d.ts +4 -4
- package/build/src/host/admin_clients_service.js +5 -4
- package/build/src/host/admin_console/admin_shell_controller.js +4 -2
- package/build/src/host/admin_console/console_audit_controller.js +1 -1
- package/build/src/host/admin_console/console_clients_controller.js +5 -3
- package/build/src/host/admin_console/console_impersonation_controller.js +1 -1
- package/build/src/host/admin_console/console_keys_controller.js +2 -4
- package/build/src/host/admin_console/console_orgs_controller.js +3 -3
- package/build/src/host/admin_console/console_roles_controller.js +30 -12
- package/build/src/host/admin_console/console_sessions_controller.js +3 -3
- package/build/src/host/admin_console/console_settings_controller.js +11 -5
- package/build/src/host/admin_console/console_users_controller.d.ts +2 -2
- package/build/src/host/admin_console/console_users_controller.js +46 -47
- package/build/src/host/admin_prefix.js +2 -2
- package/build/src/host/admin_sessions_service.js +1 -3
- package/build/src/host/adonis_auth_sync.d.ts +1 -1
- package/build/src/host/adonis_auth_user_provider.d.ts +1 -1
- package/build/src/host/app_key.js +1 -1
- package/build/src/host/avatar_storage.js +1 -3
- package/build/src/host/config_locks.js +1 -3
- package/build/src/host/console_session.js +1 -1
- package/build/src/host/controllers/account_apps_controller.js +3 -2
- package/build/src/host/controllers/account_confirm_controller.js +2 -6
- package/build/src/host/controllers/account_mfa_controller.js +63 -12
- package/build/src/host/controllers/account_orgs_controller.js +47 -27
- package/build/src/host/controllers/account_security_controller.d.ts +2 -2
- package/build/src/host/controllers/account_security_controller.js +114 -115
- package/build/src/host/controllers/account_session_controller.js +5 -5
- package/build/src/host/controllers/account_tokens_controller.js +3 -2
- package/build/src/host/controllers/interaction_controller.js +59 -22
- package/build/src/host/controllers/registration_controller.js +28 -13
- package/build/src/host/default_mailer.js +11 -7
- package/build/src/host/durable/account_deletion_workflow.d.ts +2 -2
- package/build/src/host/durable/account_deletion_workflow.js +14 -14
- package/build/src/host/durable/account_export_workflow.d.ts +2 -2
- package/build/src/host/durable/account_export_workflow.js +12 -12
- package/build/src/host/durable/index.d.ts +5 -5
- package/build/src/host/durable/index.js +6 -6
- package/build/src/host/i18n.d.ts +1376 -1376
- package/build/src/host/i18n.js +1380 -1380
- package/build/src/host/impersonation.js +1 -7
- package/build/src/host/key_rotation_actions.d.ts +1 -1
- package/build/src/host/login_attempt.js +35 -6
- package/build/src/host/login_notify.js +1 -1
- package/build/src/host/otp_lockout.js +1 -3
- package/build/src/host/rate_limit.js +1 -1
- package/build/src/host/register_auth_host.d.ts +30 -0
- package/build/src/host/register_auth_host.js +120 -74
- package/build/src/host/renderers/edge_renderer.js +15 -2
- package/build/src/host/renderers/inertia_renderer.d.ts +9 -1
- package/build/src/host/renderers/inertia_renderer.js +9 -1
- package/build/src/host/runtime_settings.js +1 -1
- package/build/src/host/runtime_toggles.d.ts +3 -3
- package/build/src/host/runtime_toggles.js +69 -24
- package/build/src/host/security_notice_service.d.ts +1 -1
- package/build/src/host/security_notice_service.js +1 -1
- package/build/src/host/session_context.js +1 -1
- package/build/src/host/sudo/index.d.ts +3 -3
- package/build/src/host/sudo/index.js +3 -3
- package/build/src/host/sudo/methods/magic_link.js +6 -5
- package/build/src/host/sudo/methods/passkey.js +7 -4
- package/build/src/host/sudo/methods/password.js +8 -3
- package/build/src/host/sudo/runtime.js +6 -13
- package/build/src/host/sudo_mode.js +3 -2
- package/build/src/host/ui-dist/assets/index-BQoTDiLV.js +137 -0
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/keys/jwks_manager.js +1 -1
- package/build/src/keys/keystore.js +1 -1
- package/build/src/keys/keystore_manager.d.ts +2 -2
- package/build/src/keys/keystore_manager.js +2 -2
- package/build/src/keys/keystore_vault.js +8 -4
- package/build/src/mixins/with_audit_log.d.ts +1 -1
- package/build/src/mixins/with_auth_user.d.ts +1 -1
- package/build/src/mixins/with_auth_user.js +1 -1
- package/build/src/mixins/with_credentials.d.ts +1 -1
- package/build/src/mixins/with_mfa.d.ts +1 -1
- package/build/src/mixins/with_personal_access_token.d.ts +1 -1
- package/build/src/mixins/with_provider_identity.d.ts +1 -1
- package/build/src/mixins/with_webauthn_credential.d.ts +1 -1
- package/build/src/observability/diagnostics_bridge.js +2 -2
- package/build/src/observability/metrics_service.js +1 -1
- package/build/src/observability/telescope/data_providers.d.ts +1 -1
- package/build/src/observability/telescope/data_providers.js +29 -43
- package/build/src/observability/telescope/extension.d.ts +1 -1
- package/build/src/observability/telescope/extension.js +58 -58
- package/build/src/observability/telescope/index.d.ts +2 -2
- package/build/src/observability/telescope/index.js +2 -2
- package/build/src/observability/wire_provider_events.d.ts +1 -1
- package/build/src/observability/wire_provider_events.js +11 -11
- package/build/src/password/common_passwords.js +1 -1
- package/build/src/password/password_manager.d.ts +1 -1
- package/build/src/password/password_manager.js +4 -4
- package/build/src/pat/lucid_pat_store.js +2 -6
- package/build/src/pat/pat_tokens.js +1 -1
- package/build/src/provider/interaction_actions.js +1 -1
- package/build/src/provider/key_rotation_scheduler.js +3 -1
- package/build/src/provider/logout_sources.js +2 -8
- package/build/src/provider/oidc_service.d.ts +2 -2
- package/build/src/provider/oidc_service.js +22 -14
- package/build/src/provider/single_flight_lock.js +1 -3
- package/build/src/provider/token_exchange.d.ts +1 -1
- package/build/src/schema/ensure.js +3 -1
- package/build/stubs/ui/react/components/auth_shell.tsx +19 -19
- package/build/stubs/ui/react/pages/account/login.tsx +6 -7
- package/build/stubs/ui/react/pages/account/mfa.tsx +16 -15
- package/build/stubs/ui/react/pages/account/tokens.tsx +27 -17
- package/build/stubs/ui/react/pages/consent.tsx +12 -12
- package/build/stubs/ui/react/pages/forgot.tsx +7 -7
- package/build/stubs/ui/react/pages/login.tsx +22 -24
- package/build/stubs/ui/react/pages/mfa-challenge.tsx +9 -10
- package/build/stubs/ui/react/pages/reset.tsx +10 -10
- package/build/stubs/ui/react/pages/signup.tsx +30 -16
- package/build/stubs/ui/react/pages/verify-email.tsx +6 -6
- package/package.json +1 -1
- package/stubs/main.ts +2 -2
- package/stubs/ui/react/components/auth_shell.tsx +19 -19
- package/stubs/ui/react/pages/account/login.tsx +6 -7
- package/stubs/ui/react/pages/account/mfa.tsx +16 -15
- package/stubs/ui/react/pages/account/tokens.tsx +27 -17
- package/stubs/ui/react/pages/consent.tsx +12 -12
- package/stubs/ui/react/pages/forgot.tsx +7 -7
- package/stubs/ui/react/pages/login.tsx +22 -24
- package/stubs/ui/react/pages/mfa-challenge.tsx +9 -10
- package/stubs/ui/react/pages/reset.tsx +10 -10
- package/stubs/ui/react/pages/signup.tsx +30 -16
- package/stubs/ui/react/pages/verify-email.tsx +6 -6
- package/build/src/host/ui-dist/assets/index-BmI_J87g.js +0 -137
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import QRCode from 'qrcode';
|
|
3
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
4
|
-
import { translate } from '../i18n.js';
|
|
5
3
|
import { supportsPasskeys } from '../../accounts/account_store.js';
|
|
6
|
-
import {
|
|
4
|
+
import { accountPath } from '../account_paths.js';
|
|
5
|
+
import { translate } from '../i18n.js';
|
|
6
|
+
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
7
7
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
8
|
+
import { dispatchSecurityNotice } from '../security_notice_service.js';
|
|
8
9
|
import { requireSudo } from '../sudo_mode.js';
|
|
9
10
|
/** Desafio WebAuthn pendente (registro) guardado na sessão entre begin/finish. */
|
|
10
11
|
const PASSKEY_REG_CHALLENGE_KEY = 'authkit_passkey_reg_challenge';
|
|
12
|
+
/**
|
|
13
|
+
* `true` quando a requisição é uma NAVEGAÇÃO (form/browser) e não um XHR/fetch:
|
|
14
|
+
* o cliente aceita `text/html` e NÃO pede explicitamente `application/json`.
|
|
15
|
+
*
|
|
16
|
+
* Endpoints duais (JSON para o app React / redirect para o `<form>` clássico)
|
|
17
|
+
* decidem a resposta por aqui. Um `fetch` sem `Accept` explícito manda coringa
|
|
18
|
+
* (`*` + `/` + `*`) → não é navegação → JSON (mantém o comportamento de sempre
|
|
19
|
+
* da `mfa.edge`).
|
|
20
|
+
*/
|
|
21
|
+
function isNavigationRequest(ctx) {
|
|
22
|
+
const accept = (ctx.request.header('accept') ?? '').toLowerCase();
|
|
23
|
+
const wantsJson = accept.includes('application/json');
|
|
24
|
+
const wantsHtml = accept.includes('text/html');
|
|
25
|
+
return wantsHtml && !wantsJson;
|
|
26
|
+
}
|
|
11
27
|
/**
|
|
12
28
|
* Console de MFA da conta (atrás do account_auth middleware). Enrollment TOTP
|
|
13
29
|
* com QR, confirmação com código, exibição única dos recovery codes e disable.
|
|
@@ -90,8 +106,28 @@ export default class AccountMfaController {
|
|
|
90
106
|
if (accountForNotice) {
|
|
91
107
|
const ts = new Date().toISOString();
|
|
92
108
|
const ip = ctx.request.ip?.() ?? null;
|
|
93
|
-
await dispatchSecurityNotice(ctx, {
|
|
94
|
-
|
|
109
|
+
await dispatchSecurityNotice(ctx, {
|
|
110
|
+
account: { id: userId, email: accountForNotice.email },
|
|
111
|
+
kind: 'passkey_added',
|
|
112
|
+
ip,
|
|
113
|
+
timestamp: ts,
|
|
114
|
+
}, cfg.mail, cfg.audit);
|
|
115
|
+
await dispatchSecurityNotice(ctx, {
|
|
116
|
+
account: { id: userId, email: accountForNotice.email },
|
|
117
|
+
kind: 'mfa_enabled',
|
|
118
|
+
ip,
|
|
119
|
+
timestamp: ts,
|
|
120
|
+
}, cfg.mail, cfg.audit);
|
|
121
|
+
}
|
|
122
|
+
// Resposta DUAL. A cerimônia WebAuthn da tela built-in (`mfa.edge`) chama
|
|
123
|
+
// este endpoint por `fetch` e só olha `res.ok` — mas um `<form>` HTML
|
|
124
|
+
// clássico (progressive enhancement, ou host que POSTa a assertion sem JS)
|
|
125
|
+
// fica encarando o JSON `{ok:true}` cru. Navegação (o cliente ACEITA HTML e
|
|
126
|
+
// NÃO pede JSON) → redirect para a tela de MFA (respeitando `accountRoutes`);
|
|
127
|
+
// XHR/fetch → JSON de sempre. O fetch da view manda Accept coringa, então
|
|
128
|
+
// cai no ramo JSON e continua funcionando.
|
|
129
|
+
if (isNavigationRequest(ctx)) {
|
|
130
|
+
return ctx.response.redirect(accountPath('mfa'));
|
|
95
131
|
}
|
|
96
132
|
return { ok: true };
|
|
97
133
|
}
|
|
@@ -116,9 +152,14 @@ export default class AccountMfaController {
|
|
|
116
152
|
// Notificação de segurança: passkey_removed (best-effort).
|
|
117
153
|
const accountForNotice = await cfg.accountStore.findById(userId);
|
|
118
154
|
if (accountForNotice) {
|
|
119
|
-
await dispatchSecurityNotice(ctx, {
|
|
155
|
+
await dispatchSecurityNotice(ctx, {
|
|
156
|
+
account: { id: userId, email: accountForNotice.email },
|
|
157
|
+
kind: 'passkey_removed',
|
|
158
|
+
ip: ctx.request.ip?.() ?? null,
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
}, cfg.mail, cfg.audit);
|
|
120
161
|
}
|
|
121
|
-
return ctx.response.redirect('
|
|
162
|
+
return ctx.response.redirect(accountPath('mfa'));
|
|
122
163
|
}
|
|
123
164
|
/** POST /account/mfa/enroll — gera segredo pendente + QR e mostra a confirmação. */
|
|
124
165
|
async enroll(ctx) {
|
|
@@ -133,7 +174,7 @@ export default class AccountMfaController {
|
|
|
133
174
|
return sudoResultEnroll;
|
|
134
175
|
const started = await cfg.accountStore.startTotpEnrollment?.(userId);
|
|
135
176
|
if (!started) {
|
|
136
|
-
return ctx.response.redirect('
|
|
177
|
+
return ctx.response.redirect(accountPath('mfa'));
|
|
137
178
|
}
|
|
138
179
|
// QR renderizado server-side como data-URL e passado como prop.
|
|
139
180
|
const qrDataUrl = await QRCode.toDataURL(started.otpauthUri);
|
|
@@ -176,11 +217,16 @@ export default class AccountMfaController {
|
|
|
176
217
|
// Notificação de segurança: mfa_enabled (best-effort).
|
|
177
218
|
const accountForMfaNotice = await cfg.accountStore.findById(userId);
|
|
178
219
|
if (accountForMfaNotice) {
|
|
179
|
-
await dispatchSecurityNotice(ctx, {
|
|
220
|
+
await dispatchSecurityNotice(ctx, {
|
|
221
|
+
account: { id: userId, email: accountForMfaNotice.email },
|
|
222
|
+
kind: 'mfa_enabled',
|
|
223
|
+
ip: ctx.request.ip?.() ?? null,
|
|
224
|
+
timestamp: new Date().toISOString(),
|
|
225
|
+
}, cfg.mail, cfg.audit);
|
|
180
226
|
}
|
|
181
227
|
// Mostra os recovery codes UMA vez (flash) e volta pro estado "ativado".
|
|
182
228
|
ctx.session.flash('recoveryCodes', result.recoveryCodes ?? []);
|
|
183
|
-
return ctx.response.redirect('
|
|
229
|
+
return ctx.response.redirect(accountPath('mfa'));
|
|
184
230
|
}
|
|
185
231
|
/** POST /account/mfa/disable — desliga o MFA. */
|
|
186
232
|
async disable(ctx) {
|
|
@@ -201,8 +247,13 @@ export default class AccountMfaController {
|
|
|
201
247
|
// Notificação de segurança: mfa_disabled (best-effort).
|
|
202
248
|
const accountForDisableNotice = await cfg.accountStore.findById(userId);
|
|
203
249
|
if (accountForDisableNotice) {
|
|
204
|
-
await dispatchSecurityNotice(ctx, {
|
|
250
|
+
await dispatchSecurityNotice(ctx, {
|
|
251
|
+
account: { id: userId, email: accountForDisableNotice.email },
|
|
252
|
+
kind: 'mfa_disabled',
|
|
253
|
+
ip: ctx.request.ip?.() ?? null,
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
}, cfg.mail, cfg.audit);
|
|
205
256
|
}
|
|
206
|
-
return ctx.response.redirect('
|
|
257
|
+
return ctx.response.redirect(accountPath('mfa'));
|
|
207
258
|
}
|
|
208
259
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { supportsOrganizations } from '../../accounts/account_store.js';
|
|
3
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
4
3
|
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
4
|
+
import { accountPath } from '../account_paths.js';
|
|
5
|
+
import { ACTIVE_ORG_COOKIE, ACTIVE_ORG_COOKIE_TTL, encodeActiveOrgCookie, } from '../active_org_cookie.js';
|
|
6
|
+
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
5
7
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
6
8
|
import { isRoleInCatalog } from '../runtime_toggles.js';
|
|
7
|
-
import { ACTIVE_ORG_COOKIE, ACTIVE_ORG_COOKIE_TTL, encodeActiveOrgCookie, } from '../active_org_cookie.js';
|
|
8
9
|
/**
|
|
9
10
|
* Console de conta — Organizations. Server-rendered, padrão dos outros controllers
|
|
10
11
|
* de conta (account_tokens_controller, account_security_controller, etc.).
|
|
@@ -52,9 +53,7 @@ export default class AccountOrgsController {
|
|
|
52
53
|
availableRoles: cfg.organizations.roles,
|
|
53
54
|
messages,
|
|
54
55
|
};
|
|
55
|
-
return cfg.render
|
|
56
|
-
? cfg.render(ctx, 'account/orgs', props)
|
|
57
|
-
: response.notFound();
|
|
56
|
+
return cfg.render ? cfg.render(ctx, 'account/orgs', props) : response.notFound();
|
|
58
57
|
}
|
|
59
58
|
/** POST /account/orgs — criar nova org (requer allowSelfCreate). */
|
|
60
59
|
async store(ctx) {
|
|
@@ -69,7 +68,7 @@ export default class AccountOrgsController {
|
|
|
69
68
|
const name = request.input('name', '').trim();
|
|
70
69
|
const slug = request.input('slug', '').trim();
|
|
71
70
|
if (!name || !slug)
|
|
72
|
-
return response.redirect('
|
|
71
|
+
return response.redirect(accountPath('orgs'));
|
|
73
72
|
try {
|
|
74
73
|
await store.createOrg({ name, slug, ownerAccountId: accountId });
|
|
75
74
|
await cfg.audit?.record({ type: 'organization.created', accountId, metadata: { slug } });
|
|
@@ -77,7 +76,7 @@ export default class AccountOrgsController {
|
|
|
77
76
|
catch {
|
|
78
77
|
// slug duplicado ou outro erro — redireciona sem mensagem de erro específica
|
|
79
78
|
}
|
|
80
|
-
return response.redirect('
|
|
79
|
+
return response.redirect(accountPath('orgs'));
|
|
81
80
|
}
|
|
82
81
|
/** POST /account/orgs/:id/activate — define org ativa (valida membership). */
|
|
83
82
|
async activate(ctx) {
|
|
@@ -91,12 +90,16 @@ export default class AccountOrgsController {
|
|
|
91
90
|
const orgId = params.id;
|
|
92
91
|
const membership = await store.getOrgMembership(orgId, accountId);
|
|
93
92
|
if (!membership)
|
|
94
|
-
return response.redirect('
|
|
93
|
+
return response.redirect(accountPath('orgs'));
|
|
95
94
|
const org = await store.findOrgById(orgId);
|
|
96
95
|
if (!org)
|
|
97
|
-
return response.redirect('
|
|
96
|
+
return response.redirect(accountPath('orgs'));
|
|
98
97
|
// Grava cookie de org ativa
|
|
99
|
-
const cookieValue = encodeActiveOrgCookie({
|
|
98
|
+
const cookieValue = encodeActiveOrgCookie({
|
|
99
|
+
orgId,
|
|
100
|
+
orgSlug: org.slug,
|
|
101
|
+
orgRole: membership.role,
|
|
102
|
+
});
|
|
100
103
|
ctx.response.cookie(ACTIVE_ORG_COOKIE, cookieValue, {
|
|
101
104
|
httpOnly: true,
|
|
102
105
|
sameSite: 'lax',
|
|
@@ -104,8 +107,12 @@ export default class AccountOrgsController {
|
|
|
104
107
|
secure: ctx.request.secure(),
|
|
105
108
|
path: '/',
|
|
106
109
|
});
|
|
107
|
-
await cfg.audit?.record({
|
|
108
|
-
|
|
110
|
+
await cfg.audit?.record({
|
|
111
|
+
type: 'organization.switched',
|
|
112
|
+
accountId,
|
|
113
|
+
metadata: { orgId, orgSlug: org.slug },
|
|
114
|
+
});
|
|
115
|
+
return response.redirect(accountPath('orgs'));
|
|
109
116
|
}
|
|
110
117
|
/** POST /account/orgs/deactivate — remove org ativa. */
|
|
111
118
|
async deactivate(ctx) {
|
|
@@ -115,7 +122,7 @@ export default class AccountOrgsController {
|
|
|
115
122
|
const accountId = session.get(ACCOUNT_SESSION_KEY);
|
|
116
123
|
ctx.response.clearCookie(ACTIVE_ORG_COOKIE, { path: '/' });
|
|
117
124
|
await cfg.audit?.record({ type: 'organization.deactivated', accountId });
|
|
118
|
-
return response.redirect('
|
|
125
|
+
return response.redirect(accountPath('orgs'));
|
|
119
126
|
}
|
|
120
127
|
/** POST /account/orgs/:id/leave — sai da org (verifica last_owner). */
|
|
121
128
|
async leave(ctx) {
|
|
@@ -128,9 +135,13 @@ export default class AccountOrgsController {
|
|
|
128
135
|
return response.forbidden();
|
|
129
136
|
const result = await store.removeOrgMember(params.id, accountId);
|
|
130
137
|
if (result.ok) {
|
|
131
|
-
await cfg.audit?.record({
|
|
138
|
+
await cfg.audit?.record({
|
|
139
|
+
type: 'organization.member_removed',
|
|
140
|
+
accountId,
|
|
141
|
+
metadata: { orgId: params.id, self: true },
|
|
142
|
+
});
|
|
132
143
|
}
|
|
133
|
-
return response.redirect('
|
|
144
|
+
return response.redirect(accountPath('orgs'));
|
|
134
145
|
}
|
|
135
146
|
/** POST /account/orgs/:id/invite — convida membro por e-mail. */
|
|
136
147
|
async invite(ctx) {
|
|
@@ -149,7 +160,7 @@ export default class AccountOrgsController {
|
|
|
149
160
|
const email = request.input('email', '').trim();
|
|
150
161
|
const role = request.input('role', 'member').trim();
|
|
151
162
|
if (!email)
|
|
152
|
-
return response.redirect('
|
|
163
|
+
return response.redirect(accountPath('orgs'));
|
|
153
164
|
// H4: valida o role contra o catálogo efetivo de roles da org (runtime →
|
|
154
165
|
// config → defaults). Role fora do catálogo é rejeitada (não cria convite).
|
|
155
166
|
// Usa o helper PURO `isRoleInCatalog` (mesmo ponto de verdade do caminho admin).
|
|
@@ -160,7 +171,9 @@ export default class AccountOrgsController {
|
|
|
160
171
|
invitationTtlHours: cfg.organizations.invitationTtlHours,
|
|
161
172
|
}, params.id);
|
|
162
173
|
if (!roleValid) {
|
|
163
|
-
return response.unprocessableEntity({
|
|
174
|
+
return response.unprocessableEntity({
|
|
175
|
+
error: { code: 'invalid_role', message: 'Role inválida.' },
|
|
176
|
+
});
|
|
164
177
|
}
|
|
165
178
|
// H4: no fluxo member-facing, só um OWNER pode conceder a role `owner`.
|
|
166
179
|
// Um admin (não-owner) tentando convidar como `owner` é escalonamento.
|
|
@@ -177,7 +190,7 @@ export default class AccountOrgsController {
|
|
|
177
190
|
// Dispara e-mail via mail hook (best-effort)
|
|
178
191
|
if (cfg.mail?.onOrgInvitation) {
|
|
179
192
|
const org = await store.findOrgById(params.id);
|
|
180
|
-
const acceptUrl = `${ctx.request.protocol()}://${ctx.request.host()}
|
|
193
|
+
const acceptUrl = `${ctx.request.protocol()}://${ctx.request.host()}${accountPath('orgs')}/invitations/${token}/accept`;
|
|
181
194
|
try {
|
|
182
195
|
await cfg.mail.onOrgInvitation({
|
|
183
196
|
email,
|
|
@@ -198,7 +211,7 @@ export default class AccountOrgsController {
|
|
|
198
211
|
accountId,
|
|
199
212
|
metadata: { orgId: params.id, email, role },
|
|
200
213
|
});
|
|
201
|
-
return response.redirect('
|
|
214
|
+
return response.redirect(accountPath('orgs'));
|
|
202
215
|
}
|
|
203
216
|
/** GET /account/orgs/invitations/:token/accept — mostra tela de aceite. */
|
|
204
217
|
async showAcceptInvitation(ctx) {
|
|
@@ -213,7 +226,7 @@ export default class AccountOrgsController {
|
|
|
213
226
|
if (!accountId) {
|
|
214
227
|
// Não logado: redireciona para login (configurável) com return URL
|
|
215
228
|
const loginUrl = getAccountLoginUrl();
|
|
216
|
-
const returnTo = encodeURIComponent(
|
|
229
|
+
const returnTo = encodeURIComponent(`${accountPath('orgs')}/invitations/${params.token}/accept`);
|
|
217
230
|
const sep = loginUrl.includes('?') ? '&' : '?';
|
|
218
231
|
return response.redirect(`${loginUrl}${sep}returnTo=${returnTo}`);
|
|
219
232
|
}
|
|
@@ -243,7 +256,7 @@ export default class AccountOrgsController {
|
|
|
243
256
|
const tokenHash = createHash('sha256').update(params.token).digest('hex');
|
|
244
257
|
const invitation = await store.findInvitationByTokenHash(tokenHash);
|
|
245
258
|
if (!invitation)
|
|
246
|
-
return response.redirect('
|
|
259
|
+
return response.redirect(accountPath('orgs'));
|
|
247
260
|
const result = await store.acceptInvitation(invitation.id, accountId);
|
|
248
261
|
if (result.ok) {
|
|
249
262
|
await cfg.audit?.record({
|
|
@@ -252,7 +265,7 @@ export default class AccountOrgsController {
|
|
|
252
265
|
metadata: { orgId: invitation.organizationId, invitationId: invitation.id },
|
|
253
266
|
});
|
|
254
267
|
}
|
|
255
|
-
return response.redirect('
|
|
268
|
+
return response.redirect(accountPath('orgs'));
|
|
256
269
|
}
|
|
257
270
|
/** POST /account/orgs/:id/members/:accountId/remove */
|
|
258
271
|
async removeMember(ctx) {
|
|
@@ -264,7 +277,8 @@ export default class AccountOrgsController {
|
|
|
264
277
|
if (!supportsOrganizations(store))
|
|
265
278
|
return response.forbidden();
|
|
266
279
|
const actorMembership = await store.getOrgMembership(params.id, actorId);
|
|
267
|
-
if (!actorMembership ||
|
|
280
|
+
if (!actorMembership ||
|
|
281
|
+
(actorMembership.role !== 'owner' && actorMembership.role !== 'admin')) {
|
|
268
282
|
return response.forbidden();
|
|
269
283
|
}
|
|
270
284
|
const result = await store.removeOrgMember(params.id, params.accountId);
|
|
@@ -275,7 +289,7 @@ export default class AccountOrgsController {
|
|
|
275
289
|
metadata: { orgId: params.id, targetAccountId: params.accountId },
|
|
276
290
|
});
|
|
277
291
|
}
|
|
278
|
-
return response.redirect('
|
|
292
|
+
return response.redirect(accountPath('orgs'));
|
|
279
293
|
}
|
|
280
294
|
/** POST /account/orgs/:id/invitations/:invId/revoke */
|
|
281
295
|
async revokeInvitation(ctx) {
|
|
@@ -287,7 +301,8 @@ export default class AccountOrgsController {
|
|
|
287
301
|
if (!supportsOrganizations(store))
|
|
288
302
|
return response.forbidden();
|
|
289
303
|
const actorMembership = await store.getOrgMembership(params.id, actorId);
|
|
290
|
-
if (!actorMembership ||
|
|
304
|
+
if (!actorMembership ||
|
|
305
|
+
(actorMembership.role !== 'owner' && actorMembership.role !== 'admin')) {
|
|
291
306
|
return response.forbidden();
|
|
292
307
|
}
|
|
293
308
|
// Escopado por org (params.id já validado acima): impede IDOR cross-org.
|
|
@@ -297,7 +312,7 @@ export default class AccountOrgsController {
|
|
|
297
312
|
actorId,
|
|
298
313
|
metadata: { orgId: params.id, invitationId: params.invId },
|
|
299
314
|
});
|
|
300
|
-
return response.redirect('
|
|
315
|
+
return response.redirect(accountPath('orgs'));
|
|
301
316
|
}
|
|
302
317
|
// ──────────────────────────────────────────────────────────────────────────
|
|
303
318
|
// JSON endpoints — consumed by @adonis-agora/authkit-react hooks.
|
|
@@ -356,7 +371,12 @@ export default class AccountOrgsController {
|
|
|
356
371
|
const members = canManage ? await store.listOrgMembers(params.id) : [];
|
|
357
372
|
const enrichedMembers = await Promise.all(members.map(async (m) => {
|
|
358
373
|
const account = await store.findById(m.accountId);
|
|
359
|
-
return {
|
|
374
|
+
return {
|
|
375
|
+
accountId: m.accountId,
|
|
376
|
+
email: account?.email ?? null,
|
|
377
|
+
role: m.role,
|
|
378
|
+
joinedAt: m.joinedAt,
|
|
379
|
+
};
|
|
360
380
|
}));
|
|
361
381
|
return {
|
|
362
382
|
id: org.id,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { HttpContext } from
|
|
1
|
+
import '../augmentations.js';
|
|
2
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
3
|
/**
|
|
4
4
|
* Self-service de segurança da conta (console de conta): trocar a senha e o
|
|
5
5
|
* e-mail. A troca de senha exige a senha ATUAL (verifyCredentials). A troca de
|