@strapi-community/plugin-better-auth-dashboard 1.0.0 → 1.1.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/README.md +121 -2
- package/dist/admin/{Root-CgVBJuoM.mjs → Root-C-dQhq8o.mjs} +1 -1
- package/dist/admin/{Root-CsiOnzlB.js → Root-CMQ7swi6.js} +1 -1
- package/dist/admin/{index-DvfwvCj0.mjs → index-B5jiojjV.mjs} +1 -1
- package/dist/admin/{index-BCBu-0nr.js → index-Bko3vlk0.js} +4 -4
- package/dist/admin/{index-CPSoAbZt.js → index-CSyPvFnN.js} +6 -4
- package/dist/admin/{index-gFqHlMLP.mjs → index-CvNwHg1k.mjs} +2 -2
- package/dist/admin/{index-D9ttdrT3.js → index-D1nrIz0Y.js} +2 -2
- package/dist/admin/{index-BI2WIsds.mjs → index-DAkkUU0o.mjs} +4 -4
- package/dist/admin/{index-VyHXoQ6p.mjs → index-NF35nlqQ.mjs} +6 -4
- package/dist/admin/{index-DQdn5s8q.js → index-hbz3gTsi.js} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/{useModelSchema-C6bxNjW4.mjs → useModelSchema-Bo8WEBwb.mjs} +1 -1
- package/dist/admin/{useModelSchema-BogQah8f.js → useModelSchema-DON5XCju.js} +1 -1
- package/dist/server/index.js +458 -49
- package/dist/server/index.mjs +459 -50
- package/package.json +7 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { errors } from "@strapi/utils";
|
|
1
|
+
import { errors, z } from "@strapi/utils";
|
|
2
2
|
const strapi$1 = { "name": "better-auth-dashboard" };
|
|
3
3
|
const packageJson = {
|
|
4
4
|
strapi: strapi$1
|
|
@@ -35,8 +35,14 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
const config = {
|
|
38
|
-
default: {},
|
|
38
|
+
default: { mcp: { enabled: false }, dashApi: { enabled: false } },
|
|
39
39
|
validator(config2) {
|
|
40
|
+
if (config2.mcp !== void 0 && config2.mcp.enabled !== void 0 && typeof config2.mcp.enabled !== "boolean") {
|
|
41
|
+
throw new Error("mcp.enabled must be a boolean");
|
|
42
|
+
}
|
|
43
|
+
if (config2.dashApi !== void 0 && config2.dashApi.enabled !== void 0 && typeof config2.dashApi.enabled !== "boolean") {
|
|
44
|
+
throw new Error("dashApi.enabled must be a boolean");
|
|
45
|
+
}
|
|
40
46
|
if (config2.email_callback_url === void 0) return;
|
|
41
47
|
if (typeof config2.email_callback_url !== "string" || config2.email_callback_url.length === 0) {
|
|
42
48
|
throw new Error("email_callback_url must be a non-empty string");
|
|
@@ -50,60 +56,83 @@ const config = {
|
|
|
50
56
|
};
|
|
51
57
|
const DASHBOARD_API_KEY = process.env.BETTER_AUTH_DASHBOARD_SECRET || "strapi-internal-dashboard-key";
|
|
52
58
|
const DASH_CONTEXT_HEADER = "x-dash-context";
|
|
59
|
+
function decodeDashContextHeader(value) {
|
|
60
|
+
if (typeof value !== "string" || value.length === 0) return {};
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(Buffer.from(value, "base64").toString("utf8"));
|
|
63
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
53
70
|
async function hashApiKey(value) {
|
|
54
71
|
const data = new TextEncoder().encode(value);
|
|
55
72
|
const buf = await crypto.subtle.digest("SHA-256", data);
|
|
56
73
|
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
57
74
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
async function signDashToken(auth, extra = {}) {
|
|
76
|
+
const { token } = await auth.api.signJWT({
|
|
77
|
+
body: {
|
|
78
|
+
payload: {
|
|
79
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
80
|
+
apiKeyHash: await hashApiKey(DASHBOARD_API_KEY),
|
|
81
|
+
...extra
|
|
82
|
+
},
|
|
83
|
+
overrideOptions: { jwt: { expirationTime: "5m" } }
|
|
67
84
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
});
|
|
86
|
+
return token;
|
|
87
|
+
}
|
|
88
|
+
function getAuth(strapi2) {
|
|
89
|
+
const auth = strapi2.service("plugin::better-auth.auth-service").getAuth();
|
|
90
|
+
if (!auth) {
|
|
91
|
+
throw new Error("No Better Auth configuration was found.");
|
|
92
|
+
}
|
|
93
|
+
if (!auth.api?.signJWT) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
"The Better Auth jwt() plugin is required for dashboard/MCP features. Add jwt() from 'better-auth/plugins' to your Better Auth configuration."
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return auth;
|
|
99
|
+
}
|
|
100
|
+
async function getDashApi(strapi2, context = {}) {
|
|
101
|
+
const auth = getAuth(strapi2);
|
|
102
|
+
const token = await signDashToken(auth, context);
|
|
103
|
+
return { auth, headers: new Headers({ Authorization: `Bearer ${token}` }) };
|
|
104
|
+
}
|
|
105
|
+
const dashController = () => ({
|
|
106
|
+
async handleDashRequest(ctx) {
|
|
107
|
+
const path = ctx.params.path;
|
|
108
|
+
if (!path) {
|
|
109
|
+
throw new errors.ValidationError("Missing dash endpoint path.");
|
|
79
110
|
}
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
});
|
|
111
|
+
const auth = getAuth(strapi);
|
|
112
|
+
const token = await signDashToken(
|
|
113
|
+
auth,
|
|
114
|
+
decodeDashContextHeader(ctx.request.headers[DASH_CONTEXT_HEADER])
|
|
115
|
+
);
|
|
87
116
|
const url = new URL(
|
|
88
117
|
ctx.request.url,
|
|
89
118
|
`${ctx.request.protocol}://${ctx.request.host}`
|
|
90
119
|
);
|
|
91
|
-
|
|
92
|
-
url.pathname = `/api/auth/${betterAuthPath}`;
|
|
120
|
+
url.pathname = `/api/auth/dash/${path}`;
|
|
93
121
|
const headers = new Headers();
|
|
94
|
-
Object.entries(ctx.request.headers)
|
|
122
|
+
for (const [key, value] of Object.entries(ctx.request.headers)) {
|
|
95
123
|
if (value) {
|
|
96
124
|
headers.set(key, Array.isArray(value) ? value[0] : String(value));
|
|
97
125
|
}
|
|
98
|
-
}
|
|
126
|
+
}
|
|
99
127
|
headers.set("Authorization", `Bearer ${token}`);
|
|
100
128
|
const hasBody = ctx.request.method !== "GET" && ctx.request.method !== "HEAD";
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
const response = await auth.handler(
|
|
130
|
+
new Request(url.toString(), {
|
|
131
|
+
method: ctx.request.method,
|
|
132
|
+
headers,
|
|
133
|
+
body: hasBody ? JSON.stringify(ctx.request.body) : void 0
|
|
134
|
+
})
|
|
135
|
+
);
|
|
107
136
|
ctx.status = response.status;
|
|
108
137
|
response.headers.forEach((value, key) => {
|
|
109
138
|
ctx.set(key, value);
|
|
@@ -204,7 +233,7 @@ const settingsController = () => ({
|
|
|
204
233
|
}
|
|
205
234
|
});
|
|
206
235
|
const controllers = {
|
|
207
|
-
"
|
|
236
|
+
"dash-controller": dashController,
|
|
208
237
|
"db-controller": dbController,
|
|
209
238
|
"settings-controller": settingsController
|
|
210
239
|
};
|
|
@@ -342,6 +371,373 @@ const ACTIONS = {
|
|
|
342
371
|
}
|
|
343
372
|
]
|
|
344
373
|
};
|
|
374
|
+
const USER_UID = "plugin::better-auth.user";
|
|
375
|
+
const ACTION = {
|
|
376
|
+
OVERVIEW_READ: `plugin::${PLUGIN_ID}.overview.read`,
|
|
377
|
+
USER_READ: `plugin::${PLUGIN_ID}.user.read`,
|
|
378
|
+
USER_UPDATE: `plugin::${PLUGIN_ID}.user.update`
|
|
379
|
+
};
|
|
380
|
+
function getMcp(strapi2) {
|
|
381
|
+
return strapi2.ai?.mcp;
|
|
382
|
+
}
|
|
383
|
+
function userHasAttribute(strapi2, uid, attribute) {
|
|
384
|
+
const map = strapi2.contentTypes;
|
|
385
|
+
return Boolean(map[uid]?.attributes && attribute in map[uid].attributes);
|
|
386
|
+
}
|
|
387
|
+
function toolResult(payload) {
|
|
388
|
+
return {
|
|
389
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
390
|
+
structuredContent: payload
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function toolError(message) {
|
|
394
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
395
|
+
}
|
|
396
|
+
function dashErrorMessage(e) {
|
|
397
|
+
if (e && typeof e === "object") {
|
|
398
|
+
const anyE = e;
|
|
399
|
+
if (anyE.body?.message) return String(anyE.body.message);
|
|
400
|
+
if (anyE.message) return String(anyE.message);
|
|
401
|
+
}
|
|
402
|
+
return String(e);
|
|
403
|
+
}
|
|
404
|
+
function isNotFoundError(e) {
|
|
405
|
+
const anyE = e;
|
|
406
|
+
return anyE?.statusCode === 404 || anyE?.status === "NOT_FOUND";
|
|
407
|
+
}
|
|
408
|
+
const dashUserOutput = z.object({
|
|
409
|
+
id: z.union([z.string(), z.number()]),
|
|
410
|
+
name: z.string().nullable().optional(),
|
|
411
|
+
email: z.string().nullable().optional(),
|
|
412
|
+
emailVerified: z.boolean().nullable().optional(),
|
|
413
|
+
image: z.string().nullable().optional(),
|
|
414
|
+
role: z.string().nullable().optional(),
|
|
415
|
+
banned: z.boolean().nullable().optional(),
|
|
416
|
+
banReason: z.string().nullable().optional(),
|
|
417
|
+
banExpires: z.union([z.string(), z.number()]).nullable().optional(),
|
|
418
|
+
createdAt: z.union([z.string(), z.number()]).nullable().optional(),
|
|
419
|
+
updatedAt: z.union([z.string(), z.number()]).nullable().optional()
|
|
420
|
+
});
|
|
421
|
+
function mapDashUser(u) {
|
|
422
|
+
return {
|
|
423
|
+
id: u.id,
|
|
424
|
+
name: u.name ?? null,
|
|
425
|
+
email: u.email ?? null,
|
|
426
|
+
emailVerified: u.emailVerified ?? null,
|
|
427
|
+
image: u.image ?? null,
|
|
428
|
+
role: u.role ?? null,
|
|
429
|
+
banned: u.banned ?? null,
|
|
430
|
+
banReason: u.banReason ?? null,
|
|
431
|
+
banExpires: u.banExpires ?? null,
|
|
432
|
+
createdAt: u.createdAt ?? null,
|
|
433
|
+
updatedAt: u.updatedAt ?? null
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function registerBanTools(mcp) {
|
|
437
|
+
mcp.registerTool({
|
|
438
|
+
name: "better_auth_ban_user",
|
|
439
|
+
title: "Ban a Better Auth user",
|
|
440
|
+
description: "Ban (block) a Better Auth user by id, through the Better Auth dashboard API. Optionally set a reason and an expiry in days; Better Auth revokes the user's sessions.",
|
|
441
|
+
auth: { policies: [{ action: ACTION.USER_UPDATE }] },
|
|
442
|
+
resolveInputSchema: () => z.object({
|
|
443
|
+
userId: z.string().describe("The Better Auth user id"),
|
|
444
|
+
banReason: z.string().optional(),
|
|
445
|
+
banExpiresInDays: z.number().positive().optional().describe(
|
|
446
|
+
"Days from now until the ban expires; omit for a permanent ban"
|
|
447
|
+
)
|
|
448
|
+
}),
|
|
449
|
+
resolveOutputSchema: () => z.object({ banned: z.boolean() }),
|
|
450
|
+
createHandler: (strapi2) => async ({ args }) => {
|
|
451
|
+
const body = {};
|
|
452
|
+
if (args.banReason) body.banReason = args.banReason;
|
|
453
|
+
if (args.banExpiresInDays) {
|
|
454
|
+
body.banExpires = Date.now() + args.banExpiresInDays * 864e5;
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const { auth, headers } = await getDashApi(strapi2, {
|
|
458
|
+
userId: args.userId
|
|
459
|
+
});
|
|
460
|
+
await auth.api.dashBanUser({ body, headers });
|
|
461
|
+
return toolResult({ banned: true });
|
|
462
|
+
} catch (e) {
|
|
463
|
+
return toolError(`Failed to ban user: ${dashErrorMessage(e)}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
mcp.registerTool({
|
|
468
|
+
name: "better_auth_unban_user",
|
|
469
|
+
title: "Unban a Better Auth user",
|
|
470
|
+
description: "Lift the ban on a Better Auth user by id, through the Better Auth dashboard API.",
|
|
471
|
+
auth: { policies: [{ action: ACTION.USER_UPDATE }] },
|
|
472
|
+
resolveInputSchema: () => z.object({ userId: z.string().describe("The Better Auth user id") }),
|
|
473
|
+
resolveOutputSchema: () => z.object({ unbanned: z.boolean() }),
|
|
474
|
+
createHandler: (strapi2) => async ({ args }) => {
|
|
475
|
+
try {
|
|
476
|
+
const { auth, headers } = await getDashApi(strapi2, {
|
|
477
|
+
userId: args.userId
|
|
478
|
+
});
|
|
479
|
+
await auth.api.dashUnbanUser({ headers });
|
|
480
|
+
return toolResult({ unbanned: true });
|
|
481
|
+
} catch (e) {
|
|
482
|
+
return toolError(`Failed to unban user: ${dashErrorMessage(e)}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
function registerGetUser(mcp) {
|
|
488
|
+
mcp.registerTool({
|
|
489
|
+
name: "better_auth_get_user",
|
|
490
|
+
title: "Get a Better Auth user",
|
|
491
|
+
description: "Fetch a single Better Auth user by its Better Auth user id (use better_auth_list_users to find the id). Returns the user and its session/account counts.",
|
|
492
|
+
auth: { policies: [{ action: ACTION.USER_READ }] },
|
|
493
|
+
resolveInputSchema: () => z.object({
|
|
494
|
+
userId: z.string().describe("The Better Auth user id")
|
|
495
|
+
}),
|
|
496
|
+
resolveOutputSchema: () => z.object({
|
|
497
|
+
found: z.boolean(),
|
|
498
|
+
user: dashUserOutput.nullable(),
|
|
499
|
+
sessionsCount: z.number().optional(),
|
|
500
|
+
accountsCount: z.number().optional()
|
|
501
|
+
}),
|
|
502
|
+
createHandler: (strapi2) => async ({ args }) => {
|
|
503
|
+
try {
|
|
504
|
+
const { auth, headers } = await getDashApi(strapi2, {
|
|
505
|
+
userId: args.userId
|
|
506
|
+
});
|
|
507
|
+
const user = await auth.api.getDashUser({ query: {}, headers });
|
|
508
|
+
const raw = user;
|
|
509
|
+
return toolResult({
|
|
510
|
+
found: true,
|
|
511
|
+
user: mapDashUser(user),
|
|
512
|
+
sessionsCount: Array.isArray(raw.session) ? raw.session.length : void 0,
|
|
513
|
+
accountsCount: Array.isArray(raw.account) ? raw.account.length : void 0
|
|
514
|
+
});
|
|
515
|
+
} catch (e) {
|
|
516
|
+
if (isNotFoundError(e)) {
|
|
517
|
+
return toolResult({ found: false, user: null });
|
|
518
|
+
}
|
|
519
|
+
return toolError(`Failed to get user: ${dashErrorMessage(e)}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
function registerListUsers(mcp) {
|
|
525
|
+
mcp.registerTool({
|
|
526
|
+
name: "better_auth_list_users",
|
|
527
|
+
title: "List Better Auth users",
|
|
528
|
+
description: "List and search Better Auth users (via the Better Auth dashboard API). `search` matches name or email, case-insensitively. Returns users plus the total count.",
|
|
529
|
+
auth: { policies: [{ action: ACTION.USER_READ }] },
|
|
530
|
+
resolveInputSchema: () => z.object({
|
|
531
|
+
search: z.string().optional().describe("Case-insensitive substring matched against name or email"),
|
|
532
|
+
limit: z.number().int().min(1).max(100).default(25),
|
|
533
|
+
offset: z.number().int().min(0).default(0)
|
|
534
|
+
}),
|
|
535
|
+
resolveOutputSchema: () => z.object({
|
|
536
|
+
total: z.number(),
|
|
537
|
+
count: z.number(),
|
|
538
|
+
onlineUsers: z.number().optional(),
|
|
539
|
+
users: z.array(dashUserOutput)
|
|
540
|
+
}),
|
|
541
|
+
createHandler: (strapi2) => async ({ args }) => {
|
|
542
|
+
const { search, limit, offset } = args;
|
|
543
|
+
const where = search ? JSON.stringify([
|
|
544
|
+
{
|
|
545
|
+
field: "email",
|
|
546
|
+
operator: "contains",
|
|
547
|
+
value: search,
|
|
548
|
+
connector: "OR"
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
field: "name",
|
|
552
|
+
operator: "contains",
|
|
553
|
+
value: search,
|
|
554
|
+
connector: "OR"
|
|
555
|
+
}
|
|
556
|
+
]) : void 0;
|
|
557
|
+
try {
|
|
558
|
+
const { auth, headers } = await getDashApi(strapi2);
|
|
559
|
+
const result = await auth.api.getDashUsers({
|
|
560
|
+
query: {
|
|
561
|
+
limit,
|
|
562
|
+
offset,
|
|
563
|
+
sortBy: "createdAt",
|
|
564
|
+
sortOrder: "desc",
|
|
565
|
+
...where ? { where } : {}
|
|
566
|
+
},
|
|
567
|
+
headers
|
|
568
|
+
});
|
|
569
|
+
const users = (result.users ?? []).map(
|
|
570
|
+
(u) => mapDashUser(u)
|
|
571
|
+
);
|
|
572
|
+
return toolResult({
|
|
573
|
+
total: result.total ?? users.length,
|
|
574
|
+
count: users.length,
|
|
575
|
+
onlineUsers: result.onlineUsers,
|
|
576
|
+
users
|
|
577
|
+
});
|
|
578
|
+
} catch (e) {
|
|
579
|
+
return toolError(`Failed to list users: ${dashErrorMessage(e)}`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
function registerSignupsGraph(mcp) {
|
|
585
|
+
mcp.registerTool({
|
|
586
|
+
name: "better_auth_signups_graph",
|
|
587
|
+
title: "Better Auth sign-ups over time",
|
|
588
|
+
description: "Return a time series of Better Auth user sign-ups (via the dashboard API), bucketed by day, week or month. Each point has newUsers, activeUsers and the running totalUsers.",
|
|
589
|
+
auth: { policies: [{ action: ACTION.OVERVIEW_READ }] },
|
|
590
|
+
resolveInputSchema: () => z.object({
|
|
591
|
+
period: z.enum(["daily", "weekly", "monthly"]).default("daily")
|
|
592
|
+
}),
|
|
593
|
+
resolveOutputSchema: () => z.object({
|
|
594
|
+
period: z.string(),
|
|
595
|
+
points: z.array(
|
|
596
|
+
z.object({
|
|
597
|
+
date: z.string(),
|
|
598
|
+
label: z.string(),
|
|
599
|
+
totalUsers: z.number(),
|
|
600
|
+
newUsers: z.number(),
|
|
601
|
+
activeUsers: z.number()
|
|
602
|
+
})
|
|
603
|
+
)
|
|
604
|
+
}),
|
|
605
|
+
createHandler: (strapi2) => async ({ args }) => {
|
|
606
|
+
try {
|
|
607
|
+
const { auth, headers } = await getDashApi(strapi2);
|
|
608
|
+
const graph = await auth.api.dashGetUserGraphData({
|
|
609
|
+
query: { period: args.period },
|
|
610
|
+
headers
|
|
611
|
+
});
|
|
612
|
+
return toolResult({
|
|
613
|
+
period: graph.period ?? args.period,
|
|
614
|
+
points: (graph.data ?? []).map((p) => ({
|
|
615
|
+
date: String(p.date ?? ""),
|
|
616
|
+
label: String(p.label ?? ""),
|
|
617
|
+
totalUsers: Number(p.totalUsers ?? 0),
|
|
618
|
+
newUsers: Number(p.newUsers ?? 0),
|
|
619
|
+
activeUsers: Number(p.activeUsers ?? 0)
|
|
620
|
+
}))
|
|
621
|
+
});
|
|
622
|
+
} catch (e) {
|
|
623
|
+
return toolError(`Failed to load graph: ${dashErrorMessage(e)}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
function registerUpdateUser(mcp) {
|
|
629
|
+
mcp.registerTool({
|
|
630
|
+
name: "better_auth_update_user",
|
|
631
|
+
title: "Update a Better Auth user",
|
|
632
|
+
description: "Update a Better Auth user's profile (name, email, image, emailVerified) by Better Auth user id, through the Better Auth dashboard API. Use the ban tools for ban state.",
|
|
633
|
+
auth: { policies: [{ action: ACTION.USER_UPDATE }] },
|
|
634
|
+
resolveInputSchema: () => z.object({
|
|
635
|
+
userId: z.string().describe("The Better Auth user id"),
|
|
636
|
+
name: z.string().optional(),
|
|
637
|
+
email: z.string().email().optional(),
|
|
638
|
+
image: z.string().optional(),
|
|
639
|
+
emailVerified: z.boolean().optional()
|
|
640
|
+
}),
|
|
641
|
+
resolveOutputSchema: () => z.object({
|
|
642
|
+
updated: z.boolean(),
|
|
643
|
+
user: dashUserOutput.nullable()
|
|
644
|
+
}),
|
|
645
|
+
createHandler: (strapi2) => async ({ args }) => {
|
|
646
|
+
const body = {};
|
|
647
|
+
if (args.name !== void 0) body.name = args.name;
|
|
648
|
+
if (args.email !== void 0) body.email = args.email;
|
|
649
|
+
if (args.image !== void 0) body.image = args.image;
|
|
650
|
+
if (args.emailVerified !== void 0)
|
|
651
|
+
body.emailVerified = args.emailVerified;
|
|
652
|
+
if (Object.keys(body).length === 0) {
|
|
653
|
+
return toolError("No fields to update were provided.");
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
const { auth, headers } = await getDashApi(strapi2, {
|
|
657
|
+
userId: args.userId
|
|
658
|
+
});
|
|
659
|
+
const user = await auth.api.updateDashUser({ body, headers });
|
|
660
|
+
return toolResult({
|
|
661
|
+
updated: true,
|
|
662
|
+
user: mapDashUser(user)
|
|
663
|
+
});
|
|
664
|
+
} catch (e) {
|
|
665
|
+
return toolError(`Failed to update user: ${dashErrorMessage(e)}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
const signupPeriod = z.object({
|
|
671
|
+
signUps: z.number().nullable(),
|
|
672
|
+
percentage: z.number().nullable()
|
|
673
|
+
});
|
|
674
|
+
const activePeriod = z.object({
|
|
675
|
+
active: z.number().nullable(),
|
|
676
|
+
percentage: z.number().nullable()
|
|
677
|
+
});
|
|
678
|
+
function registerUserStats(mcp) {
|
|
679
|
+
mcp.registerTool({
|
|
680
|
+
name: "better_auth_user_stats",
|
|
681
|
+
title: "Better Auth user statistics",
|
|
682
|
+
description: "Return Better Auth user statistics (via the dashboard API): total users, sign-ups in the last day/week/month with percentage change vs the previous period, and active users per period.",
|
|
683
|
+
auth: { policies: [{ action: ACTION.OVERVIEW_READ }] },
|
|
684
|
+
resolveOutputSchema: () => z.object({
|
|
685
|
+
total: z.number().nullable(),
|
|
686
|
+
daily: signupPeriod,
|
|
687
|
+
weekly: signupPeriod,
|
|
688
|
+
monthly: signupPeriod,
|
|
689
|
+
activeUsers: z.object({
|
|
690
|
+
daily: activePeriod,
|
|
691
|
+
weekly: activePeriod,
|
|
692
|
+
monthly: activePeriod
|
|
693
|
+
}),
|
|
694
|
+
degraded: z.boolean().optional()
|
|
695
|
+
}),
|
|
696
|
+
createHandler: (strapi2) => async () => {
|
|
697
|
+
try {
|
|
698
|
+
const { auth, headers } = await getDashApi(strapi2);
|
|
699
|
+
const stats = await auth.api.dashGetUserStats({ headers });
|
|
700
|
+
return toolResult(stats);
|
|
701
|
+
} catch (e) {
|
|
702
|
+
return toolError(`Failed to load stats: ${dashErrorMessage(e)}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
function registerMcpTools(strapi2) {
|
|
708
|
+
const mcp = getMcp(strapi2);
|
|
709
|
+
if (!mcp) {
|
|
710
|
+
strapi2.log.warn(
|
|
711
|
+
"[@strapi-community/plugin-better-auth-dashboard] MCP integration is enabled but this Strapi version has no MCP server (requires >= 5.47). Skipping."
|
|
712
|
+
);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
registerListUsers(mcp);
|
|
716
|
+
registerGetUser(mcp);
|
|
717
|
+
registerUpdateUser(mcp);
|
|
718
|
+
registerUserStats(mcp);
|
|
719
|
+
registerSignupsGraph(mcp);
|
|
720
|
+
if (userHasAttribute(strapi2, USER_UID, "banned")) {
|
|
721
|
+
registerBanTools(mcp);
|
|
722
|
+
}
|
|
723
|
+
strapi2.log.info(
|
|
724
|
+
"[@strapi-community/plugin-better-auth-dashboard] Registered Better Auth MCP tools."
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
const HTTP_METHODS = ["GET", "POST"];
|
|
728
|
+
const contentApiRoutes = {
|
|
729
|
+
type: "content-api",
|
|
730
|
+
routes: HTTP_METHODS.map((method) => ({
|
|
731
|
+
method,
|
|
732
|
+
path: "/better-auth-dashboard/dash/:path*",
|
|
733
|
+
handler: "dash-controller.handleDashRequest",
|
|
734
|
+
info: { pluginName: "better-auth-dashboard" },
|
|
735
|
+
config: {
|
|
736
|
+
policies: [],
|
|
737
|
+
prefix: ""
|
|
738
|
+
}
|
|
739
|
+
}))
|
|
740
|
+
};
|
|
345
741
|
const register = ({ strapi: strapi2 }) => {
|
|
346
742
|
const actionsToRegister = [...ACTIONS.overview, ...ACTIONS.user];
|
|
347
743
|
if (strapi2.plugin("better-auth").contentTypes?.organization)
|
|
@@ -349,14 +745,27 @@ const register = ({ strapi: strapi2 }) => {
|
|
|
349
745
|
strapi2.admin.services.permission.actionProvider.registerMany(
|
|
350
746
|
actionsToRegister
|
|
351
747
|
);
|
|
748
|
+
const config2 = strapi2.config.get(`plugin::${PLUGIN_ID}`, {});
|
|
749
|
+
if (config2.dashApi?.enabled) {
|
|
750
|
+
strapi2.server.routes(contentApiRoutes);
|
|
751
|
+
}
|
|
752
|
+
if (config2.mcp?.enabled) {
|
|
753
|
+
try {
|
|
754
|
+
registerMcpTools(strapi2);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
strapi2.log.warn(
|
|
757
|
+
`[@strapi-community/plugin-better-auth-dashboard] Failed to register MCP tools: ${error instanceof Error ? error.message : String(error)}`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
352
761
|
};
|
|
353
762
|
const adminRoutes = () => ({
|
|
354
763
|
type: "admin",
|
|
355
764
|
routes: [
|
|
356
765
|
{
|
|
357
766
|
method: "GET",
|
|
358
|
-
path: "/auth/:path*",
|
|
359
|
-
handler: "
|
|
767
|
+
path: "/auth/dash/:path*",
|
|
768
|
+
handler: "dash-controller.handleDashRequest",
|
|
360
769
|
config: {
|
|
361
770
|
policies: ["has-permission"],
|
|
362
771
|
prefix: ""
|
|
@@ -364,8 +773,8 @@ const adminRoutes = () => ({
|
|
|
364
773
|
},
|
|
365
774
|
{
|
|
366
775
|
method: "POST",
|
|
367
|
-
path: "/auth/:path*",
|
|
368
|
-
handler: "
|
|
776
|
+
path: "/auth/dash/:path*",
|
|
777
|
+
handler: "dash-controller.handleDashRequest",
|
|
369
778
|
config: {
|
|
370
779
|
policies: ["has-permission"],
|
|
371
780
|
prefix: ""
|
|
@@ -373,8 +782,8 @@ const adminRoutes = () => ({
|
|
|
373
782
|
},
|
|
374
783
|
{
|
|
375
784
|
method: "PUT",
|
|
376
|
-
path: "/auth/:path*",
|
|
377
|
-
handler: "
|
|
785
|
+
path: "/auth/dash/:path*",
|
|
786
|
+
handler: "dash-controller.handleDashRequest",
|
|
378
787
|
config: {
|
|
379
788
|
policies: ["has-permission"],
|
|
380
789
|
prefix: ""
|
|
@@ -382,8 +791,8 @@ const adminRoutes = () => ({
|
|
|
382
791
|
},
|
|
383
792
|
{
|
|
384
793
|
method: "PATCH",
|
|
385
|
-
path: "/auth/:path*",
|
|
386
|
-
handler: "
|
|
794
|
+
path: "/auth/dash/:path*",
|
|
795
|
+
handler: "dash-controller.handleDashRequest",
|
|
387
796
|
config: {
|
|
388
797
|
policies: ["has-permission"],
|
|
389
798
|
prefix: ""
|
|
@@ -391,8 +800,8 @@ const adminRoutes = () => ({
|
|
|
391
800
|
},
|
|
392
801
|
{
|
|
393
802
|
method: "DELETE",
|
|
394
|
-
path: "/auth/:path*",
|
|
395
|
-
handler: "
|
|
803
|
+
path: "/auth/dash/:path*",
|
|
804
|
+
handler: "dash-controller.handleDashRequest",
|
|
396
805
|
config: {
|
|
397
806
|
policies: ["has-permission"],
|
|
398
807
|
prefix: ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi-community/plugin-better-auth-dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Better Auth dashboard for the Strapi admin panel",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -43,12 +43,17 @@
|
|
|
43
43
|
"@playwright/test": "^1.49.0",
|
|
44
44
|
"@strapi/sdk-plugin": "^6.0.0",
|
|
45
45
|
"@strapi/strapi": "5.60.0",
|
|
46
|
+
"@strapi/types": "5.60.0",
|
|
46
47
|
"@strapi/typescript-utils": "5.60.0",
|
|
48
|
+
"@strapi/utils": "5.60.0",
|
|
47
49
|
"@types/koa": "^3.0.1",
|
|
48
50
|
"@types/node": "^22",
|
|
49
51
|
"@types/react": "^18.0.0",
|
|
50
52
|
"@types/styled-components": "^5.1.36",
|
|
53
|
+
"@types/supertest": "^6.0.0",
|
|
54
|
+
"supertest": "^7.0.0",
|
|
51
55
|
"typescript": "^5.9.3",
|
|
56
|
+
"vitest": "^4.1.0",
|
|
52
57
|
"@strapi-community/dev-utils": "0.0.1"
|
|
53
58
|
},
|
|
54
59
|
"peerDependencies": {
|
|
@@ -81,6 +86,7 @@
|
|
|
81
86
|
"lint:ts:server": "cd server && tsc --noEmit",
|
|
82
87
|
"lint:ts:admin": "cd admin && tsc --noEmit",
|
|
83
88
|
"lint": "biome check --fix",
|
|
89
|
+
"test:integration": "vitest run",
|
|
84
90
|
"test:e2e": "playwright test"
|
|
85
91
|
}
|
|
86
92
|
}
|