@adonis-agora/authkit-server 1.0.0-rc.16 → 1.0.0-rc.17
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 +35 -0
- package/build/index.d.ts +3 -3
- package/build/index.js +2 -1
- package/build/src/audit/audit_sink.d.ts +26 -19
- package/build/src/audit/lucid_audit_sink.js +99 -28
- package/build/src/commands/expire_scan_command.js +0 -2
- package/build/src/dev_tools/tools.js +10 -12
- package/build/src/host/account_export_service.js +0 -2
- package/build/src/host/admin_api/api_clients_controller.d.ts +6 -1
- package/build/src/host/admin_api/api_clients_controller.js +9 -5
- package/build/src/host/admin_api/api_misc_controller.d.ts +17 -7
- package/build/src/host/admin_api/api_misc_controller.js +10 -8
- package/build/src/host/admin_api/api_orgs_controller.d.ts +5 -1
- package/build/src/host/admin_api/api_orgs_controller.js +10 -8
- package/build/src/host/admin_api/api_users_controller.d.ts +15 -8
- package/build/src/host/admin_api/api_users_controller.js +11 -9
- package/build/src/host/admin_console/console_audit_controller.d.ts +14 -7
- package/build/src/host/admin_console/console_audit_controller.js +7 -8
- package/build/src/host/admin_console/console_clients_controller.d.ts +2 -1
- package/build/src/host/admin_console/console_clients_controller.js +5 -5
- package/build/src/host/admin_console/console_orgs_controller.d.ts +2 -1
- package/build/src/host/admin_console/console_orgs_controller.js +6 -2
- package/build/src/host/admin_console/console_overview_controller.d.ts +1 -1
- package/build/src/host/admin_console/console_overview_controller.js +4 -6
- package/build/src/host/admin_console/console_personal_agents_controller.d.ts +17 -20
- package/build/src/host/admin_console/console_personal_agents_controller.js +29 -8
- package/build/src/host/admin_console/console_sessions_controller.d.ts +11 -10
- package/build/src/host/admin_console/console_sessions_controller.js +41 -18
- package/build/src/host/admin_console/console_users_controller.d.ts +12 -8
- package/build/src/host/admin_console/console_users_controller.js +7 -12
- package/build/src/host/admin_stats_service.js +4 -4
- package/build/src/host/list_api.d.ts +118 -0
- package/build/src/host/list_api.js +236 -0
- package/build/src/host/login_attempt.js +0 -2
- package/build/src/host/login_notify.js +1 -2
- package/build/src/host/session_context.js +1 -2
- package/build/src/host/ui-dist/assets/index-SteA4yAA.js +67 -0
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/pagination.d.ts +5 -31
- package/build/src/pagination.js +5 -12
- package/package.json +3 -2
- package/build/src/host/ui-dist/assets/index-Dq3dJVBv.js +0 -67
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
-
import { ADMIN_LIST_HTTP_DEFAULT_SIZE, parseListPage, parseListSize } from '../../pagination.js';
|
|
3
2
|
import { rolesManagedExternally } from '../account_roles.js';
|
|
4
3
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
5
4
|
import { adminUserCreateValidator, adminUserUpdateValidator } from '../admin_validators.js';
|
|
5
|
+
import { listAccountsPage, withListQuery } from '../list_api.js';
|
|
6
6
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
7
7
|
import { enrichSessionsWithContext } from '../session_context.js';
|
|
8
8
|
import { AdminUsersService } from './admin_users_service.js';
|
|
@@ -28,16 +28,18 @@ async function ctxBits(ctx) {
|
|
|
28
28
|
* envelope `{ error: { code, message } }`. Toda escrita audita com `actor: 'admin-api'`.
|
|
29
29
|
*/
|
|
30
30
|
export default class ApiUsersController {
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* GET /users — `?search=&sort=email&page=&size=` →
|
|
33
|
+
* `{ data, meta: { total, perPage, currentPage, lastPage, firstPage } }`.
|
|
34
|
+
*/
|
|
32
35
|
async index(ctx) {
|
|
33
36
|
const { cfg } = await ctxBits(ctx);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return { meta: { page, size, total: result.total }, data };
|
|
37
|
+
return withListQuery(ctx, async () => {
|
|
38
|
+
const result = await listAccountsPage(cfg.accountStore, ctx.request.qs());
|
|
39
|
+
const users = new AdminUsersService(cfg);
|
|
40
|
+
const data = await Promise.all(result.data.map(async (u) => userDtoWithRoles(cfg, u, await users.isDisabled(u.id))));
|
|
41
|
+
return { ...result, data };
|
|
42
|
+
});
|
|
41
43
|
}
|
|
42
44
|
/** GET /users/:id */
|
|
43
45
|
async show(ctx) {
|
|
@@ -3,17 +3,24 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
3
3
|
/**
|
|
4
4
|
* Endpoints JSON do log de auditoria do console admin React.
|
|
5
5
|
*
|
|
6
|
-
* GET {prefix}/api/audit?type=&
|
|
6
|
+
* GET {prefix}/api/audit?filter[type]=&filter[subject]=&after=&before=&first=&last=
|
|
7
|
+
* → `{ data, meta: { nextCursor, prevCursor, hasNext, hasPrev } }`, newest first
|
|
7
8
|
*
|
|
8
9
|
* 404 honesto (`capability_unsupported`) quando o sink não suporta consulta.
|
|
9
10
|
*/
|
|
10
11
|
export default class ConsoleAuditController {
|
|
11
12
|
index(ctx: HttpContext): Promise<void | {
|
|
12
|
-
meta:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
meta: import("@adonis-agora/filter").CursorPageMeta;
|
|
14
|
+
data: {
|
|
15
|
+
id: string;
|
|
16
|
+
type: "account.deleted" | "account.expiration_warned" | "account.expired_login_blocked" | "account.exported" | "account.extension.action" | "account.extension.redirect" | "account.locked" | "account.signed_out_all" | "agent.allowed" | "agent.blocked" | "agent.delegation_approved" | "agent.delegation_denied" | "agent.grant_revoked" | "agent.grant_revoked_by_admin" | "agent.registry_removed" | "bot_protection.rejected" | "client.created" | "client.deleted" | "client.disabled" | "client.enabled" | "client.secret_regenerated" | "client.updated" | "email.change_requested" | "email.changed" | "email_change.cancelled" | "email_change.confirmed" | "email_change.requested" | "email_verification.consumed" | "email_verification.issued" | "grant.revoked_by_user" | "impersonation" | "impersonation.panel_viewed" | "impersonation.stopped" | "keys.rotated" | "login.failure" | "login.magic_link_sent" | "login.new_device" | "login.new_ip_notified" | "login.otp_failed" | "login.otp_invalidated" | "login.otp_sent" | "login.otp_verified" | "login.success" | "maintenance.disabled" | "maintenance.enabled" | "mfa.disabled" | "mfa.enabled" | "mfa.recovery_codes_regenerated" | "organization.created" | "organization.deactivated" | "organization.deleted" | "organization.invitation_accepted" | "organization.invitation_revoked" | "organization.invitation_sent" | "organization.member_added" | "organization.member_removed" | "organization.member_role_changed" | "organization.member_role_updated" | "organization.switched" | "organization.updated" | "otp.locked" | "otp.unlock_failed" | "otp.unlocked" | "passkey.registered" | "passkey.removed" | "password.changed" | "password.expired_change_forced" | "password.rehashed" | "password_reset.consumed" | "password_reset.issued" | "pat.issued" | "pat.revoked" | "pat.used" | "profile.updated" | "roles_catalog.updated" | "security_notice.sent" | "service_tenant.token_issued" | "session.revoked" | "session.revoked_all" | "session.single_enforced" | "settings.updated" | "signup" | "sudo.confirmed" | "trusted_device.revoked" | "user.created" | "user.deleted" | "user.disabled" | "user.enabled" | "user.password_reset_sent";
|
|
17
|
+
accountId: string | null;
|
|
18
|
+
email: string | null;
|
|
19
|
+
clientId: string | null;
|
|
20
|
+
actorId: string | null;
|
|
21
|
+
ip: string | null;
|
|
22
|
+
metadata: Record<string, unknown> | null;
|
|
23
|
+
createdAt: string | null;
|
|
24
|
+
}[];
|
|
18
25
|
}>;
|
|
19
26
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
-
import { ADMIN_LIST_HTTP_DEFAULT_SIZE, parseListPage, parseListSize } from '../../pagination.js';
|
|
3
2
|
import { apiError, auditDto } from '../admin_api/dto.js';
|
|
3
|
+
import { listAuditPage, withListQuery } from '../list_api.js';
|
|
4
4
|
/**
|
|
5
5
|
* Endpoints JSON do log de auditoria do console admin React.
|
|
6
6
|
*
|
|
7
|
-
* GET {prefix}/api/audit?type=&
|
|
7
|
+
* GET {prefix}/api/audit?filter[type]=&filter[subject]=&after=&before=&first=&last=
|
|
8
|
+
* → `{ data, meta: { nextCursor, prevCursor, hasNext, hasPrev } }`, newest first
|
|
8
9
|
*
|
|
9
10
|
* 404 honesto (`capability_unsupported`) quando o sink não suporta consulta.
|
|
10
11
|
*/
|
|
@@ -16,11 +17,9 @@ export default class ConsoleAuditController {
|
|
|
16
17
|
if (!sink || typeof sink.list !== 'function') {
|
|
17
18
|
return ctx.response.notFound(apiError('capability_unsupported', 'O sink de auditoria configurado não suporta consulta.'));
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const result = await sink.list({ page, size, type, subject });
|
|
24
|
-
return { meta: { page, size, total: result.total }, data: result.data.map(auditDto) };
|
|
20
|
+
return withListQuery(ctx, async () => {
|
|
21
|
+
const page = await listAuditPage(sink, ctx.request.qs());
|
|
22
|
+
return { ...page, data: page.data.map(auditDto) };
|
|
23
|
+
});
|
|
25
24
|
}
|
|
26
25
|
}
|
|
@@ -15,7 +15,8 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
15
15
|
export default class ConsoleClientsController {
|
|
16
16
|
#private;
|
|
17
17
|
/** GET {prefix}/api/clients */
|
|
18
|
-
index(ctx: HttpContext): Promise<{
|
|
18
|
+
index(ctx: HttpContext): Promise<void | {
|
|
19
|
+
meta: import("@adonis-agora/filter").OffsetPageMeta;
|
|
19
20
|
data: {
|
|
20
21
|
clientId: string;
|
|
21
22
|
confidential: boolean;
|
|
@@ -3,6 +3,7 @@ import { apiError, clientDto, createdClientDto } from '../admin_api/dto.js';
|
|
|
3
3
|
import { AdminClientsService } from '../admin_clients_service.js';
|
|
4
4
|
import { clientCreateInput, clientInputValidator, clientPartialInput, } from '../admin_validators.js';
|
|
5
5
|
import { ClientMetadataError } from '../client_metadata.js';
|
|
6
|
+
import { listClientsPage, withListQuery } from '../list_api.js';
|
|
6
7
|
/**
|
|
7
8
|
* Metadata incoerente com o tipo de client (ex.: nativo com secret, redirect
|
|
8
9
|
* `myapp://` num client web) — 422, igual a um input que falha no Vine.
|
|
@@ -27,11 +28,10 @@ export default class ConsoleClientsController {
|
|
|
27
28
|
async index(ctx) {
|
|
28
29
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
29
30
|
const svc = new AdminClientsService(service);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return { data: clients.map(clientDto), canList: true };
|
|
31
|
+
return withListQuery(ctx, async () => {
|
|
32
|
+
const page = await listClientsPage(svc.canList ? await svc.list() : [], ctx.request.qs());
|
|
33
|
+
return { ...page, data: page.data.map(clientDto), canList: svc.canList };
|
|
34
|
+
});
|
|
35
35
|
}
|
|
36
36
|
/** GET {prefix}/api/clients/:id */
|
|
37
37
|
async show(ctx) {
|
|
@@ -4,7 +4,7 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
4
4
|
* Endpoints JSON de organizações do console admin React.
|
|
5
5
|
* 404 honesto quando o store não suporta organizações (`capability_unsupported`).
|
|
6
6
|
*
|
|
7
|
-
* GET {prefix}/api/orgs
|
|
7
|
+
* GET {prefix}/api/orgs?search=&sort=&page=&size= → página de orgs com contagem de membros
|
|
8
8
|
* POST {prefix}/api/orgs → cria uma org
|
|
9
9
|
* GET {prefix}/api/orgs/:id → detalhe da org (membros + convites)
|
|
10
10
|
* PATCH {prefix}/api/orgs/:id → edita nome/slug/logo
|
|
@@ -22,6 +22,7 @@ export default class ConsoleOrgsController {
|
|
|
22
22
|
private settings;
|
|
23
23
|
/** GET {prefix}/api/orgs */
|
|
24
24
|
index(ctx: HttpContext): Promise<void | {
|
|
25
|
+
meta: import("@adonis-agora/filter").OffsetPageMeta;
|
|
25
26
|
data: {
|
|
26
27
|
id: string;
|
|
27
28
|
name: string;
|
|
@@ -3,13 +3,14 @@ import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
|
3
3
|
import { AdminOrgsService } from '../admin_api/admin_orgs_service.js';
|
|
4
4
|
import { apiError, orgDetailDto, orgDto } from '../admin_api/dto.js';
|
|
5
5
|
import { orgAddMemberValidator, orgCreateValidator, orgInvitationValidator, orgMemberRoleValidator, orgUpdateValidator, } from '../admin_validators.js';
|
|
6
|
+
import { listOrgsPage, withListQuery } from '../list_api.js';
|
|
6
7
|
import { authkitOrigin } from '../origin.js';
|
|
7
8
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
8
9
|
/**
|
|
9
10
|
* Endpoints JSON de organizações do console admin React.
|
|
10
11
|
* 404 honesto quando o store não suporta organizações (`capability_unsupported`).
|
|
11
12
|
*
|
|
12
|
-
* GET {prefix}/api/orgs
|
|
13
|
+
* GET {prefix}/api/orgs?search=&sort=&page=&size= → página de orgs com contagem de membros
|
|
13
14
|
* POST {prefix}/api/orgs → cria uma org
|
|
14
15
|
* GET {prefix}/api/orgs/:id → detalhe da org (membros + convites)
|
|
15
16
|
* PATCH {prefix}/api/orgs/:id → edita nome/slug/logo
|
|
@@ -46,7 +47,10 @@ export default class ConsoleOrgsController {
|
|
|
46
47
|
if (!Array.isArray(result)) {
|
|
47
48
|
return ctx.response.notFound(apiError('capability_unsupported', 'O store não suporta organizações.'));
|
|
48
49
|
}
|
|
49
|
-
return
|
|
50
|
+
return withListQuery(ctx, async () => {
|
|
51
|
+
const page = await listOrgsPage(result, ctx.request.qs());
|
|
52
|
+
return { ...page, data: page.data.map((o) => orgDto(o)) };
|
|
53
|
+
});
|
|
50
54
|
}
|
|
51
55
|
/** POST {prefix}/api/orgs */
|
|
52
56
|
async store(ctx) {
|
|
@@ -6,7 +6,7 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
6
6
|
* Métricas-resumo do IdP para o dashboard do console React. Reutiliza
|
|
7
7
|
* `computeAdminStats` (mesma fonte do console Edge) e adiciona:
|
|
8
8
|
* - `clientsCount` — número de clients no adapter (0 quando o adapter não enumera)
|
|
9
|
-
* - `auditTotal` — total de eventos de auditoria (
|
|
9
|
+
* - `auditTotal` — total de eventos de auditoria (`null` quando o sink não conta)
|
|
10
10
|
* - `recentEvents` — últimos 5 eventos de auditoria ([] quando não suporta)
|
|
11
11
|
*/
|
|
12
12
|
export default class ConsoleOverviewController {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
-
import { LIST_FIRST_PAGE } from '../../pagination.js';
|
|
3
2
|
import { AdminClientsService } from '../admin_clients_service.js';
|
|
4
3
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
5
4
|
import { computeAdminStats } from '../admin_stats_service.js';
|
|
@@ -9,7 +8,7 @@ import { computeAdminStats } from '../admin_stats_service.js';
|
|
|
9
8
|
* Métricas-resumo do IdP para o dashboard do console React. Reutiliza
|
|
10
9
|
* `computeAdminStats` (mesma fonte do console Edge) e adiciona:
|
|
11
10
|
* - `clientsCount` — número de clients no adapter (0 quando o adapter não enumera)
|
|
12
|
-
* - `auditTotal` — total de eventos de auditoria (
|
|
11
|
+
* - `auditTotal` — total de eventos de auditoria (`null` quando o sink não conta)
|
|
13
12
|
* - `recentEvents` — últimos 5 eventos de auditoria ([] quando não suporta)
|
|
14
13
|
*/
|
|
15
14
|
export default class ConsoleOverviewController {
|
|
@@ -20,9 +19,8 @@ export default class ConsoleOverviewController {
|
|
|
20
19
|
const stats = await computeAdminStats(cfg, sessions);
|
|
21
20
|
const clients = new AdminClientsService(service);
|
|
22
21
|
const clientsCount = clients.canList ? (await clients.list()).length : 0;
|
|
23
|
-
const recentResult = typeof cfg.audit?.list === 'function'
|
|
24
|
-
|
|
25
|
-
: null;
|
|
22
|
+
const recentResult = typeof cfg.audit?.list === 'function' ? await cfg.audit.list({ size: 5 }) : null;
|
|
23
|
+
const auditTotal = typeof cfg.audit?.count === 'function' ? await cfg.audit.count({}) : null;
|
|
26
24
|
return {
|
|
27
25
|
usersTotal: stats.totalUsers,
|
|
28
26
|
activeSessions: stats.activeSessions,
|
|
@@ -34,7 +32,7 @@ export default class ConsoleOverviewController {
|
|
|
34
32
|
windowDays: stats.windowDays,
|
|
35
33
|
auditSupported: stats.auditSupported,
|
|
36
34
|
clientsCount,
|
|
37
|
-
auditTotal
|
|
35
|
+
auditTotal,
|
|
38
36
|
recentEvents: (recentResult?.data ?? []).map((e) => ({
|
|
39
37
|
id: e.id,
|
|
40
38
|
type: e.type,
|
|
@@ -44,26 +44,23 @@ export default class ConsolePersonalAgentsController {
|
|
|
44
44
|
};
|
|
45
45
|
agents: AgentEntry[];
|
|
46
46
|
}>;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
expiresAt: string;
|
|
65
|
-
}[];
|
|
66
|
-
}>;
|
|
47
|
+
/**
|
|
48
|
+
* GET {prefix}/api/personal-agents/connections — live grants, newest first:
|
|
49
|
+
* `?filter[protocol]=&filter[clientId]=&filter[accountId]=&sort=-createdAt&page=&size=` →
|
|
50
|
+
* `{ data, meta: { total, perPage, currentPage, lastPage, firstPage } }`.
|
|
51
|
+
*/
|
|
52
|
+
connections(ctx: HttpContext): Promise<void | import("@adonis-agora/filter").OffsetPage<{
|
|
53
|
+
id: string;
|
|
54
|
+
protocol: AgentProtocolId;
|
|
55
|
+
clientId: string;
|
|
56
|
+
agentName: string;
|
|
57
|
+
accountId: string;
|
|
58
|
+
accountEmail: string | null;
|
|
59
|
+
scopes: string[];
|
|
60
|
+
createdAt: string;
|
|
61
|
+
updatedAt: string;
|
|
62
|
+
expiresAt: string;
|
|
63
|
+
}>>;
|
|
67
64
|
/** POST {prefix}/api/personal-agents/connections/:grantId/revoke */
|
|
68
65
|
revokeConnection(ctx: HttpContext): Promise<void | {}>;
|
|
69
66
|
/**
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
+
import { InvalidColumnFilterError, offsetPage } from '@adonis-agora/filter';
|
|
2
3
|
import { displayNameFor } from '../../agents/agent_identity.js';
|
|
3
4
|
import { parseScope } from '../../agents/config.js';
|
|
4
5
|
import { personalAgentsFor } from '../../agents/runtime.js';
|
|
5
|
-
import { parseListPage, parseListSize } from '../../pagination.js';
|
|
6
6
|
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
7
7
|
import { apiError } from '../admin_api/dto.js';
|
|
8
|
+
import { ConnectionListFilter, listQueryError, readListFilter, readPage, readSort, } from '../list_api.js';
|
|
8
9
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
9
10
|
import { requireSudo } from '../sudo_mode.js';
|
|
10
11
|
/** Mesmo gate de sudo das outras ações destrutivas do console (sessões, chaves). */
|
|
@@ -182,19 +183,39 @@ export default class ConsolePersonalAgentsController {
|
|
|
182
183
|
agents,
|
|
183
184
|
};
|
|
184
185
|
}
|
|
185
|
-
/**
|
|
186
|
+
/**
|
|
187
|
+
* GET {prefix}/api/personal-agents/connections — live grants, newest first:
|
|
188
|
+
* `?filter[protocol]=&filter[clientId]=&filter[accountId]=&sort=-createdAt&page=&size=` →
|
|
189
|
+
* `{ data, meta: { total, perPage, currentPage, lastPage, firstPage } }`.
|
|
190
|
+
*/
|
|
186
191
|
async connections(ctx) {
|
|
187
192
|
const runtime = await runtimeFor(ctx);
|
|
188
193
|
if (!runtime)
|
|
189
194
|
return notSupported(ctx);
|
|
190
195
|
const qs = ctx.request.qs();
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
196
|
+
let query;
|
|
197
|
+
try {
|
|
198
|
+
const f = await readListFilter(qs, ConnectionListFilter);
|
|
199
|
+
readSort(qs, { sortable: ['createdAt'], default: '-createdAt', fixed: true });
|
|
200
|
+
const protocol = f.first('protocol');
|
|
201
|
+
if (protocol !== undefined && !enabledProtocols(runtime).includes(protocol)) {
|
|
202
|
+
throw new InvalidColumnFilterError(`Unknown protocol: "${protocol}".`);
|
|
203
|
+
}
|
|
204
|
+
query = {
|
|
205
|
+
...readPage(qs),
|
|
206
|
+
...(protocol !== undefined && { protocol }),
|
|
207
|
+
...(f.first('clientId') !== undefined && { clientId: f.first('clientId') }),
|
|
208
|
+
...(f.first('accountId') !== undefined && { accountId: f.first('accountId') }),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return listQueryError(ctx, error);
|
|
213
|
+
}
|
|
214
|
+
const { page, size, protocol } = query;
|
|
194
215
|
const filter = {
|
|
195
216
|
idPrefix: protocol ? grantPrefixOf(runtime, protocol) : undefined,
|
|
196
|
-
clientId:
|
|
197
|
-
accountId:
|
|
217
|
+
clientId: query.clientId,
|
|
218
|
+
accountId: query.accountId,
|
|
198
219
|
};
|
|
199
220
|
const now = new Date();
|
|
200
221
|
const [total, rows] = await Promise.all([
|
|
@@ -226,7 +247,7 @@ export default class ConsolePersonalAgentsController {
|
|
|
226
247
|
expiresAt: g.expiresAt.toISOString(),
|
|
227
248
|
};
|
|
228
249
|
}));
|
|
229
|
-
return {
|
|
250
|
+
return offsetPage(data, { total, page, size });
|
|
230
251
|
}
|
|
231
252
|
/** POST {prefix}/api/personal-agents/connections/:grantId/revoke */
|
|
232
253
|
async revokeConnection(ctx) {
|
|
@@ -3,7 +3,7 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
3
3
|
/**
|
|
4
4
|
* Endpoints JSON de sessões/grants do console admin React.
|
|
5
5
|
*
|
|
6
|
-
* GET {prefix}/api/sessions?accountId=
|
|
6
|
+
* GET {prefix}/api/sessions?filter[accountId]=&search=&sort=&page=&size= → página de sessões ativas
|
|
7
7
|
* POST {prefix}/api/sessions/revoke-all → revoga todas as sessões de ?accountId=
|
|
8
8
|
* GET {prefix}/api/users/:id/sessions → lista sessões + grants de um usuário específico
|
|
9
9
|
* POST {prefix}/api/users/:id/revoke-sessions → revoga todas as sessões de um usuário específico
|
|
@@ -14,16 +14,15 @@ export default class ConsoleSessionsController {
|
|
|
14
14
|
* Retorna o shape UserSessionsResult: { supported, truncated, sessions, grants }.
|
|
15
15
|
*/
|
|
16
16
|
private listForAccount;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* GET {prefix}/api/sessions — active sessions, one page:
|
|
19
|
+
* `?filter[accountId]=&search=&sort=-loginTs|email&page=&size=` →
|
|
20
|
+
* `{ data, meta, supported, truncated, grants }`. `search` matches the email or the IP;
|
|
21
|
+
* `grants` lists the account's grants when `filter[accountId]` is set (`[]` otherwise).
|
|
22
|
+
*/
|
|
18
23
|
index(ctx: HttpContext): Promise<void | {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
grants: never[];
|
|
22
|
-
truncated?: undefined;
|
|
23
|
-
} | {
|
|
24
|
-
supported: boolean;
|
|
25
|
-
truncated: boolean;
|
|
26
|
-
sessions: {
|
|
24
|
+
meta: import("@adonis-agora/filter").OffsetPageMeta;
|
|
25
|
+
data: {
|
|
27
26
|
id: string;
|
|
28
27
|
accountId: string;
|
|
29
28
|
email: string | null;
|
|
@@ -35,6 +34,8 @@ export default class ConsoleSessionsController {
|
|
|
35
34
|
ip: string | null;
|
|
36
35
|
location: string | null;
|
|
37
36
|
}[];
|
|
37
|
+
supported: boolean;
|
|
38
|
+
truncated: boolean;
|
|
38
39
|
grants: {
|
|
39
40
|
id: string;
|
|
40
41
|
accountId: string;
|
|
@@ -3,6 +3,7 @@ import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
|
3
3
|
import { apiError, grantDto, sessionDto } from '../admin_api/dto.js';
|
|
4
4
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
5
5
|
import { sessionAccountValidator } from '../admin_validators.js';
|
|
6
|
+
import { pageRows, readListFilter, readSearch, readSort, SessionListFilter, searchRows, sortRows, withListQuery, } from '../list_api.js';
|
|
6
7
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
7
8
|
import { enrichSessionsWithContext } from '../session_context.js';
|
|
8
9
|
import { requireSudo } from '../sudo_mode.js';
|
|
@@ -25,7 +26,7 @@ async function gateSudo(ctx) {
|
|
|
25
26
|
/**
|
|
26
27
|
* Endpoints JSON de sessões/grants do console admin React.
|
|
27
28
|
*
|
|
28
|
-
* GET {prefix}/api/sessions?accountId=
|
|
29
|
+
* GET {prefix}/api/sessions?filter[accountId]=&search=&sort=&page=&size= → página de sessões ativas
|
|
29
30
|
* POST {prefix}/api/sessions/revoke-all → revoga todas as sessões de ?accountId=
|
|
30
31
|
* GET {prefix}/api/users/:id/sessions → lista sessões + grants de um usuário específico
|
|
31
32
|
* POST {prefix}/api/users/:id/revoke-sessions → revoga todas as sessões de um usuário específico
|
|
@@ -55,26 +56,48 @@ export default class ConsoleSessionsController {
|
|
|
55
56
|
grants: grants.map(grantDto),
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* GET {prefix}/api/sessions — active sessions, one page:
|
|
61
|
+
* `?filter[accountId]=&search=&sort=-loginTs|email&page=&size=` →
|
|
62
|
+
* `{ data, meta, supported, truncated, grants }`. `search` matches the email or the IP;
|
|
63
|
+
* `grants` lists the account's grants when `filter[accountId]` is set (`[]` otherwise).
|
|
64
|
+
*/
|
|
59
65
|
async index(ctx) {
|
|
60
66
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
61
|
-
const
|
|
67
|
+
const cfg = service.config;
|
|
62
68
|
const adminSvc = new AdminSessionsService(service);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
const qs = ctx.request.qs();
|
|
70
|
+
return withListQuery(ctx, async () => {
|
|
71
|
+
const filter = await readListFilter(qs, SessionListFilter);
|
|
72
|
+
const sort = readSort(qs, { sortable: ['loginTs', 'email'], default: '-loginTs' });
|
|
73
|
+
if (!adminSvc.canList) {
|
|
74
|
+
return { ...pageRows([], qs), supported: false, truncated: false, grants: [] };
|
|
75
|
+
}
|
|
76
|
+
const accountId = filter.first('accountId');
|
|
77
|
+
let sessions;
|
|
78
|
+
let grants = [];
|
|
79
|
+
let truncated = false;
|
|
80
|
+
if (accountId) {
|
|
81
|
+
const account = await cfg.accountStore.findById(accountId);
|
|
82
|
+
if (!account) {
|
|
83
|
+
return ctx.response.notFound(apiError('not_found', 'Usuário não encontrado.'));
|
|
84
|
+
}
|
|
85
|
+
sessions = (await enrichSessionsWithContext(cfg, accountId, await adminSvc.listSessions(accountId))).map((s) => ({ ...s, email: s.email ?? account.email }));
|
|
86
|
+
grants = (await adminSvc.listGrants(accountId)).map(grantDto);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const all = await adminSvc.listAllSessions();
|
|
90
|
+
sessions = all.sessions;
|
|
91
|
+
truncated = all.truncated;
|
|
92
|
+
}
|
|
93
|
+
const found = searchRows(sessions, readSearch(qs), [(s) => s.email, (s) => s.ip]);
|
|
94
|
+
const sorted = sortRows(found, sort, {
|
|
95
|
+
loginTs: (s) => s.loginTs ?? null,
|
|
96
|
+
email: (s) => s.email ?? null,
|
|
97
|
+
});
|
|
98
|
+
const page = pageRows(sorted, qs);
|
|
99
|
+
return { ...page, data: page.data.map(sessionDto), supported: true, truncated, grants };
|
|
100
|
+
});
|
|
78
101
|
}
|
|
79
102
|
/** GET {prefix}/api/users/:id/sessions */
|
|
80
103
|
async userSessions(ctx) {
|
|
@@ -3,7 +3,7 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
3
3
|
/**
|
|
4
4
|
* Endpoints JSON de usuários do console admin React.
|
|
5
5
|
*
|
|
6
|
-
* GET {prefix}/api/users?search=&page=&size=
|
|
6
|
+
* GET {prefix}/api/users?search=&sort=&page=&size= → `{ data, meta: { total, perPage, currentPage, lastPage, firstPage } }`
|
|
7
7
|
* GET {prefix}/api/users/:id → detalhe + sessões + identidades + MFA status
|
|
8
8
|
* POST {prefix}/api/users → criar usuário
|
|
9
9
|
* PATCH {prefix}/api/users/:id/roles → substituir roles globais
|
|
@@ -19,13 +19,17 @@ import type { HttpContext } from '@adonisjs/core/http';
|
|
|
19
19
|
export default class ConsoleUsersController {
|
|
20
20
|
#private;
|
|
21
21
|
/** GET {prefix}/api/users */
|
|
22
|
-
index(ctx: HttpContext): Promise<{
|
|
23
|
-
meta:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
index(ctx: HttpContext): Promise<void | {
|
|
23
|
+
meta: import("@adonis-agora/filter").OffsetPageMeta;
|
|
24
|
+
data: {
|
|
25
|
+
id: string;
|
|
26
|
+
email: string;
|
|
27
|
+
name: string | null;
|
|
28
|
+
avatarUrl: string | null;
|
|
29
|
+
globalRoles: string[];
|
|
30
|
+
disabled: boolean;
|
|
31
|
+
roles: string[];
|
|
32
|
+
}[];
|
|
29
33
|
}>;
|
|
30
34
|
/** GET {prefix}/api/users/:id */
|
|
31
35
|
show(ctx: HttpContext): Promise<void | {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { supportsAccountDeletion, supportsAccountStatus } from '../../accounts/account_store.js';
|
|
3
|
-
import { ADMIN_LIST_HTTP_DEFAULT_SIZE, parseListPage, parseListSize } from '../../pagination.js';
|
|
4
3
|
import { rolesManagedExternally } from '../account_roles.js';
|
|
5
4
|
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
6
5
|
import { AdminUsersService } from '../admin_api/admin_users_service.js';
|
|
7
6
|
import { apiError, grantDto, rolesManagedExternallyError, sessionDto, userDtoWithRoles, } from '../admin_api/dto.js';
|
|
8
7
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
9
8
|
import { adminUserCreateValidator, adminUserRolesValidator, adminUserUpdateValidator, } from '../admin_validators.js';
|
|
9
|
+
import { listAccountsPage, withListQuery } from '../list_api.js';
|
|
10
10
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
11
11
|
import { resolveEffectiveRolesCatalog } from '../runtime_toggles.js';
|
|
12
12
|
import { enrichSessionsWithContext } from '../session_context.js';
|
|
@@ -35,7 +35,7 @@ async function gateSudo(ctx) {
|
|
|
35
35
|
/**
|
|
36
36
|
* Endpoints JSON de usuários do console admin React.
|
|
37
37
|
*
|
|
38
|
-
* GET {prefix}/api/users?search=&page=&size=
|
|
38
|
+
* GET {prefix}/api/users?search=&sort=&page=&size= → `{ data, meta: { total, perPage, currentPage, lastPage, firstPage } }`
|
|
39
39
|
* GET {prefix}/api/users/:id → detalhe + sessões + identidades + MFA status
|
|
40
40
|
* POST {prefix}/api/users → criar usuário
|
|
41
41
|
* PATCH {prefix}/api/users/:id/roles → substituir roles globais
|
|
@@ -53,17 +53,12 @@ export default class ConsoleUsersController {
|
|
|
53
53
|
async index(ctx) {
|
|
54
54
|
const service = await ctx.containerResolver.make('authkit.server');
|
|
55
55
|
const cfg = service.config;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
page,
|
|
62
|
-
size,
|
|
56
|
+
return withListQuery(ctx, async () => {
|
|
57
|
+
const result = await listAccountsPage(cfg.accountStore, ctx.request.qs());
|
|
58
|
+
const users = new AdminUsersService(cfg);
|
|
59
|
+
const data = await Promise.all(result.data.map(async (u) => userDtoWithRoles(cfg, u, await users.isDisabled(u.id))));
|
|
60
|
+
return { ...result, data };
|
|
63
61
|
});
|
|
64
|
-
const users = new AdminUsersService(cfg);
|
|
65
|
-
const data = await Promise.all(result.data.map(async (u) => userDtoWithRoles(cfg, u, await users.isDisabled(u.id))));
|
|
66
|
-
return { meta: { page, size, total: result.total }, data };
|
|
67
62
|
}
|
|
68
63
|
/** GET {prefix}/api/users/:id */
|
|
69
64
|
async show(ctx) {
|
|
@@ -68,9 +68,9 @@ export async function computeAdminStats(cfg, sessionsService) {
|
|
|
68
68
|
async function loadEventsInWindow(audit, type, windowStartMs) {
|
|
69
69
|
const out = [];
|
|
70
70
|
const size = ADMIN_LIST_MAX_SIZE;
|
|
71
|
-
let
|
|
71
|
+
let after;
|
|
72
72
|
while (out.length < MAX_EVENTS) {
|
|
73
|
-
const result = await audit.list({ type,
|
|
73
|
+
const result = await audit.list({ type, size, ...(after !== undefined && { after }) });
|
|
74
74
|
if (result.data.length === 0)
|
|
75
75
|
break;
|
|
76
76
|
let allBelowWindow = true;
|
|
@@ -88,9 +88,9 @@ async function loadEventsInWindow(audit, type, windowStartMs) {
|
|
|
88
88
|
// Página inteira anterior à janela → não há mais nada relevante (ordem desc).
|
|
89
89
|
if (allBelowWindow)
|
|
90
90
|
break;
|
|
91
|
-
if (result.
|
|
91
|
+
if (!result.hasNext || !result.nextCursor)
|
|
92
92
|
break;
|
|
93
|
-
|
|
93
|
+
after = result.nextCursor;
|
|
94
94
|
}
|
|
95
95
|
return out;
|
|
96
96
|
}
|