strapi-plugin-oidc 1.3.1 → 1.4.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.
@@ -7,7 +7,7 @@ const react = require("react");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const icons = require("@strapi/icons");
9
9
  const reactIntl = require("react-intl");
10
- const index = require("./index-Cse9ex24.js");
10
+ const index = require("./index-CZDixCh4.js");
11
11
  const styled = require("styled-components");
12
12
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
13
13
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
@@ -56,13 +56,17 @@ function Whitelist({
56
56
  useWhitelist,
57
57
  loading,
58
58
  onSave,
59
- onDelete
59
+ onDelete,
60
+ onDeleteAll,
61
+ onImport,
62
+ onExport
60
63
  }) {
61
64
  const [email, setEmail] = react.useState("");
62
65
  const [selectedRoles, setSelectedRoles] = react.useState([]);
63
66
  const [page, setPage] = react.useState(1);
64
67
  const { formatMessage } = reactIntl.useIntl();
65
68
  const { toggleNotification } = admin.useNotification();
69
+ const fileInputRef = react.useRef(null);
66
70
  const PAGE_SIZE = 10;
67
71
  const pageCount = Math.ceil(users.length / PAGE_SIZE) || 1;
68
72
  const paginatedUsers = users.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
@@ -83,9 +87,93 @@ function Whitelist({
83
87
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
84
88
  return emailRegex.test(email);
85
89
  }, [email]);
90
+ const handleImport = react.useCallback(
91
+ async (e) => {
92
+ const file = e.target.files?.[0];
93
+ if (!fileInputRef.current) return;
94
+ fileInputRef.current.value = "";
95
+ if (!file) return;
96
+ try {
97
+ const text = await file.text();
98
+ const parsed = JSON.parse(text);
99
+ if (!Array.isArray(parsed)) throw new Error();
100
+ const entries = parsed.filter((item) => item?.email).map((item) => ({
101
+ email: String(item.email),
102
+ roles: Array.isArray(item.roles) ? item.roles : []
103
+ }));
104
+ const count = await onImport(entries);
105
+ if (count === 0) {
106
+ toggleNotification({
107
+ type: "info",
108
+ message: formatMessage(index.getTrad("whitelist.import.none"))
109
+ });
110
+ } else {
111
+ toggleNotification({
112
+ type: "success",
113
+ message: formatMessage(index.getTrad("whitelist.import.success"), { count })
114
+ });
115
+ }
116
+ } catch {
117
+ toggleNotification({
118
+ type: "warning",
119
+ message: formatMessage(index.getTrad("whitelist.import.error"))
120
+ });
121
+ }
122
+ },
123
+ [onImport, formatMessage, toggleNotification]
124
+ );
86
125
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
87
126
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", variant: "omega", textColor: "neutral600", marginBottom: 4, children: formatMessage(index.getTrad("whitelist.description")) }),
88
127
  useWhitelist && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
128
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, children: [
129
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage(index.getTrad("whitelist.count"), { count: users.length }) }),
130
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
131
+ /* @__PURE__ */ jsxRuntime.jsx(
132
+ designSystem.Button,
133
+ {
134
+ size: "S",
135
+ variant: "tertiary",
136
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}),
137
+ onClick: onExport,
138
+ disabled: users.length === 0,
139
+ children: formatMessage(index.getTrad("whitelist.export"))
140
+ }
141
+ ),
142
+ /* @__PURE__ */ jsxRuntime.jsx(
143
+ designSystem.Button,
144
+ {
145
+ size: "S",
146
+ variant: "tertiary",
147
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Upload, {}),
148
+ onClick: () => fileInputRef.current?.click(),
149
+ children: formatMessage(index.getTrad("whitelist.import"))
150
+ }
151
+ ),
152
+ /* @__PURE__ */ jsxRuntime.jsx(
153
+ "input",
154
+ {
155
+ ref: fileInputRef,
156
+ type: "file",
157
+ accept: ".json,application/json",
158
+ style: { display: "none" },
159
+ onChange: handleImport
160
+ }
161
+ ),
162
+ users.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Root, { children: [
163
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { size: "S", variant: "danger-light", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}), children: formatMessage(index.getTrad("whitelist.delete.all.label")) }) }),
164
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
165
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: formatMessage(index.getTrad("whitelist.delete.all.title")) }),
166
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { icon: /* @__PURE__ */ jsxRuntime.jsx(icons.WarningCircle, { fill: "danger600" }), children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", textAlign: "center", children: formatMessage(index.getTrad("whitelist.delete.all.description"), {
167
+ count: users.length
168
+ }) }) }) }),
169
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
170
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { fullWidth: true, variant: "tertiary", children: formatMessage(index.getTrad("page.cancel")) }) }),
171
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Action, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { fullWidth: true, variant: "danger", onClick: onDeleteAll, children: formatMessage(index.getTrad("whitelist.delete.all.label")) }) })
172
+ ] })
173
+ ] })
174
+ ] })
175
+ ] })
176
+ ] }),
89
177
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, marginTop: 5, marginBottom: 5, alignItems: "flex-start", children: [
90
178
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(
91
179
  designSystem.Field.Input,
@@ -137,17 +225,22 @@ function Whitelist({
137
225
  return r ? r.name : roleId;
138
226
  }).join(", ");
139
227
  let userRolesNames = getRoleNames(user.roles || []);
228
+ let isDefault = false;
140
229
  if (!userRolesNames) {
141
230
  const defaultRolesIds = oidcRoles.reduce((acc, oidc) => {
142
231
  if (oidc.role) acc.push(...oidc.role);
143
232
  return acc;
144
233
  }, []);
145
234
  userRolesNames = getRoleNames(defaultRolesIds);
235
+ isDefault = Boolean(userRolesNames);
146
236
  }
147
237
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
148
238
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: index$1 + 1 + (page - 1) * PAGE_SIZE }),
149
239
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: user.email }),
150
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: userRolesNames || "-" }),
240
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: userRolesNames ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
241
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: userRolesNames }),
242
+ isDefault && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "(Default)" })
243
+ ] }) : "-" }),
151
244
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(LocalizedDate, { date: user.createdAt }) }),
152
245
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { paddingRight: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
153
246
  designSystem.Flex,
@@ -351,7 +444,7 @@ function CustomSwitch({ checked, onChange, label, disabled }) {
351
444
  ] });
352
445
  }
353
446
  function useOidcSettings() {
354
- const { get, put } = admin.useFetchClient();
447
+ const { get, put, post } = admin.useFetchClient();
355
448
  const [loading, setLoading] = react.useState(false);
356
449
  const [showSuccess, setSuccess] = react.useState(false);
357
450
  const [showError, setError] = react.useState(false);
@@ -401,6 +494,31 @@ function useOidcSettings() {
401
494
  setEnforceOIDC(false);
402
495
  }
403
496
  };
497
+ const onDeleteAll = () => {
498
+ setUsers([]);
499
+ if (useWhitelist) setEnforceOIDC(false);
500
+ };
501
+ const onImport = async (entries) => {
502
+ const response = await post("/strapi-plugin-oidc/whitelist/import", { users: entries });
503
+ const refreshed = await get("/strapi-plugin-oidc/whitelist");
504
+ setUsers(refreshed.data.whitelistUsers);
505
+ setInitialUsers(JSON.parse(JSON.stringify(refreshed.data.whitelistUsers)));
506
+ return response.data.importedCount;
507
+ };
508
+ const onExport = () => {
509
+ const roleMap = new Map(roles.map((r) => [String(r.id), r.name]));
510
+ const data = users.map(({ email, roles: userRoles }) => ({
511
+ email,
512
+ roles: (userRoles || []).map((id) => roleMap.get(String(id)) ?? id)
513
+ }));
514
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
515
+ const url = URL.createObjectURL(blob);
516
+ const a = document.createElement("a");
517
+ a.href = url;
518
+ a.download = "whitelist.json";
519
+ a.click();
520
+ URL.revokeObjectURL(url);
521
+ };
404
522
  const onToggleWhitelist = (e) => {
405
523
  const checked = e.target.checked;
406
524
  setUseWhitelist(checked);
@@ -472,6 +590,9 @@ function useOidcSettings() {
472
590
  onChangeRole,
473
591
  onRegisterWhitelist,
474
592
  onDeleteWhitelist,
593
+ onDeleteAll,
594
+ onImport,
595
+ onExport,
475
596
  onToggleWhitelist,
476
597
  onToggleEnforce,
477
598
  onSaveAll
@@ -481,6 +602,7 @@ function useOidcSettings() {
481
602
  function HomePage$1() {
482
603
  const { formatMessage } = reactIntl.useIntl();
483
604
  const { state, actions } = useOidcSettings();
605
+ const blocker = reactRouterDom.useBlocker(state.isDirty);
484
606
  return /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Protect, { permissions: [{ action: "plugin::strapi-plugin-oidc.read", subject: null }], children: [
485
607
  /* @__PURE__ */ jsxRuntime.jsx(
486
608
  admin.Layouts.Header,
@@ -525,7 +647,10 @@ function HomePage$1() {
525
647
  oidcRoles: state.oidcRoles,
526
648
  useWhitelist: state.useWhitelist,
527
649
  onSave: actions.onRegisterWhitelist,
528
- onDelete: actions.onDeleteWhitelist
650
+ onDelete: actions.onDeleteWhitelist,
651
+ onDeleteAll: actions.onDeleteAll,
652
+ onImport: actions.onImport,
653
+ onExport: actions.onExport
529
654
  }
530
655
  )
531
656
  ] }),
@@ -564,6 +689,14 @@ function HomePage$1() {
564
689
  children: formatMessage(index.getTrad("page.save"))
565
690
  }
566
691
  ) })
692
+ ] }) }),
693
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: blocker.state === "blocked", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
694
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: formatMessage(index.getTrad("unsaved.title")) }),
695
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: formatMessage(index.getTrad("unsaved.description")) }),
696
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
697
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: () => blocker.reset?.(), children: formatMessage(index.getTrad("unsaved.cancel")) }) }),
698
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Action, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger", onClick: () => blocker.proceed?.(), children: formatMessage(index.getTrad("unsaved.confirm")) }) })
699
+ ] })
567
700
  ] }) })
568
701
  ] });
569
702
  }
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const index = require("./index-Cse9ex24.js");
3
+ const index = require("./index-CZDixCh4.js");
4
4
  exports.default = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "./index-D1ypRUlq.mjs";
1
+ import { i } from "./index-8hB6LKml.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -95,6 +95,21 @@ async function bootstrap({ strapi: strapi2 }) {
95
95
  }
96
96
  ];
97
97
  await strapi2.admin.services.permission.actionProvider.registerMany(actions);
98
+ const enforceOIDCConfig = getEnforceOIDCConfig(strapi2);
99
+ if (enforceOIDCConfig !== null) {
100
+ try {
101
+ const whitelistService2 = strapi2.plugin("strapi-plugin-oidc").service("whitelist");
102
+ const settings = await whitelistService2.getSettings();
103
+ if (settings.enforceOIDC !== enforceOIDCConfig) {
104
+ await whitelistService2.setSettings({ ...settings, enforceOIDC: enforceOIDCConfig });
105
+ strapi2.log.info(
106
+ `[strapi-plugin-oidc] OIDC_ENFORCE=${enforceOIDCConfig} written to database settings`
107
+ );
108
+ }
109
+ } catch (err) {
110
+ strapi2.log.error("[strapi-plugin-oidc] Failed to sync OIDC_ENFORCE to database:", err);
111
+ }
112
+ }
98
113
  try {
99
114
  const oidcRoleCount = await strapi2.query("plugin::strapi-plugin-oidc.roles").count({
100
115
  where: { oauth_type: "4" }
@@ -491,6 +506,48 @@ async function removeEmail(ctx) {
491
506
  await whitelistService2.removeUser(id);
492
507
  ctx.body = {};
493
508
  }
509
+ async function deleteAll(ctx) {
510
+ await strapi.db.query("plugin::strapi-plugin-oidc.whitelists").deleteMany({});
511
+ ctx.body = {};
512
+ }
513
+ async function importUsers(ctx) {
514
+ const { users } = ctx.request.body;
515
+ if (!Array.isArray(users)) {
516
+ ctx.status = 400;
517
+ ctx.body = { error: "Expected { users: [{email, roles}] }" };
518
+ return;
519
+ }
520
+ const allRoles = await strapi.query("admin::role").findMany({});
521
+ const roleNameToId = new Map(allRoles.map((r) => [r.name, String(r.id)]));
522
+ const resolveRole = (nameOrId) => roleNameToId.get(nameOrId) ?? nameOrId;
523
+ const normalized = users.filter((u) => u?.email).map((u) => ({
524
+ email: String(u.email).trim().toLowerCase(),
525
+ roles: (Array.isArray(u.roles) ? u.roles : []).map(resolveRole)
526
+ }));
527
+ const seen = /* @__PURE__ */ new Set();
528
+ const deduped = normalized.filter((u) => {
529
+ if (seen.has(u.email)) return false;
530
+ seen.add(u.email);
531
+ return true;
532
+ });
533
+ const strapiUsers = await strapi.query("admin::user").findMany({
534
+ where: { email: { $in: deduped.map((u) => u.email) } },
535
+ populate: ["roles"]
536
+ });
537
+ const strapiUserMap = new Map(strapiUsers.map((u) => [u.email, u]));
538
+ const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
539
+ const existing = await whitelistService2.getUsers();
540
+ const existingEmails = new Set(existing.map((u) => u.email));
541
+ let importedCount = 0;
542
+ for (const user of deduped) {
543
+ if (existingEmails.has(user.email)) continue;
544
+ const strapiUser = strapiUserMap.get(user.email);
545
+ const finalRoles = strapiUser?.roles?.length ? strapiUser.roles.map((r) => String(r.id)) : user.roles;
546
+ await whitelistService2.registerUser(user.email, finalRoles);
547
+ importedCount++;
548
+ }
549
+ ctx.body = { importedCount };
550
+ }
494
551
  async function syncUsers(ctx) {
495
552
  let { users } = ctx.request.body;
496
553
  users = users.map((u) => ({ ...u, email: String(u.email).toLowerCase() }));
@@ -532,7 +589,9 @@ const whitelist = {
532
589
  publicSettings,
533
590
  register,
534
591
  removeEmail,
535
- syncUsers
592
+ deleteAll,
593
+ syncUsers,
594
+ importUsers
536
595
  };
537
596
  const controllers = {
538
597
  oidc,
@@ -556,134 +615,133 @@ const rateLimitMiddleware = async (ctx, next) => {
556
615
  rateLimitMap.set(ip, requestStamps);
557
616
  await next();
558
617
  };
559
- const routes = [
560
- {
561
- method: "GET",
562
- path: "/oidc-roles",
563
- handler: "role.find",
564
- config: {
565
- policies: [
566
- "admin::isAuthenticatedAdmin",
567
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.read"] } }
568
- ]
569
- }
570
- },
571
- {
572
- method: "PUT",
573
- path: "/oidc-roles",
574
- handler: "role.update",
575
- config: {
576
- policies: [
577
- "admin::isAuthenticatedAdmin",
578
- {
579
- name: "admin::hasPermissions",
580
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
581
- }
582
- ]
583
- }
584
- },
585
- {
586
- method: "GET",
587
- path: "/oidc",
588
- handler: "oidc.oidcSignIn",
589
- config: {
590
- auth: false,
591
- middlewares: [rateLimitMiddleware]
592
- }
593
- },
594
- {
595
- method: "GET",
596
- path: "/oidc/callback",
597
- handler: "oidc.oidcSignInCallback",
598
- config: {
599
- auth: false,
600
- middlewares: [rateLimitMiddleware]
601
- }
602
- },
603
- {
604
- method: "GET",
605
- path: "/logout",
606
- handler: "oidc.logout",
607
- config: {
608
- auth: false
609
- }
610
- },
611
- {
612
- method: "GET",
613
- path: "/whitelist",
614
- handler: "whitelist.info",
615
- config: {
616
- policies: [
617
- "admin::isAuthenticatedAdmin",
618
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.read"] } }
619
- ]
620
- }
621
- },
622
- {
623
- method: "PUT",
624
- path: "/whitelist/settings",
625
- handler: "whitelist.updateSettings",
626
- config: {
627
- policies: [
628
- "admin::isAuthenticatedAdmin",
629
- {
630
- name: "admin::hasPermissions",
631
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
632
- }
633
- ]
634
- }
635
- },
636
- {
637
- method: "GET",
638
- path: "/settings/public",
639
- handler: "whitelist.publicSettings",
640
- config: {
641
- auth: false
642
- }
643
- },
644
- {
645
- method: "PUT",
646
- path: "/whitelist/sync",
647
- handler: "whitelist.syncUsers",
648
- config: {
649
- policies: [
650
- "admin::isAuthenticatedAdmin",
651
- {
652
- name: "admin::hasPermissions",
653
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
654
- }
655
- ]
656
- }
657
- },
658
- {
659
- method: "POST",
660
- path: "/whitelist",
661
- handler: "whitelist.register",
662
- config: {
663
- policies: [
664
- "admin::isAuthenticatedAdmin",
665
- {
666
- name: "admin::hasPermissions",
667
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
668
- }
669
- ]
618
+ const adminPolicies = (action) => ({
619
+ policies: [
620
+ "admin::isAuthenticatedAdmin",
621
+ {
622
+ name: "admin::hasPermissions",
623
+ config: { actions: [`plugin::strapi-plugin-oidc.${action}`] }
670
624
  }
625
+ ]
626
+ });
627
+ const routes = {
628
+ admin: {
629
+ type: "admin",
630
+ routes: [
631
+ {
632
+ method: "GET",
633
+ path: "/oidc-roles",
634
+ handler: "role.find",
635
+ config: adminPolicies("read")
636
+ },
637
+ {
638
+ method: "PUT",
639
+ path: "/oidc-roles",
640
+ handler: "role.update",
641
+ config: adminPolicies("update")
642
+ },
643
+ {
644
+ method: "GET",
645
+ path: "/oidc",
646
+ handler: "oidc.oidcSignIn",
647
+ config: { auth: false, middlewares: [rateLimitMiddleware] }
648
+ },
649
+ {
650
+ method: "GET",
651
+ path: "/oidc/callback",
652
+ handler: "oidc.oidcSignInCallback",
653
+ config: { auth: false, middlewares: [rateLimitMiddleware] }
654
+ },
655
+ {
656
+ method: "GET",
657
+ path: "/logout",
658
+ handler: "oidc.logout",
659
+ config: { auth: false }
660
+ },
661
+ {
662
+ method: "GET",
663
+ path: "/whitelist",
664
+ handler: "whitelist.info",
665
+ config: adminPolicies("read")
666
+ },
667
+ {
668
+ method: "PUT",
669
+ path: "/whitelist/settings",
670
+ handler: "whitelist.updateSettings",
671
+ config: adminPolicies("update")
672
+ },
673
+ {
674
+ method: "GET",
675
+ path: "/settings/public",
676
+ handler: "whitelist.publicSettings",
677
+ config: { auth: false }
678
+ },
679
+ {
680
+ method: "PUT",
681
+ path: "/whitelist/sync",
682
+ handler: "whitelist.syncUsers",
683
+ config: adminPolicies("update")
684
+ },
685
+ {
686
+ method: "POST",
687
+ path: "/whitelist/import",
688
+ handler: "whitelist.importUsers",
689
+ config: adminPolicies("update")
690
+ },
691
+ {
692
+ method: "POST",
693
+ path: "/whitelist",
694
+ handler: "whitelist.register",
695
+ config: adminPolicies("update")
696
+ },
697
+ {
698
+ method: "DELETE",
699
+ path: "/whitelist/:id",
700
+ handler: "whitelist.removeEmail",
701
+ config: adminPolicies("update")
702
+ },
703
+ {
704
+ method: "DELETE",
705
+ path: "/whitelist",
706
+ handler: "whitelist.deleteAll",
707
+ config: adminPolicies("update")
708
+ }
709
+ ]
671
710
  },
672
- {
673
- method: "DELETE",
674
- path: "/whitelist/:id",
675
- handler: "whitelist.removeEmail",
676
- config: {
677
- policies: [
678
- "admin::isAuthenticatedAdmin",
679
- {
680
- name: "admin::hasPermissions",
681
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
682
- }
683
- ]
684
- }
711
+ // API-token-authenticated routes for programmatic whitelist management.
712
+ // Accessible at /strapi-plugin-oidc/... using a Strapi API token
713
+ // (full-access or custom) in the Authorization: Bearer <token> header.
714
+ "content-api": {
715
+ type: "content-api",
716
+ routes: [
717
+ {
718
+ method: "GET",
719
+ path: "/whitelist",
720
+ handler: "whitelist.info"
721
+ },
722
+ {
723
+ method: "POST",
724
+ path: "/whitelist",
725
+ handler: "whitelist.register"
726
+ },
727
+ {
728
+ method: "POST",
729
+ path: "/whitelist/import",
730
+ handler: "whitelist.importUsers"
731
+ },
732
+ {
733
+ method: "DELETE",
734
+ path: "/whitelist/:id",
735
+ handler: "whitelist.removeEmail"
736
+ },
737
+ {
738
+ method: "DELETE",
739
+ path: "/whitelist",
740
+ handler: "whitelist.deleteAll"
741
+ }
742
+ ]
685
743
  }
686
- ];
744
+ };
687
745
  const policies = {};
688
746
  function renderHtmlTemplate(title, content) {
689
747
  return `