@nest-my-admin/testing 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/index.d.ts +121 -0
- package/dist/index.js +438 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nest-my-admin contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nest-my-admin/testing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Test helpers for [nest-my-admin](https://github.com/phantomboy0/nest-my-admin): call the admin as any user in-process, and crawl it for permission leaks (`expectNoLeaks`).
|
|
4
|
+
|
|
5
|
+
Documentation: https://github.com/phantomboy0/nest-my-admin#readme
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { INestApplicationContext, ModuleMetadata } from '@nestjs/common';
|
|
2
|
+
import { type TestingModuleBuilder } from '@nestjs/testing';
|
|
3
|
+
import { type ActionResult, type AdminRecord, type AdminUser, type AuditResponse, type ListResponse, type MetaResponse, type OptionsResponse, type PageResponse, type ResourceSchema, type SearchResponse } from '@nest-my-admin/core';
|
|
4
|
+
import { EffectivePermissions } from '@nest-my-admin/core/internal';
|
|
5
|
+
/** Who a test acts as. With `roles`, exactly these roles (code or stored); without, the admin resolves them as for a request. */
|
|
6
|
+
export interface TestUser extends Partial<Omit<AdminUser, 'id'>> {
|
|
7
|
+
id: string | number;
|
|
8
|
+
roles?: string[];
|
|
9
|
+
}
|
|
10
|
+
/** Query parameters: a string (`search=x&page=2`), URLSearchParams, or an object (`{ filter: { status: { eq: 'open' } } }`). */
|
|
11
|
+
export type TestQuery = string | URLSearchParams | Record<string, unknown>;
|
|
12
|
+
/** Calls the admin API as one user, in-process, through the same checks as HTTP requests (no sign-in, no HTTP). */
|
|
13
|
+
export declare class AdminClient {
|
|
14
|
+
private readonly app;
|
|
15
|
+
readonly user: AdminUser;
|
|
16
|
+
private readonly roles;
|
|
17
|
+
private readonly locale?;
|
|
18
|
+
constructor(app: INestApplicationContext, user: AdminUser, roles: string[] | undefined, locale?: string | undefined);
|
|
19
|
+
/** The user's effective permissions. */
|
|
20
|
+
permissions(): Promise<EffectivePermissions>;
|
|
21
|
+
/** Whether the user holds a permission code. */
|
|
22
|
+
can(code: string): Promise<boolean>;
|
|
23
|
+
private run;
|
|
24
|
+
meta(): Promise<MetaResponse>;
|
|
25
|
+
schema(resource: string): Promise<ResourceSchema>;
|
|
26
|
+
list(resource: string, query?: TestQuery): Promise<ListResponse>;
|
|
27
|
+
get(resource: string, id: string | number): Promise<AdminRecord>;
|
|
28
|
+
create(resource: string, body: object): Promise<AdminRecord>;
|
|
29
|
+
update(resource: string, id: string | number, body: object, options?: {
|
|
30
|
+
version?: number;
|
|
31
|
+
}): Promise<AdminRecord>;
|
|
32
|
+
delete(resource: string, id: string | number, options?: {
|
|
33
|
+
version?: number;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
restore(resource: string, id: string | number): Promise<AdminRecord>;
|
|
36
|
+
purge(resource: string, id: string | number): Promise<void>;
|
|
37
|
+
search(q: string, query?: TestQuery): Promise<SearchResponse>;
|
|
38
|
+
/** A relation field's choices (`?search=`, `values`). */
|
|
39
|
+
options(resource: string, field: string, query?: TestQuery): Promise<OptionsResponse>;
|
|
40
|
+
/**
|
|
41
|
+
* Runs an `@AdminAction`. With one id the answer holds the record, with several `ok` and `failed`; a resource
|
|
42
|
+
* action takes no ids. `input` is the action's form.
|
|
43
|
+
*/
|
|
44
|
+
runAction(resource: string, action: string, options?: {
|
|
45
|
+
ids?: Array<string | number>;
|
|
46
|
+
input?: object;
|
|
47
|
+
}): Promise<ActionResult>;
|
|
48
|
+
/** A settings page: its form, its values without the secrets, and which secrets are set. */
|
|
49
|
+
page(name: string): Promise<PageResponse>;
|
|
50
|
+
/** Saves the keys of `changes` on a settings page (a secret: text replaces it, `null` clears it). */
|
|
51
|
+
savePage(name: string, changes: object): Promise<PageResponse>;
|
|
52
|
+
/** A record's audit entries, newest first, 50 a page (`{ before }` is the page before's `next`). */
|
|
53
|
+
history(resource: string, id: string | number, query?: TestQuery): Promise<AuditResponse>;
|
|
54
|
+
/** The audit page (`actor`, `event`, `resource`, `record`, `from`, `to`, `before`); needs `audit.view`. */
|
|
55
|
+
audit(query?: TestQuery): Promise<AuditResponse>;
|
|
56
|
+
}
|
|
57
|
+
/** One thing a user saw that they should not have. */
|
|
58
|
+
export interface Leak {
|
|
59
|
+
kind: 'hidden-value' | 'out-of-scope-record' | 'unreachable-resource';
|
|
60
|
+
resource: string;
|
|
61
|
+
/** The record it belongs to (`_id`), when there is one. */
|
|
62
|
+
record?: string;
|
|
63
|
+
/** The hidden field whose value appeared. */
|
|
64
|
+
field?: string;
|
|
65
|
+
/** Where it appeared: the call and the JSON path. */
|
|
66
|
+
where: string;
|
|
67
|
+
/** The value, shortened. */
|
|
68
|
+
value?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface LeakReport {
|
|
71
|
+
/** API calls made as the user. */
|
|
72
|
+
calls: number;
|
|
73
|
+
/** Records of the resource (as a superuser) the crawl knew about. */
|
|
74
|
+
records: number;
|
|
75
|
+
hiddenFields: string[];
|
|
76
|
+
outOfScope: number;
|
|
77
|
+
/**
|
|
78
|
+
* With the audit log on: a history or the audit page had more pages than `maxPages` (50 entries each), so the
|
|
79
|
+
* crawl did not read all of it. Raise `maxPages` to cover the rest.
|
|
80
|
+
*/
|
|
81
|
+
auditTruncated?: boolean;
|
|
82
|
+
leaks: Leak[];
|
|
83
|
+
}
|
|
84
|
+
export declare class AdminLeakError extends Error {
|
|
85
|
+
readonly report: LeakReport;
|
|
86
|
+
constructor(report: LeakReport);
|
|
87
|
+
}
|
|
88
|
+
export interface NoLeaksOptions {
|
|
89
|
+
as: TestUser;
|
|
90
|
+
/** Pages crawled per list (100 records each). Default 20. */
|
|
91
|
+
maxPages?: number;
|
|
92
|
+
/** Hidden values searched for with `/api/search`. Default 20. */
|
|
93
|
+
maxSearches?: number;
|
|
94
|
+
}
|
|
95
|
+
/** Test helpers around an app that imports `AdminModule`. */
|
|
96
|
+
export declare class AdminTesting {
|
|
97
|
+
readonly app: INestApplicationContext;
|
|
98
|
+
constructor(app: INestApplicationContext);
|
|
99
|
+
/** A client acting as `user`. */
|
|
100
|
+
as(user: TestUser, options?: {
|
|
101
|
+
locale?: string;
|
|
102
|
+
}): AdminClient;
|
|
103
|
+
/** A client that may do everything. */
|
|
104
|
+
get superuser(): AdminClient;
|
|
105
|
+
/**
|
|
106
|
+
* Crawls everything `options.as` can reach around `resource` and fails (with `AdminLeakError`) when any value of a
|
|
107
|
+
* field hidden from them, or any record outside their scopes, shows up: in meta, the schema, every list page, each
|
|
108
|
+
* record, global search for the hidden values, and other resources' relation columns and pickers. With the audit
|
|
109
|
+
* log on, also in each record's history and (for a user with `audit.view`) the audit page, where the values the
|
|
110
|
+
* hidden fields held before count too.
|
|
111
|
+
*/
|
|
112
|
+
expectNoLeaks(resource: string, options: NoLeaksOptions): Promise<LeakReport>;
|
|
113
|
+
close(): Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Compiles and initializes a testing module around your app's imports, for `as()` and `expectNoLeaks()`.
|
|
117
|
+
* `configure` can override providers (`builder.overrideProvider(...)`).
|
|
118
|
+
*/
|
|
119
|
+
export declare function createAdminTestingModule(metadata: ModuleMetadata, configure?: (builder: TestingModuleBuilder) => TestingModuleBuilder): Promise<AdminTesting>;
|
|
120
|
+
/** The same helpers for an application you already created (an e2e test's `app`). */
|
|
121
|
+
export declare function adminTesting(app: INestApplicationContext): AdminTesting;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { IncomingMessage } from 'node:http';
|
|
2
|
+
import { Socket } from 'node:net';
|
|
3
|
+
import { Test } from '@nestjs/testing';
|
|
4
|
+
import { AdminError, } from '@nest-my-admin/core';
|
|
5
|
+
import { AdminApiService, AdminAudit, AdminPolicy, AdminRbac, AuditReader, EffectivePermissions, createAdminContext, runInAdminContext } from '@nest-my-admin/core/internal';
|
|
6
|
+
/** Calls the admin API as one user, in-process, through the same checks as HTTP requests (no sign-in, no HTTP). */
|
|
7
|
+
export class AdminClient {
|
|
8
|
+
app;
|
|
9
|
+
user;
|
|
10
|
+
roles;
|
|
11
|
+
locale;
|
|
12
|
+
constructor(app, user, roles, locale) {
|
|
13
|
+
this.app = app;
|
|
14
|
+
this.user = user;
|
|
15
|
+
this.roles = roles;
|
|
16
|
+
this.locale = locale;
|
|
17
|
+
}
|
|
18
|
+
/** The user's effective permissions. */
|
|
19
|
+
async permissions() {
|
|
20
|
+
const policy = this.app.get(AdminPolicy, { strict: false });
|
|
21
|
+
if (this.user.isSuperuser || this.roles === undefined)
|
|
22
|
+
return policy.forUser(this.user);
|
|
23
|
+
const known = await this.app.get(AdminRbac, { strict: false }).roles();
|
|
24
|
+
return new EffectivePermissions(this.roles.map((name) => {
|
|
25
|
+
const role = known.get(name);
|
|
26
|
+
if (!role)
|
|
27
|
+
throw new Error(`@nest-my-admin/testing: "${name}" is not a role (known: ${[...known.keys()].join(', ') || 'none'})`);
|
|
28
|
+
return role;
|
|
29
|
+
}), false);
|
|
30
|
+
}
|
|
31
|
+
/** Whether the user holds a permission code. */
|
|
32
|
+
async can(code) {
|
|
33
|
+
return (await this.permissions()).can(code);
|
|
34
|
+
}
|
|
35
|
+
async run(fn) {
|
|
36
|
+
const request = new IncomingMessage(new Socket());
|
|
37
|
+
request.method = 'GET';
|
|
38
|
+
request.url = '/';
|
|
39
|
+
const ctx = createAdminContext(request);
|
|
40
|
+
ctx.user = this.user;
|
|
41
|
+
ctx.permissions = await this.permissions();
|
|
42
|
+
if (this.locale)
|
|
43
|
+
ctx.locale = this.locale;
|
|
44
|
+
const api = this.app.get(AdminApiService, { strict: false });
|
|
45
|
+
return runInAdminContext(ctx, async () => fn(api, ctx));
|
|
46
|
+
}
|
|
47
|
+
meta() {
|
|
48
|
+
return this.run((api, ctx) => api.meta(ctx.locale, ctx));
|
|
49
|
+
}
|
|
50
|
+
schema(resource) {
|
|
51
|
+
return this.run((api, ctx) => api.schema(resource, ctx.locale, ctx));
|
|
52
|
+
}
|
|
53
|
+
list(resource, query) {
|
|
54
|
+
return this.run((api, ctx) => api.list(resource, toParams(query), ctx));
|
|
55
|
+
}
|
|
56
|
+
get(resource, id) {
|
|
57
|
+
return this.run((api, ctx) => api.get(resource, String(id), ctx));
|
|
58
|
+
}
|
|
59
|
+
create(resource, body) {
|
|
60
|
+
return this.run((api, ctx) => api.create(resource, body, ctx));
|
|
61
|
+
}
|
|
62
|
+
update(resource, id, body, options = {}) {
|
|
63
|
+
return this.run((api, ctx) => api.update(resource, String(id), body, ctx, options.version));
|
|
64
|
+
}
|
|
65
|
+
delete(resource, id, options = {}) {
|
|
66
|
+
return this.run((api, ctx) => api.remove(resource, String(id), ctx, options.version));
|
|
67
|
+
}
|
|
68
|
+
restore(resource, id) {
|
|
69
|
+
return this.run((api, ctx) => api.restore(resource, String(id), ctx));
|
|
70
|
+
}
|
|
71
|
+
purge(resource, id) {
|
|
72
|
+
return this.run((api, ctx) => api.purge(resource, String(id), ctx));
|
|
73
|
+
}
|
|
74
|
+
search(q, query) {
|
|
75
|
+
const params = toParams(query);
|
|
76
|
+
params.set('q', q);
|
|
77
|
+
return this.run((api, ctx) => api.search(params, ctx));
|
|
78
|
+
}
|
|
79
|
+
/** A relation field's choices (`?search=`, `values`). */
|
|
80
|
+
options(resource, field, query) {
|
|
81
|
+
return this.run((api, ctx) => api.fieldOptions(resource, field, toParams(query), ctx));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Runs an `@AdminAction`. With one id the answer holds the record, with several `ok` and `failed`; a resource
|
|
85
|
+
* action takes no ids. `input` is the action's form.
|
|
86
|
+
*/
|
|
87
|
+
runAction(resource, action, options = {}) {
|
|
88
|
+
const body = { ...(options.ids ? { ids: options.ids.map(String) } : {}), ...(options.input ? { input: options.input } : {}) };
|
|
89
|
+
return this.run((api, ctx) => api.runAction(resource, action, body, ctx));
|
|
90
|
+
}
|
|
91
|
+
/** A settings page: its form, its values without the secrets, and which secrets are set. */
|
|
92
|
+
page(name) {
|
|
93
|
+
return this.run((api, ctx) => api.page(name, ctx));
|
|
94
|
+
}
|
|
95
|
+
/** Saves the keys of `changes` on a settings page (a secret: text replaces it, `null` clears it). */
|
|
96
|
+
savePage(name, changes) {
|
|
97
|
+
return this.run((api, ctx) => api.savePage(name, changes, ctx));
|
|
98
|
+
}
|
|
99
|
+
/** A record's audit entries, newest first, 50 a page (`{ before }` is the page before's `next`). */
|
|
100
|
+
history(resource, id, query) {
|
|
101
|
+
return this.run((_api, ctx) => this.app.get(AuditReader, { strict: false }).history(resource, String(id), toParams(query), ctx));
|
|
102
|
+
}
|
|
103
|
+
/** The audit page (`actor`, `event`, `resource`, `record`, `from`, `to`, `before`); needs `audit.view`. */
|
|
104
|
+
audit(query) {
|
|
105
|
+
return this.run((_api, ctx) => this.app.get(AuditReader, { strict: false }).list(toParams(query), ctx));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export class AdminLeakError extends Error {
|
|
109
|
+
report;
|
|
110
|
+
constructor(report) {
|
|
111
|
+
const lines = report.leaks.slice(0, 50).map((leak) => {
|
|
112
|
+
const what = leak.kind === 'hidden-value' ? `hidden field "${leak.field}" of ${leak.resource} ${leak.record}` : leak.kind === 'out-of-scope-record' ? `out-of-scope ${leak.resource} ${leak.record}` : `resource "${leak.resource}"`;
|
|
113
|
+
return ` - ${what}${leak.value !== undefined ? ` (${JSON.stringify(leak.value)})` : ''} in ${leak.where}`;
|
|
114
|
+
});
|
|
115
|
+
const more = report.leaks.length > 50 ? `\n … and ${report.leaks.length - 50} more` : '';
|
|
116
|
+
super(`${report.leaks.length} permission leak(s):\n${lines.join('\n')}${more}`);
|
|
117
|
+
this.report = report;
|
|
118
|
+
this.name = 'AdminLeakError';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Test helpers around an app that imports `AdminModule`. */
|
|
122
|
+
export class AdminTesting {
|
|
123
|
+
app;
|
|
124
|
+
constructor(app) {
|
|
125
|
+
this.app = app;
|
|
126
|
+
}
|
|
127
|
+
/** A client acting as `user`. */
|
|
128
|
+
as(user, options = {}) {
|
|
129
|
+
const { roles, ...rest } = user;
|
|
130
|
+
const adminUser = { displayName: String(user.id), isSuperuser: false, ...rest, id: user.id };
|
|
131
|
+
return new AdminClient(this.app, adminUser, roles, options.locale);
|
|
132
|
+
}
|
|
133
|
+
/** A client that may do everything. */
|
|
134
|
+
get superuser() {
|
|
135
|
+
return this.as({ id: '@nest-my-admin/testing', displayName: 'Test superuser', isSuperuser: true });
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Crawls everything `options.as` can reach around `resource` and fails (with `AdminLeakError`) when any value of a
|
|
139
|
+
* field hidden from them, or any record outside their scopes, shows up: in meta, the schema, every list page, each
|
|
140
|
+
* record, global search for the hidden values, and other resources' relation columns and pickers. With the audit
|
|
141
|
+
* log on, also in each record's history and (for a user with `audit.view`) the audit page, where the values the
|
|
142
|
+
* hidden fields held before count too.
|
|
143
|
+
*/
|
|
144
|
+
async expectNoLeaks(resource, options) {
|
|
145
|
+
const root = this.superuser;
|
|
146
|
+
const user = this.as(options.as);
|
|
147
|
+
const maxPages = options.maxPages ?? 20;
|
|
148
|
+
const leaks = [];
|
|
149
|
+
let calls = 0;
|
|
150
|
+
const call = async (fn) => {
|
|
151
|
+
calls++;
|
|
152
|
+
return fn();
|
|
153
|
+
};
|
|
154
|
+
const full = await root.schema(resource);
|
|
155
|
+
const all = await crawlList(root, resource, maxPages);
|
|
156
|
+
let auditTruncated;
|
|
157
|
+
/** Every page of an audit listing, up to `maxPages`; the report says when there was more. */
|
|
158
|
+
const auditPages = async (fetch) => {
|
|
159
|
+
const { entries, truncated } = await crawlAudit(fetch, maxPages);
|
|
160
|
+
auditTruncated = auditTruncated === true || truncated;
|
|
161
|
+
return entries;
|
|
162
|
+
};
|
|
163
|
+
const report = (hiddenFields, outOfScope) => {
|
|
164
|
+
const result = { calls, records: all.length, hiddenFields, outOfScope, ...(auditTruncated !== undefined ? { auditTruncated } : {}), leaks };
|
|
165
|
+
if (leaks.length > 0)
|
|
166
|
+
throw new AdminLeakError(result);
|
|
167
|
+
return result;
|
|
168
|
+
};
|
|
169
|
+
// Not reachable: the resource is absent from meta and every call is "not found".
|
|
170
|
+
const meta = await call(() => user.meta());
|
|
171
|
+
const reachable = meta.groups.some((group) => group.resources.some((item) => item.name === resource));
|
|
172
|
+
if (!reachable) {
|
|
173
|
+
const probes = [
|
|
174
|
+
['schema', () => user.schema(resource)],
|
|
175
|
+
['list', () => user.list(resource)],
|
|
176
|
+
...all.slice(0, 5).map((record) => [`get ${idOf(record)}`, () => user.get(resource, idOf(record))]),
|
|
177
|
+
];
|
|
178
|
+
for (const [label, probe] of probes) {
|
|
179
|
+
if (!(await answersNotFound(() => call(probe))))
|
|
180
|
+
leaks.push({ kind: 'unreachable-resource', resource, where: label });
|
|
181
|
+
}
|
|
182
|
+
return report([], all.length);
|
|
183
|
+
}
|
|
184
|
+
const schema = await call(() => user.schema(resource));
|
|
185
|
+
const visible = new Set(schema.fields.map((field) => field.name));
|
|
186
|
+
const hiddenFields = full.fields.map((field) => field.name).filter((name) => !visible.has(name));
|
|
187
|
+
const seen = await crawlList(user, resource, maxPages, () => calls++);
|
|
188
|
+
const seenIds = new Set(seen.map((record) => idOf(record)));
|
|
189
|
+
const outside = all.filter((record) => !seenIds.has(idOf(record)));
|
|
190
|
+
const outsideIds = new Set(outside.map((record) => idOf(record)));
|
|
191
|
+
const primary = full.primaryKeys.length === 1 ? full.primaryKeys[0] : undefined;
|
|
192
|
+
const outsideKeys = new Set(primary ? outside.map((record) => String(record[primary])) : []);
|
|
193
|
+
// Values the user must never see: hidden fields of every record. A value that is also visible legitimately
|
|
194
|
+
// (the same text in a visible field of a record in scope) is not a secret.
|
|
195
|
+
const allowed = new Set();
|
|
196
|
+
for (const record of all)
|
|
197
|
+
if (seenIds.has(idOf(record)))
|
|
198
|
+
for (const name of visible)
|
|
199
|
+
for (const value of leaves(record[name]))
|
|
200
|
+
allowed.add(value);
|
|
201
|
+
const secrets = new Map();
|
|
202
|
+
for (const record of all) {
|
|
203
|
+
for (const field of hiddenFields) {
|
|
204
|
+
for (const value of leaves(record[field]))
|
|
205
|
+
if (value.length >= 4 && !allowed.has(value) && !secrets.has(value))
|
|
206
|
+
secrets.set(value, { kind: 'hidden-value', record: idOf(record), field });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Records out of scope: their title and texts (seen through another resource's title or column).
|
|
210
|
+
for (const record of outside) {
|
|
211
|
+
for (const value of [String(record._title ?? ''), ...full.fields.filter((field) => field.type === 'string' || field.type === 'text').flatMap((field) => leaves(record[field.name]))]) {
|
|
212
|
+
if (value.length >= 4 && !allowed.has(value) && !secrets.has(value))
|
|
213
|
+
secrets.set(value, { kind: 'out-of-scope-record', record: idOf(record) });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// With the audit log on, what was is as secret as what is: the values the hidden fields held before, and the
|
|
217
|
+
// titles and texts the records out of scope had.
|
|
218
|
+
const audited = this.app.get(AdminAudit, { strict: false }).enabled;
|
|
219
|
+
if (audited) {
|
|
220
|
+
const texts = new Set(full.fields.filter((field) => field.type === 'string' || field.type === 'text').map((field) => field.name));
|
|
221
|
+
const add = (value, owner) => {
|
|
222
|
+
if (value.length >= 4 && !allowed.has(value) && !secrets.has(value))
|
|
223
|
+
secrets.set(value, owner);
|
|
224
|
+
};
|
|
225
|
+
for (const record of all) {
|
|
226
|
+
const id = idOf(record);
|
|
227
|
+
// A record the superuser cannot open (in the trash since it was listed) has no history to read: only that.
|
|
228
|
+
const entries = await auditPages((before) => root.history(resource, id, before)).catch((error) => (isAdminError(error) && error.code === 'NOT_FOUND' ? [] : Promise.reject(error)));
|
|
229
|
+
for (const entry of entries) {
|
|
230
|
+
if (outsideIds.has(id) && entry.recordTitle)
|
|
231
|
+
add(entry.recordTitle, { kind: 'out-of-scope-record', record: id });
|
|
232
|
+
for (const item of entry.changes ?? []) {
|
|
233
|
+
const values = [...leaves(item.before), ...leaves(item.after)];
|
|
234
|
+
if (hiddenFields.includes(item.field))
|
|
235
|
+
for (const value of values)
|
|
236
|
+
add(value, { kind: 'hidden-value', record: id, field: item.field });
|
|
237
|
+
else if (outsideIds.has(id) && texts.has(item.field))
|
|
238
|
+
for (const value of values)
|
|
239
|
+
add(value, { kind: 'out-of-scope-record', record: id });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const scan = (where, body, ownRecords) => {
|
|
245
|
+
walk(body, '$', (path, value, parent) => {
|
|
246
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
247
|
+
const text = String(value);
|
|
248
|
+
for (const [secret, owner] of secrets) {
|
|
249
|
+
if (text.includes(secret))
|
|
250
|
+
leaks.push({ kind: owner.kind, resource, record: owner.record, ...(owner.field ? { field: owner.field } : {}), where: `${where} ${path}`, value: shorten(secret) });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (ownRecords && path.endsWith('._id') && typeof value === 'string' && outsideIds.has(value)) {
|
|
254
|
+
leaks.push({ kind: 'out-of-scope-record', resource, record: value, where: `${where} ${path}` });
|
|
255
|
+
}
|
|
256
|
+
void parent;
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
scan('meta', meta, false);
|
|
260
|
+
scan(`schema ${resource}`, schema, false);
|
|
261
|
+
scan(`list ${resource}`, seen, true);
|
|
262
|
+
for (const record of seen)
|
|
263
|
+
scan(`get ${resource} ${idOf(record)}`, await call(() => user.get(resource, idOf(record))), true);
|
|
264
|
+
for (const record of outside) {
|
|
265
|
+
if (!(await answersNotFound(() => call(() => user.get(resource, idOf(record))))))
|
|
266
|
+
leaks.push({ kind: 'out-of-scope-record', resource, record: idOf(record), where: `get ${resource} ${idOf(record)}` });
|
|
267
|
+
}
|
|
268
|
+
if (audited) {
|
|
269
|
+
for (const record of seen)
|
|
270
|
+
scan(`history ${resource} ${idOf(record)}`, await auditPages((before) => call(() => user.history(resource, idOf(record), before))), false);
|
|
271
|
+
for (const record of outside) {
|
|
272
|
+
if (!(await answersNotFound(() => call(() => user.history(resource, idOf(record))))))
|
|
273
|
+
leaks.push({ kind: 'out-of-scope-record', resource, record: idOf(record), where: `history ${resource} ${idOf(record)}` });
|
|
274
|
+
}
|
|
275
|
+
if (await user.can('audit.view')) {
|
|
276
|
+
const entries = await auditPages((before) => call(() => user.audit(before)));
|
|
277
|
+
scan('audit', entries, false);
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
if (entry.resource === resource && entry.recordId !== undefined && outsideIds.has(entry.recordId))
|
|
280
|
+
leaks.push({ kind: 'out-of-scope-record', resource, record: entry.recordId, where: `audit entry ${entry.id}` });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const hiddenValues = [...secrets.entries()].filter(([, owner]) => owner.kind === 'hidden-value').map(([secret]) => secret);
|
|
285
|
+
for (const secret of hiddenValues.slice(0, options.maxSearches ?? 20)) {
|
|
286
|
+
const found = await call(() => user.search(secret));
|
|
287
|
+
scan(`search ${JSON.stringify(shorten(secret))}`, found, false);
|
|
288
|
+
for (const group of found.groups) {
|
|
289
|
+
if (group.resource !== resource)
|
|
290
|
+
continue;
|
|
291
|
+
for (const item of group.items)
|
|
292
|
+
if (outsideIds.has(item._id))
|
|
293
|
+
leaks.push({ kind: 'out-of-scope-record', resource, record: item._id, where: `search ${JSON.stringify(shorten(secret))}` });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Every other resource the user reaches: its titles and columns, and the relations pointing here (columns, pickers).
|
|
297
|
+
for (const other of meta.groups.flatMap((group) => group.resources.map((item) => item.name))) {
|
|
298
|
+
if (other === resource)
|
|
299
|
+
continue;
|
|
300
|
+
const otherSchema = await call(() => user.schema(other));
|
|
301
|
+
const page = await call(() => user.list(other, { pageSize: 100 }));
|
|
302
|
+
scan(`list ${other}`, page, false);
|
|
303
|
+
const pointing = otherSchema.fields.filter((field) => field.type === 'relation' && field.relation?.resource === resource);
|
|
304
|
+
for (const record of page.items) {
|
|
305
|
+
for (const field of pointing) {
|
|
306
|
+
for (const ref of refs(record[field.name])) {
|
|
307
|
+
if (outsideKeys.has(String(ref.id)) && !ref.title.startsWith('#'))
|
|
308
|
+
leaks.push({ kind: 'out-of-scope-record', resource, record: String(ref.id), where: `list ${other} ${idOf(record)} ${field.name}`, value: shorten(ref.title) });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
for (const field of pointing) {
|
|
313
|
+
if (!otherSchema.form.create.includes(field.name) && !otherSchema.form.update.includes(field.name))
|
|
314
|
+
continue;
|
|
315
|
+
const choices = await call(() => user.options(other, field.name, { pageSize: 100 })).catch((error) => (isAdminError(error) ? { items: [] } : Promise.reject(error)));
|
|
316
|
+
scan(`options ${other}.${field.name}`, choices, false);
|
|
317
|
+
for (const ref of choices.items)
|
|
318
|
+
if (outsideKeys.has(String(ref.id)))
|
|
319
|
+
leaks.push({ kind: 'out-of-scope-record', resource, record: String(ref.id), where: `options ${other}.${field.name}` });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return report(hiddenFields, outside.length);
|
|
323
|
+
}
|
|
324
|
+
async close() {
|
|
325
|
+
await this.app.close();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Compiles and initializes a testing module around your app's imports, for `as()` and `expectNoLeaks()`.
|
|
330
|
+
* `configure` can override providers (`builder.overrideProvider(...)`).
|
|
331
|
+
*/
|
|
332
|
+
export async function createAdminTestingModule(metadata, configure) {
|
|
333
|
+
let builder = Test.createTestingModule(metadata);
|
|
334
|
+
if (configure)
|
|
335
|
+
builder = configure(builder);
|
|
336
|
+
const moduleRef = await builder.compile();
|
|
337
|
+
await moduleRef.init();
|
|
338
|
+
return new AdminTesting(moduleRef);
|
|
339
|
+
}
|
|
340
|
+
/** The same helpers for an application you already created (an e2e test's `app`). */
|
|
341
|
+
export function adminTesting(app) {
|
|
342
|
+
return new AdminTesting(app);
|
|
343
|
+
}
|
|
344
|
+
async function crawlList(client, resource, maxPages, count) {
|
|
345
|
+
const records = [];
|
|
346
|
+
let after;
|
|
347
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
348
|
+
count?.();
|
|
349
|
+
const result = await client.list(resource, { pageSize: 100, ...(after ? { after } : { page }) });
|
|
350
|
+
records.push(...result.items);
|
|
351
|
+
if (result.nextCursor !== undefined) {
|
|
352
|
+
if (!result.nextCursor)
|
|
353
|
+
break;
|
|
354
|
+
after = result.nextCursor;
|
|
355
|
+
}
|
|
356
|
+
else if (result.items.length < result.pageSize || (result.total !== null && page * result.pageSize >= result.total) || result.hasMore === false)
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
return records;
|
|
360
|
+
}
|
|
361
|
+
/** Every page of an audit listing (50 entries each), up to `maxPages`; `truncated` when there were more. */
|
|
362
|
+
async function crawlAudit(fetch, maxPages) {
|
|
363
|
+
const entries = [];
|
|
364
|
+
let before;
|
|
365
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
366
|
+
const result = await fetch(before ? { before } : {});
|
|
367
|
+
entries.push(...result.entries);
|
|
368
|
+
if (!result.next)
|
|
369
|
+
return { entries, truncated: false };
|
|
370
|
+
before = result.next;
|
|
371
|
+
}
|
|
372
|
+
return { entries, truncated: true };
|
|
373
|
+
}
|
|
374
|
+
async function answersNotFound(fn) {
|
|
375
|
+
try {
|
|
376
|
+
await fn();
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
if (isAdminError(error) && error.code === 'NOT_FOUND')
|
|
381
|
+
return true;
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function isAdminError(error) {
|
|
386
|
+
return error instanceof AdminError;
|
|
387
|
+
}
|
|
388
|
+
function toParams(query) {
|
|
389
|
+
if (query === undefined)
|
|
390
|
+
return new URLSearchParams();
|
|
391
|
+
if (typeof query === 'string')
|
|
392
|
+
return new URLSearchParams(query);
|
|
393
|
+
if (query instanceof URLSearchParams)
|
|
394
|
+
return new URLSearchParams(query);
|
|
395
|
+
const params = new URLSearchParams();
|
|
396
|
+
const add = (key, value) => {
|
|
397
|
+
if (value === undefined)
|
|
398
|
+
return;
|
|
399
|
+
if (Array.isArray(value))
|
|
400
|
+
value.forEach((item) => add(key, item));
|
|
401
|
+
else if (value !== null && typeof value === 'object')
|
|
402
|
+
for (const [inner, nested] of Object.entries(value))
|
|
403
|
+
add(`${key}[${inner}]`, nested);
|
|
404
|
+
else
|
|
405
|
+
params.append(key, String(value));
|
|
406
|
+
};
|
|
407
|
+
for (const [key, value] of Object.entries(query))
|
|
408
|
+
add(key, value);
|
|
409
|
+
return params;
|
|
410
|
+
}
|
|
411
|
+
/** The texts inside a value (a relation's title, an object's strings), numbers as text. */
|
|
412
|
+
function leaves(value) {
|
|
413
|
+
const out = [];
|
|
414
|
+
walk(value, '', (_path, leaf) => {
|
|
415
|
+
if (typeof leaf === 'string' || (typeof leaf === 'number' && Number.isFinite(leaf)))
|
|
416
|
+
out.push(String(leaf));
|
|
417
|
+
});
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
function walk(value, path, visit, parent) {
|
|
421
|
+
if (Array.isArray(value))
|
|
422
|
+
value.forEach((item, index) => walk(item, `${path}[${index}]`, visit, value));
|
|
423
|
+
else if (value !== null && typeof value === 'object' && !(value instanceof Date))
|
|
424
|
+
for (const [key, item] of Object.entries(value))
|
|
425
|
+
walk(item, `${path}.${key}`, visit, value);
|
|
426
|
+
else
|
|
427
|
+
visit(path, value instanceof Date ? value.toISOString() : value, parent);
|
|
428
|
+
}
|
|
429
|
+
function refs(value) {
|
|
430
|
+
return (Array.isArray(value) ? value : value ? [value] : []).filter((ref) => typeof ref === 'object' && ref !== null && 'id' in ref && typeof ref.title === 'string');
|
|
431
|
+
}
|
|
432
|
+
function shorten(text) {
|
|
433
|
+
return text.length > 40 ? `${text.slice(0, 37)}…` : text;
|
|
434
|
+
}
|
|
435
|
+
function idOf(record) {
|
|
436
|
+
return String(record._id);
|
|
437
|
+
}
|
|
438
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nest-my-admin/testing",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Test helpers for nest-my-admin: call the admin as any user, and crawl for permission leaks",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/phantomboy0/nest-my-admin.git",
|
|
9
|
+
"directory": "packages/testing"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/phantomboy0/nest-my-admin#readme",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist"],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20.19.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"prepack": "cp ../../LICENSE .",
|
|
29
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
30
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@nest-my-admin/core": "0.2.1",
|
|
34
|
+
"@nestjs/common": "^11.0.0 || ^12.0.0",
|
|
35
|
+
"@nestjs/core": "^11.0.0 || ^12.0.0",
|
|
36
|
+
"@nestjs/testing": "^11.0.0 || ^12.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@nest-my-admin/core": "0.2.1",
|
|
40
|
+
"@nestjs/common": "12.1.1",
|
|
41
|
+
"@nestjs/core": "12.1.1",
|
|
42
|
+
"@nestjs/platform-express": "12.1.1",
|
|
43
|
+
"@nestjs/testing": "12.1.1",
|
|
44
|
+
"@nestjs/typeorm": "12.0.2",
|
|
45
|
+
"class-transformer": "0.5.1",
|
|
46
|
+
"class-validator": "0.15.1",
|
|
47
|
+
"mysql2": "3.24.5",
|
|
48
|
+
"pg": "8.23.0",
|
|
49
|
+
"reflect-metadata": "0.2.2",
|
|
50
|
+
"rxjs": "7.8.2",
|
|
51
|
+
"sql.js": "1.14.2",
|
|
52
|
+
"typeorm": "1.1.1"
|
|
53
|
+
}
|
|
54
|
+
}
|