@adonis-agora/authkit-server 0.90.2 → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3637 -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 +3 -3
- package/build/index.js +3 -5
- 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/define_config.d.ts +6 -12
- package/build/src/define_config.js +3 -2
- package/build/src/doctor/checks.d.ts +3 -4
- package/build/src/doctor/checks.js +5 -7
- 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 +18 -5
- 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_validators.d.ts +15 -11
- package/build/src/host/admin_validators.js +16 -9
- 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/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/agent_consent_controller.js +23 -5
- package/build/src/host/controllers/interaction_controller.js +6 -2
- 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 +6 -0
- package/build/src/host/register_auth_host.js +32 -8
- 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 +0 -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 +9 -8
- 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
|
@@ -3,10 +3,18 @@
|
|
|
3
3
|
* seu, ex.: "Claude Code"), senão o próprio `client_id`. Client que sumiu do adapter → o id.
|
|
4
4
|
*/
|
|
5
5
|
export async function clientDisplayNames(service, clientIds) {
|
|
6
|
-
const
|
|
6
|
+
const info = await clientDisplayInfo(service, clientIds);
|
|
7
|
+
return new Map([...info].map(([id, v]) => [id, v.name]));
|
|
8
|
+
}
|
|
9
|
+
/** Nome de exibição e logo (`logo_uri` registrado, ou `null`) de cada client. */
|
|
10
|
+
export async function clientDisplayInfo(service, clientIds) {
|
|
11
|
+
const info = new Map();
|
|
7
12
|
await Promise.all([...new Set(clientIds)].map(async (clientId) => {
|
|
8
13
|
const client = await service.provider.Client.find(clientId).catch(() => null);
|
|
9
|
-
|
|
14
|
+
info.set(clientId, {
|
|
15
|
+
name: client?.clientName || clientId,
|
|
16
|
+
logoUrl: typeof client?.logoUri === 'string' && client.logoUri ? client.logoUri : null,
|
|
17
|
+
});
|
|
10
18
|
}));
|
|
11
|
-
return
|
|
19
|
+
return info;
|
|
12
20
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ConsentRequestParams } from './screen_props.js';
|
|
2
|
+
/**
|
|
3
|
+
* The interaction's request parameters for the consent screen: `client_id` and the
|
|
4
|
+
* standard string parameters are guaranteed strings (or absent); everything else is
|
|
5
|
+
* passed through untouched.
|
|
6
|
+
*/
|
|
7
|
+
export declare function consentRequestParams(raw: Record<string, unknown> | undefined): ConsentRequestParams;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Optional standard parameters of an authorization request, typed as strings. */
|
|
2
|
+
const CONSENT_STRING_PARAMS = [
|
|
3
|
+
'redirect_uri',
|
|
4
|
+
'response_type',
|
|
5
|
+
'scope',
|
|
6
|
+
'state',
|
|
7
|
+
'prompt',
|
|
8
|
+
'nonce',
|
|
9
|
+
'code_challenge',
|
|
10
|
+
'code_challenge_method',
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* The interaction's request parameters for the consent screen: `client_id` and the
|
|
14
|
+
* standard string parameters are guaranteed strings (or absent); everything else is
|
|
15
|
+
* passed through untouched.
|
|
16
|
+
*/
|
|
17
|
+
export function consentRequestParams(raw) {
|
|
18
|
+
const params = { ...raw, client_id: String(raw?.client_id ?? '') };
|
|
19
|
+
for (const key of CONSENT_STRING_PARAMS) {
|
|
20
|
+
const value = raw?.[key];
|
|
21
|
+
if (typeof value === 'string')
|
|
22
|
+
params[key] = value;
|
|
23
|
+
else
|
|
24
|
+
delete params[key];
|
|
25
|
+
}
|
|
26
|
+
return params;
|
|
27
|
+
}
|
|
@@ -4,7 +4,7 @@ import { personalAgentsFor } from '../../agents/runtime.js';
|
|
|
4
4
|
import { accountPath } from '../account_paths.js';
|
|
5
5
|
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
6
6
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
7
|
-
import {
|
|
7
|
+
import { clientDisplayInfo } from '../client_names.js';
|
|
8
8
|
/**
|
|
9
9
|
* Self-service de consentimento ("apps com acesso") no console de conta. Lista os
|
|
10
10
|
* Grants da própria conta agrupados por client (resolvendo o nome do client da
|
|
@@ -23,8 +23,8 @@ export default class AccountAppsController {
|
|
|
23
23
|
const supported = sessions.canList;
|
|
24
24
|
const grantList = supported ? await sessions.listGrants(accountId) : [];
|
|
25
25
|
// O nome que o client registrou; sem ele, o clientId.
|
|
26
|
-
const
|
|
27
|
-
const nameOf = (clientId) => clientId ? (
|
|
26
|
+
const info = await clientDisplayInfo(service, grantList.map((g) => g.clientId).filter((id) => !!id));
|
|
27
|
+
const nameOf = (clientId) => clientId ? (info.get(clientId)?.name ?? clientId) : '';
|
|
28
28
|
const revoked = ctx.session.flashMessages.get('appRevoked');
|
|
29
29
|
// Personal agents com delegação ativa (só quando a feature está ligada).
|
|
30
30
|
const agentsRuntime = await personalAgentsFor(ctx);
|
|
@@ -58,6 +58,7 @@ export default class AccountAppsController {
|
|
|
58
58
|
.map((g) => ({
|
|
59
59
|
clientId: g.clientId,
|
|
60
60
|
name: nameOf(g.clientId),
|
|
61
|
+
logoUrl: info.get(g.clientId)?.logoUrl ?? null,
|
|
61
62
|
accessTokens: g.accessTokens,
|
|
62
63
|
refreshTokens: g.refreshTokens,
|
|
63
64
|
})),
|
|
@@ -9,13 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import '../augmentations.js';
|
|
11
11
|
import type { HttpContext } from '@adonisjs/core/http';
|
|
12
|
-
import { sudoContextFrom } from '../sudo/runtime.js';
|
|
13
|
-
/**
|
|
14
|
-
* Reexport de compatibilidade. O construtor canônico do `SudoContext` vive em
|
|
15
|
-
* `sudo/runtime.ts` (é runtime do SPI, não detalhe da tela); este caminho
|
|
16
|
-
* antigo segue valendo para quem já o importava.
|
|
17
|
-
*/
|
|
18
|
-
export { sudoContextFrom };
|
|
19
12
|
export default class AccountConfirmController {
|
|
20
13
|
show(ctx: HttpContext): Promise<unknown>;
|
|
21
14
|
}
|
|
@@ -9,12 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import '../augmentations.js';
|
|
11
11
|
import { configuredSudoMethods, isSudoMethodMounted, LAST_METHOD_SESSION_KEY, resolveAvailableMethods, sudoContextFrom, } from '../sudo/runtime.js';
|
|
12
|
-
/**
|
|
13
|
-
* Reexport de compatibilidade. O construtor canônico do `SudoContext` vive em
|
|
14
|
-
* `sudo/runtime.ts` (é runtime do SPI, não detalhe da tela); este caminho
|
|
15
|
-
* antigo segue valendo para quem já o importava.
|
|
16
|
-
*/
|
|
17
|
-
export { sudoContextFrom };
|
|
18
12
|
export default class AccountConfirmController {
|
|
19
13
|
async show(ctx) {
|
|
20
14
|
const c = await sudoContextFrom(ctx);
|
|
@@ -27,54 +27,4 @@ export default class AccountOrgsController {
|
|
|
27
27
|
removeMember(ctx: HttpContext): Promise<void>;
|
|
28
28
|
/** POST /account/orgs/:id/invitations/:invId/revoke */
|
|
29
29
|
revokeInvitation(ctx: HttpContext): Promise<void>;
|
|
30
|
-
/**
|
|
31
|
-
* GET /account/orgs/json — lista as orgs do usuário logado com papel + se ativa.
|
|
32
|
-
* Usado pelo hook `useOrganizations()`.
|
|
33
|
-
*/
|
|
34
|
-
listJson(ctx: HttpContext): Promise<void | {
|
|
35
|
-
supported: boolean;
|
|
36
|
-
activeOrgId: any;
|
|
37
|
-
orgs: {
|
|
38
|
-
id: string;
|
|
39
|
-
name: string;
|
|
40
|
-
slug: string;
|
|
41
|
-
logoUrl: string | null;
|
|
42
|
-
role: string;
|
|
43
|
-
isActive: boolean;
|
|
44
|
-
}[];
|
|
45
|
-
}>;
|
|
46
|
-
/**
|
|
47
|
-
* GET /account/orgs/:id/json — detalhes da org ativa (para uso pelo hook `useOrganization()`).
|
|
48
|
-
* Só retorna se o usuário é membro.
|
|
49
|
-
*/
|
|
50
|
-
showJson(ctx: HttpContext): Promise<void | {
|
|
51
|
-
id: string;
|
|
52
|
-
name: string;
|
|
53
|
-
slug: string;
|
|
54
|
-
logoUrl: string | null;
|
|
55
|
-
role: string;
|
|
56
|
-
canManage: boolean;
|
|
57
|
-
members: {
|
|
58
|
-
accountId: string;
|
|
59
|
-
email: string | null;
|
|
60
|
-
role: string;
|
|
61
|
-
joinedAt: string;
|
|
62
|
-
}[];
|
|
63
|
-
}>;
|
|
64
|
-
/**
|
|
65
|
-
* GET /account/orgs/invitations/json — lista convites pendentes pro e-mail do usuário logado.
|
|
66
|
-
* Usado pelo hook `useOrgInvitations()`.
|
|
67
|
-
*/
|
|
68
|
-
listInvitationsJson(ctx: HttpContext): Promise<void | {
|
|
69
|
-
invitations: {
|
|
70
|
-
id: string;
|
|
71
|
-
organizationId: string;
|
|
72
|
-
orgName: string;
|
|
73
|
-
orgSlug: string;
|
|
74
|
-
email: string;
|
|
75
|
-
role: string;
|
|
76
|
-
expiresAt: string;
|
|
77
|
-
createdAt: string;
|
|
78
|
-
}[];
|
|
79
|
-
}>;
|
|
80
30
|
}
|
|
@@ -27,12 +27,10 @@ export default class AccountOrgsController {
|
|
|
27
27
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
28
28
|
const cfg = service.config;
|
|
29
29
|
const store = cfg.accountStore;
|
|
30
|
-
const messages = cfg.messages;
|
|
31
30
|
if (!supportsOrganizations(store)) {
|
|
32
31
|
return cfg.render
|
|
33
32
|
? cfg.render(ctx, 'account/orgs', {
|
|
34
33
|
supported: false,
|
|
35
|
-
messages,
|
|
36
34
|
csrfToken: ctx.request.csrfToken,
|
|
37
35
|
})
|
|
38
36
|
: response.notFound();
|
|
@@ -64,10 +62,11 @@ export default class AccountOrgsController {
|
|
|
64
62
|
pendingInvitations: invitationsWithOrg,
|
|
65
63
|
allowSelfCreate: policy.allowSelfCreate,
|
|
66
64
|
availableRoles: policy.roles,
|
|
67
|
-
messages,
|
|
68
65
|
csrfToken: ctx.request.csrfToken,
|
|
69
66
|
};
|
|
70
|
-
return cfg.render
|
|
67
|
+
return cfg.render
|
|
68
|
+
? cfg.render(ctx, 'account/orgs', props)
|
|
69
|
+
: response.notFound();
|
|
71
70
|
}
|
|
72
71
|
/** POST /account/orgs — criar nova org (requer allowSelfCreate). */
|
|
73
72
|
async store(ctx) {
|
|
@@ -265,11 +264,13 @@ export default class AccountOrgsController {
|
|
|
265
264
|
const props = {
|
|
266
265
|
invitation,
|
|
267
266
|
token: params.token,
|
|
268
|
-
messages: cfg.messages,
|
|
269
267
|
csrfToken: ctx.request.csrfToken,
|
|
270
268
|
};
|
|
271
269
|
return cfg.render
|
|
272
|
-
? cfg.render(ctx, 'account/orgs', {
|
|
270
|
+
? cfg.render(ctx, 'account/orgs', {
|
|
271
|
+
...props,
|
|
272
|
+
subview: 'accept-invitation',
|
|
273
|
+
})
|
|
273
274
|
: response.notFound();
|
|
274
275
|
}
|
|
275
276
|
/** POST /account/orgs/invitations/:token/accept — processa aceite. */
|
|
@@ -351,111 +352,4 @@ export default class AccountOrgsController {
|
|
|
351
352
|
});
|
|
352
353
|
return response.redirect(accountPath('orgs'));
|
|
353
354
|
}
|
|
354
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
355
|
-
// JSON endpoints — consumed by @adonis-agora/authkit-react hooks.
|
|
356
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
357
|
-
/**
|
|
358
|
-
* GET /account/orgs/json — lista as orgs do usuário logado com papel + se ativa.
|
|
359
|
-
* Usado pelo hook `useOrganizations()`.
|
|
360
|
-
*/
|
|
361
|
-
async listJson(ctx) {
|
|
362
|
-
const { session, response, request } = ctx;
|
|
363
|
-
const service = await ctx.containerResolver.make('authkit.server');
|
|
364
|
-
const cfg = service.config;
|
|
365
|
-
const store = cfg.accountStore;
|
|
366
|
-
if (!supportsOrganizations(store))
|
|
367
|
-
return { supported: false, orgs: [], activeOrgId: null };
|
|
368
|
-
const accountId = session.get(ACCOUNT_SESSION_KEY);
|
|
369
|
-
if (!accountId)
|
|
370
|
-
return response.unauthorized({ message: 'Not authenticated.' });
|
|
371
|
-
const orgs = await store.listOrgsForAccount(accountId);
|
|
372
|
-
const activeOrgRaw = request.cookie(ACTIVE_ORG_COOKIE);
|
|
373
|
-
const activeOrgId = activeOrgRaw ? activeOrgRaw.split('\t')[0] : null;
|
|
374
|
-
return {
|
|
375
|
-
supported: true,
|
|
376
|
-
activeOrgId,
|
|
377
|
-
orgs: orgs.map((org) => ({
|
|
378
|
-
id: org.id,
|
|
379
|
-
name: org.name,
|
|
380
|
-
slug: org.slug,
|
|
381
|
-
logoUrl: org.logoUrl ?? null,
|
|
382
|
-
role: org.role,
|
|
383
|
-
isActive: org.id === activeOrgId,
|
|
384
|
-
})),
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* GET /account/orgs/:id/json — detalhes da org ativa (para uso pelo hook `useOrganization()`).
|
|
389
|
-
* Só retorna se o usuário é membro.
|
|
390
|
-
*/
|
|
391
|
-
async showJson(ctx) {
|
|
392
|
-
const { session, response, params } = ctx;
|
|
393
|
-
const service = await ctx.containerResolver.make('authkit.server');
|
|
394
|
-
const cfg = service.config;
|
|
395
|
-
const store = cfg.accountStore;
|
|
396
|
-
if (!supportsOrganizations(store))
|
|
397
|
-
return response.notFound({ message: 'Not supported.' });
|
|
398
|
-
const accountId = session.get(ACCOUNT_SESSION_KEY);
|
|
399
|
-
if (!accountId)
|
|
400
|
-
return response.unauthorized({ message: 'Not authenticated.' });
|
|
401
|
-
const membership = await store.getOrgMembership(params.id, accountId);
|
|
402
|
-
if (!membership)
|
|
403
|
-
return response.notFound({ message: 'Organization not found or not a member.' });
|
|
404
|
-
const org = await store.findOrgById(params.id);
|
|
405
|
-
if (!org)
|
|
406
|
-
return response.notFound({ message: 'Organization not found.' });
|
|
407
|
-
const canManage = membership.role === 'owner' || membership.role === 'admin';
|
|
408
|
-
const members = canManage ? await store.listOrgMembers(params.id) : [];
|
|
409
|
-
const enrichedMembers = await Promise.all(members.map(async (m) => {
|
|
410
|
-
const account = await store.findById(m.accountId);
|
|
411
|
-
return {
|
|
412
|
-
accountId: m.accountId,
|
|
413
|
-
email: account?.email ?? null,
|
|
414
|
-
role: m.role,
|
|
415
|
-
joinedAt: m.joinedAt,
|
|
416
|
-
};
|
|
417
|
-
}));
|
|
418
|
-
return {
|
|
419
|
-
id: org.id,
|
|
420
|
-
name: org.name,
|
|
421
|
-
slug: org.slug,
|
|
422
|
-
logoUrl: org.logoUrl ?? null,
|
|
423
|
-
role: membership.role,
|
|
424
|
-
canManage,
|
|
425
|
-
members: enrichedMembers,
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
/**
|
|
429
|
-
* GET /account/orgs/invitations/json — lista convites pendentes pro e-mail do usuário logado.
|
|
430
|
-
* Usado pelo hook `useOrgInvitations()`.
|
|
431
|
-
*/
|
|
432
|
-
async listInvitationsJson(ctx) {
|
|
433
|
-
const { session, response } = ctx;
|
|
434
|
-
const service = await ctx.containerResolver.make('authkit.server');
|
|
435
|
-
const cfg = service.config;
|
|
436
|
-
const store = cfg.accountStore;
|
|
437
|
-
if (!supportsOrganizations(store))
|
|
438
|
-
return { invitations: [] };
|
|
439
|
-
const accountId = session.get(ACCOUNT_SESSION_KEY);
|
|
440
|
-
if (!accountId)
|
|
441
|
-
return response.unauthorized({ message: 'Not authenticated.' });
|
|
442
|
-
const account = await store.findById(accountId);
|
|
443
|
-
if (!account)
|
|
444
|
-
return response.unauthorized({ message: 'Not authenticated.' });
|
|
445
|
-
const invitations = await store.listPendingInvitationsForEmail(account.email);
|
|
446
|
-
const enriched = await Promise.all(invitations.map(async (inv) => {
|
|
447
|
-
const org = await store.findOrgById(inv.organizationId);
|
|
448
|
-
return {
|
|
449
|
-
id: inv.id,
|
|
450
|
-
organizationId: inv.organizationId,
|
|
451
|
-
orgName: org?.name ?? inv.organizationId,
|
|
452
|
-
orgSlug: org?.slug ?? inv.organizationId,
|
|
453
|
-
email: inv.email,
|
|
454
|
-
role: inv.role,
|
|
455
|
-
expiresAt: inv.expiresAt,
|
|
456
|
-
createdAt: inv.createdAt,
|
|
457
|
-
};
|
|
458
|
-
}));
|
|
459
|
-
return { invitations: enriched };
|
|
460
|
-
}
|
|
461
355
|
}
|
|
@@ -426,7 +426,7 @@ export default class AccountSecurityController {
|
|
|
426
426
|
});
|
|
427
427
|
const origin = authkitOrigin(cfg);
|
|
428
428
|
const confirmUrl = `${origin}${accountPath('emailConfirm')}?token=${encodeURIComponent(issued.token)}`;
|
|
429
|
-
// 1. Link de confirmação → NOVO e-mail (hook onEmailChangeConfirm >
|
|
429
|
+
// 1. Link de confirmação → NOVO e-mail (hook onEmailChangeConfirm > default).
|
|
430
430
|
if (cfg.mail?.onEmailChangeConfirm) {
|
|
431
431
|
await cfg.mail.onEmailChangeConfirm({
|
|
432
432
|
email: newEmail,
|
|
@@ -435,14 +435,6 @@ export default class AccountSecurityController {
|
|
|
435
435
|
oldEmail: account?.email ?? '',
|
|
436
436
|
});
|
|
437
437
|
}
|
|
438
|
-
else if (cfg.mail?.onEmailVerification) {
|
|
439
|
-
// Retrocompat: hook legado onEmailVerification (ainda funciona como override).
|
|
440
|
-
await cfg.mail.onEmailVerification({
|
|
441
|
-
email: newEmail,
|
|
442
|
-
verifyUrl: confirmUrl,
|
|
443
|
-
token: issued.token,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
438
|
else {
|
|
447
439
|
await sendEmailChangeConfirmationEmail(ctx, {
|
|
448
440
|
email: newEmail,
|
|
@@ -513,7 +505,9 @@ export default class AccountSecurityController {
|
|
|
513
505
|
const store = cfg.accountStore;
|
|
514
506
|
const render = cfg.render;
|
|
515
507
|
if (!supportsAccountSecurity(store)) {
|
|
516
|
-
return render(ctx, 'account/email-confirmed', {
|
|
508
|
+
return render(ctx, 'account/email-confirmed', {
|
|
509
|
+
ok: false,
|
|
510
|
+
});
|
|
517
511
|
}
|
|
518
512
|
const token = ctx.request.qs().token ?? '';
|
|
519
513
|
const result = await store.confirmEmailChange(token);
|
|
@@ -551,6 +545,8 @@ export default class AccountSecurityController {
|
|
|
551
545
|
metadata: { oldEmail: result.oldEmail, newEmail: result.newEmail },
|
|
552
546
|
}, cfg.mail, cfg.audit, cfg);
|
|
553
547
|
}
|
|
554
|
-
return render(ctx, 'account/email-confirmed', {
|
|
548
|
+
return render(ctx, 'account/email-confirmed', {
|
|
549
|
+
ok: result.ok,
|
|
550
|
+
});
|
|
555
551
|
}
|
|
556
552
|
}
|
|
@@ -51,14 +51,20 @@ export default class AgentConsentController {
|
|
|
51
51
|
error: null,
|
|
52
52
|
};
|
|
53
53
|
if (impersonationState(ctx).active) {
|
|
54
|
-
return render(ctx, 'agents/consent', {
|
|
54
|
+
return render(ctx, 'agents/consent', {
|
|
55
|
+
...base,
|
|
56
|
+
error: 'agents.consent.impersonating',
|
|
57
|
+
});
|
|
55
58
|
}
|
|
56
59
|
const input = ctx.request.input('user_code');
|
|
57
60
|
if (!input)
|
|
58
61
|
return render(ctx, 'agents/consent', base);
|
|
59
62
|
const pending = await runtime.delegation.pendingRequest(input);
|
|
60
63
|
if (!pending) {
|
|
61
|
-
return render(ctx, 'agents/consent', {
|
|
64
|
+
return render(ctx, 'agents/consent', {
|
|
65
|
+
...base,
|
|
66
|
+
error: 'agents.consent.invalid_code',
|
|
67
|
+
});
|
|
62
68
|
}
|
|
63
69
|
const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
64
70
|
const account = await service.config.accountStore.findById(accountId);
|
|
@@ -96,7 +102,11 @@ export default class AgentConsentController {
|
|
|
96
102
|
const userCode = typeof rawCode === 'string' ? rawCode : '';
|
|
97
103
|
const pending = await runtime.delegation.pendingRequest(userCode);
|
|
98
104
|
if (!pending) {
|
|
99
|
-
return render(ctx, 'agents/done', {
|
|
105
|
+
return render(ctx, 'agents/done', {
|
|
106
|
+
status: 'expired',
|
|
107
|
+
agentName: null,
|
|
108
|
+
scopes: [],
|
|
109
|
+
});
|
|
100
110
|
}
|
|
101
111
|
const name = await agentName(runtime, pending.clientId);
|
|
102
112
|
const raw = ctx.request.input('scope');
|
|
@@ -108,7 +118,11 @@ export default class AgentConsentController {
|
|
|
108
118
|
// Negou ou desmarcou tudo. Se nem negar deu, o pedido mudou de estado no
|
|
109
119
|
// meio do caminho (aprovado em outra aba, expirou).
|
|
110
120
|
if (!(await runtime.delegation.deny({ userCode, accountId }))) {
|
|
111
|
-
return render(ctx, 'agents/done', {
|
|
121
|
+
return render(ctx, 'agents/done', {
|
|
122
|
+
status: 'expired',
|
|
123
|
+
agentName: name,
|
|
124
|
+
scopes: [],
|
|
125
|
+
});
|
|
112
126
|
}
|
|
113
127
|
await cfg.audit?.record({
|
|
114
128
|
type: 'agent.delegation_denied',
|
|
@@ -117,7 +131,11 @@ export default class AgentConsentController {
|
|
|
117
131
|
ip: ctx.request.ip?.() ?? null,
|
|
118
132
|
metadata: { requested: pending.scopes.map((s) => s.id) },
|
|
119
133
|
});
|
|
120
|
-
return render(ctx, 'agents/done', {
|
|
134
|
+
return render(ctx, 'agents/done', {
|
|
135
|
+
status: 'denied',
|
|
136
|
+
agentName: name,
|
|
137
|
+
scopes: [],
|
|
138
|
+
});
|
|
121
139
|
}
|
|
122
140
|
await cfg.audit?.record({
|
|
123
141
|
type: 'agent.delegation_approved',
|
|
@@ -4,6 +4,7 @@ import { resolveAccountRoles } from '../account_roles.js';
|
|
|
4
4
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
5
5
|
import { guardBotProtection, resolveEffectiveBotProtection } from '../bot_protection.js';
|
|
6
6
|
import { brandFor, isFirstParty } from '../branding.js';
|
|
7
|
+
import { consentRequestParams } from '../consent_params.js';
|
|
7
8
|
import { isCustomLoginMethodId } from '../custom_login.js';
|
|
8
9
|
import { completeCustomLoginSession } from '../custom_login_completion.js';
|
|
9
10
|
import { beginCustomMfa, clearMfaFlow, customMfaViewProps, getMfaFlow, initializeMfaFlow, recordVerifiedMfaFactor, resolveCustomMfaMethods, verifyCustomMfa, } from '../custom_mfa.js';
|
|
@@ -264,7 +265,7 @@ export default class AuthInteractionController {
|
|
|
264
265
|
// Consent or other prompts — unchanged
|
|
265
266
|
return render(ctx, 'consent', {
|
|
266
267
|
uid: details.uid,
|
|
267
|
-
params: details.params,
|
|
268
|
+
params: consentRequestParams(details.params),
|
|
268
269
|
csrfToken: ctx.request.csrfToken,
|
|
269
270
|
brand,
|
|
270
271
|
...(await consentSubject(service, details, cfg.messages)),
|
|
@@ -1950,7 +1951,10 @@ export default class AuthInteractionController {
|
|
|
1950
1951
|
ip,
|
|
1951
1952
|
metadata: { reason: 'token_expired' },
|
|
1952
1953
|
});
|
|
1953
|
-
return render(ctx, 'otp-unlock', {
|
|
1954
|
+
return render(ctx, 'otp-unlock', {
|
|
1955
|
+
ok: false,
|
|
1956
|
+
expired: true,
|
|
1957
|
+
});
|
|
1954
1958
|
}
|
|
1955
1959
|
// Token válido: zera o lock OTP + limpa o token do DB.
|
|
1956
1960
|
const runtimeForOtp = await resolveRuntimeSettingsOrNoop(ctx);
|
|
@@ -44,7 +44,11 @@ export default class PoppyConsentController {
|
|
|
44
44
|
return redirectExact(ctx.response, outcome.url);
|
|
45
45
|
if (outcome.kind === 'error') {
|
|
46
46
|
ctx.response.status(400);
|
|
47
|
-
return render(ctx, 'agents/done', {
|
|
47
|
+
return render(ctx, 'agents/done', {
|
|
48
|
+
status: 'invalid',
|
|
49
|
+
agentName: null,
|
|
50
|
+
scopes: [],
|
|
51
|
+
});
|
|
48
52
|
}
|
|
49
53
|
const account = await service.config.accountStore.findById(accountId);
|
|
50
54
|
return render(ctx, 'agents/consent', {
|
|
@@ -72,7 +76,11 @@ export default class PoppyConsentController {
|
|
|
72
76
|
const cfg = service.config;
|
|
73
77
|
const render = cfg.render;
|
|
74
78
|
if (impersonationState(ctx).active) {
|
|
75
|
-
return render(ctx, 'agents/done', {
|
|
79
|
+
return render(ctx, 'agents/done', {
|
|
80
|
+
status: 'expired',
|
|
81
|
+
agentName: null,
|
|
82
|
+
scopes: [],
|
|
83
|
+
});
|
|
76
84
|
}
|
|
77
85
|
const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
78
86
|
const decided = await poppy.decideAuthorization({
|
|
@@ -82,7 +90,11 @@ export default class PoppyConsentController {
|
|
|
82
90
|
scopes: chosenScopes(ctx),
|
|
83
91
|
});
|
|
84
92
|
if (!decided) {
|
|
85
|
-
return render(ctx, 'agents/done', {
|
|
93
|
+
return render(ctx, 'agents/done', {
|
|
94
|
+
status: 'expired',
|
|
95
|
+
agentName: null,
|
|
96
|
+
scopes: [],
|
|
97
|
+
});
|
|
86
98
|
}
|
|
87
99
|
await cfg.audit?.record({
|
|
88
100
|
type: decided.approved ? 'agent.delegation_approved' : 'agent.delegation_denied',
|
|
@@ -110,14 +122,20 @@ export default class PoppyConsentController {
|
|
|
110
122
|
error: null,
|
|
111
123
|
};
|
|
112
124
|
if (impersonationState(ctx).active) {
|
|
113
|
-
return render(ctx, 'agents/consent', {
|
|
125
|
+
return render(ctx, 'agents/consent', {
|
|
126
|
+
...base,
|
|
127
|
+
error: 'agents.consent.impersonating',
|
|
128
|
+
});
|
|
114
129
|
}
|
|
115
130
|
const input = ctx.request.input('user_code');
|
|
116
131
|
if (!input)
|
|
117
132
|
return render(ctx, 'agents/consent', base);
|
|
118
133
|
const pending = await poppy.pendingDevice(input);
|
|
119
134
|
if (!pending) {
|
|
120
|
-
return render(ctx, 'agents/consent', {
|
|
135
|
+
return render(ctx, 'agents/consent', {
|
|
136
|
+
...base,
|
|
137
|
+
error: 'agents.consent.invalid_code',
|
|
138
|
+
});
|
|
121
139
|
}
|
|
122
140
|
const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
123
141
|
const account = await service.config.accountStore.findById(accountId);
|
|
@@ -143,7 +161,11 @@ export default class PoppyConsentController {
|
|
|
143
161
|
const cfg = service.config;
|
|
144
162
|
const render = cfg.render;
|
|
145
163
|
if (impersonationState(ctx).active) {
|
|
146
|
-
return render(ctx, 'agents/done', {
|
|
164
|
+
return render(ctx, 'agents/done', {
|
|
165
|
+
status: 'expired',
|
|
166
|
+
agentName: null,
|
|
167
|
+
scopes: [],
|
|
168
|
+
});
|
|
147
169
|
}
|
|
148
170
|
const accountId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
149
171
|
const userCode = String(ctx.request.input('user_code') ?? '');
|
|
@@ -155,7 +177,11 @@ export default class PoppyConsentController {
|
|
|
155
177
|
scopes: chosenScopes(ctx),
|
|
156
178
|
});
|
|
157
179
|
if (!decided || !pending) {
|
|
158
|
-
return render(ctx, 'agents/done', {
|
|
180
|
+
return render(ctx, 'agents/done', {
|
|
181
|
+
status: 'expired',
|
|
182
|
+
agentName: null,
|
|
183
|
+
scopes: [],
|
|
184
|
+
});
|
|
159
185
|
}
|
|
160
186
|
await cfg.audit?.record({
|
|
161
187
|
type: decided.approved ? 'agent.delegation_approved' : 'agent.delegation_denied',
|
|
@@ -450,7 +450,10 @@ export default class AuthRegistrationController {
|
|
|
450
450
|
const cfg = service.config;
|
|
451
451
|
const render = cfg.render;
|
|
452
452
|
const token = ctx.request.qs().token ?? '';
|
|
453
|
-
return render(ctx, 'reset', {
|
|
453
|
+
return render(ctx, 'reset', {
|
|
454
|
+
token,
|
|
455
|
+
csrfToken: ctx.request.csrfToken,
|
|
456
|
+
});
|
|
454
457
|
}
|
|
455
458
|
/** POST /auth/reset-password — redefine a senha. */
|
|
456
459
|
async reset(ctx) {
|
|
@@ -552,6 +555,8 @@ export default class AuthRegistrationController {
|
|
|
552
555
|
ip: ctx.request.ip?.() ?? null,
|
|
553
556
|
});
|
|
554
557
|
}
|
|
555
|
-
return render(ctx, 'verify-email', {
|
|
558
|
+
return render(ctx, 'verify-email', {
|
|
559
|
+
verified: ok,
|
|
560
|
+
});
|
|
556
561
|
}
|
|
557
562
|
}
|
package/build/src/host/i18n.d.ts
CHANGED
|
@@ -346,7 +346,6 @@ export declare const DEFAULT_MESSAGES: {
|
|
|
346
346
|
'admin.clients.grants': string;
|
|
347
347
|
'admin.clients.redirect_uris': string;
|
|
348
348
|
'admin.clients.dynamic_notice': string;
|
|
349
|
-
'admin.clients.static_section': string;
|
|
350
349
|
'admin.clients.dynamic_section': string;
|
|
351
350
|
'admin.clients.dynamic_empty': string;
|
|
352
351
|
'admin.clients.dynamic_not_supported': string;
|
|
@@ -377,7 +376,6 @@ export declare const DEFAULT_MESSAGES: {
|
|
|
377
376
|
'admin.clients.field_backchannel_uri_help': string;
|
|
378
377
|
'admin.clients.field_backchannel_session_required': string;
|
|
379
378
|
'admin.clients.field_backchannel_session_required_help': string;
|
|
380
|
-
'admin.clients.static_deprecated_notice': string;
|
|
381
379
|
'admin.orgs.page_title': string;
|
|
382
380
|
'admin.orgs.title': string;
|
|
383
381
|
'admin.orgs.detail_title': string;
|
|
@@ -1141,7 +1139,6 @@ export declare const PT_BR_MESSAGES: {
|
|
|
1141
1139
|
'admin.clients.grants': string;
|
|
1142
1140
|
'admin.clients.redirect_uris': string;
|
|
1143
1141
|
'admin.clients.dynamic_notice': string;
|
|
1144
|
-
'admin.clients.static_section': string;
|
|
1145
1142
|
'admin.clients.dynamic_section': string;
|
|
1146
1143
|
'admin.clients.dynamic_empty': string;
|
|
1147
1144
|
'admin.clients.dynamic_not_supported': string;
|
|
@@ -1172,7 +1169,6 @@ export declare const PT_BR_MESSAGES: {
|
|
|
1172
1169
|
'admin.clients.field_backchannel_uri_help': string;
|
|
1173
1170
|
'admin.clients.field_backchannel_session_required': string;
|
|
1174
1171
|
'admin.clients.field_backchannel_session_required_help': string;
|
|
1175
|
-
'admin.clients.static_deprecated_notice': string;
|
|
1176
1172
|
'admin.audit.page_title': string;
|
|
1177
1173
|
'admin.audit.title': string;
|
|
1178
1174
|
'admin.audit.type_placeholder': string;
|
package/build/src/host/i18n.js
CHANGED
|
@@ -367,7 +367,6 @@ export const DEFAULT_MESSAGES = {
|
|
|
367
367
|
'admin.clients.grants': 'Grants: {grants}',
|
|
368
368
|
'admin.clients.redirect_uris': 'Redirects: {uris}',
|
|
369
369
|
'admin.clients.dynamic_notice': 'Dynamic client registration (RFC 7591) is on — clients registered via /reg are persisted in the adapter and appear in the dynamic section below.',
|
|
370
|
-
'admin.clients.static_section': 'Static clients (config)',
|
|
371
370
|
'admin.clients.dynamic_section': 'Dynamic clients (adapter)',
|
|
372
371
|
'admin.clients.dynamic_empty': 'No dynamic clients persisted.',
|
|
373
372
|
'admin.clients.dynamic_not_supported': 'The configured OIDC adapter does not support client enumeration — dynamic management is unavailable.',
|
|
@@ -398,8 +397,6 @@ export const DEFAULT_MESSAGES = {
|
|
|
398
397
|
'admin.clients.field_backchannel_uri_help': 'OIDC Back-Channel Logout endpoint of the RP. Leave blank if not needed.',
|
|
399
398
|
'admin.clients.field_backchannel_session_required': 'Require sid in logout_token',
|
|
400
399
|
'admin.clients.field_backchannel_session_required_help': 'When checked, the IdP includes the session ID (sid) in every logout_token sent to this client.',
|
|
401
|
-
'admin.clients.static_deprecated_notice': 'These clients are defined statically in config and are deprecated. ' +
|
|
402
|
-
'Migrate them to the adapter/DB with:',
|
|
403
400
|
// Console admin — organizations.
|
|
404
401
|
'admin.orgs.page_title': 'Organizations',
|
|
405
402
|
'admin.orgs.title': 'Organizations',
|
|
@@ -1250,7 +1247,6 @@ export const PT_BR_MESSAGES = {
|
|
|
1250
1247
|
'admin.clients.grants': 'Grants: {grants}',
|
|
1251
1248
|
'admin.clients.redirect_uris': 'Redirects: {uris}',
|
|
1252
1249
|
'admin.clients.dynamic_notice': 'O registro dinâmico de clients (RFC 7591) está ligado — clients registrados via /reg são persistidos no adapter e aparecem na seção dinâmica abaixo.',
|
|
1253
|
-
'admin.clients.static_section': 'Clients estáticos (config)',
|
|
1254
1250
|
'admin.clients.dynamic_section': 'Clients dinâmicos (adapter)',
|
|
1255
1251
|
'admin.clients.dynamic_empty': 'Nenhum client dinâmico persistido.',
|
|
1256
1252
|
'admin.clients.dynamic_not_supported': 'O adapter OIDC configurado não suporta enumeração de clients — a gestão dinâmica fica indisponível.',
|
|
@@ -1281,8 +1277,6 @@ export const PT_BR_MESSAGES = {
|
|
|
1281
1277
|
'admin.clients.field_backchannel_uri_help': 'Endpoint de OIDC Back-Channel Logout do RP. Deixe em branco se não usar.',
|
|
1282
1278
|
'admin.clients.field_backchannel_session_required': 'Exigir sid no logout_token',
|
|
1283
1279
|
'admin.clients.field_backchannel_session_required_help': 'Quando marcado, o IdP inclui o session ID (sid) em cada logout_token enviado a este client.',
|
|
1284
|
-
'admin.clients.static_deprecated_notice': 'Estes clients estão definidos estaticamente no config e estão depreciados. ' +
|
|
1285
|
-
'Migre-os para o adapter/DB com:',
|
|
1286
1280
|
// Console admin — auditoria.
|
|
1287
1281
|
'admin.audit.page_title': 'Auditoria',
|
|
1288
1282
|
'admin.audit.title': 'Log de auditoria',
|