strapi-plugin-magic-sessionmanager 4.4.7 → 4.5.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.
@@ -6,8 +6,8 @@ const admin = require("@strapi/strapi/admin");
6
6
  const styled = require("styled-components");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const icons = require("@strapi/icons");
9
- const index = require("./index-BEh2DizI.js");
10
- const useLicense = require("./useLicense-DFdVp_qI.js");
9
+ const index = require("./index-CKrO7KSQ.js");
10
+ const useLicense = require("./useLicense-DHAFqFSZ.js");
11
11
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
12
12
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
13
13
  const theme = {
@@ -4,8 +4,8 @@ import { useFetchClient } from "@strapi/strapi/admin";
4
4
  import styled, { css, keyframes } from "styled-components";
5
5
  import { Loader, Typography, Box, Flex, Badge } from "@strapi/design-system";
6
6
  import { ChartBubble, Crown, User, Clock, Monitor } from "@strapi/icons";
7
- import { a as pluginId } from "./index-B0wQeSSu.mjs";
8
- import { u as useLicense } from "./useLicense-RxDUbCoU.mjs";
7
+ import { a as pluginId } from "./index-DuVZXuJh.mjs";
8
+ import { u as useLicense } from "./useLicense-Xzo6nyh3.mjs";
9
9
  const theme = {
10
10
  shadows: {
11
11
  sm: "0 1px 3px 0 rgba(0, 0, 0, 0.1)",
@@ -5,10 +5,10 @@ const react = require("react");
5
5
  const reactIntl = require("react-intl");
6
6
  const admin = require("@strapi/strapi/admin");
7
7
  const styled = require("styled-components");
8
- const index = require("./index-BEh2DizI.js");
8
+ const index = require("./index-CKrO7KSQ.js");
9
9
  const designSystem = require("@strapi/design-system");
10
10
  const icons = require("@strapi/icons");
11
- const useLicense = require("./useLicense-DFdVp_qI.js");
11
+ const useLicense = require("./useLicense-DHAFqFSZ.js");
12
12
  const StyledButtons = require("./StyledButtons-DDuxnYz8.js");
13
13
  const reactRouterDom = require("react-router-dom");
14
14
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -2025,7 +2025,11 @@ const LicenseGuard = ({ children }) => {
2025
2025
  }
2026
2026
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
2027
2027
  };
2028
+ const pluginId = "magic-sessionmanager";
2029
+ const pluginPermissions = {
2030
+ access: [{ action: `plugin::${pluginId}.access`, subject: null }]
2031
+ };
2028
2032
  const App = () => {
2029
- return /* @__PURE__ */ jsxRuntime.jsx(LicenseGuard, { children: /* @__PURE__ */ jsxRuntime.jsx(HomePage, {}) });
2033
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Protect, { permissions: pluginPermissions.access, children: /* @__PURE__ */ jsxRuntime.jsx(LicenseGuard, { children: /* @__PURE__ */ jsxRuntime.jsx(HomePage, {}) }) });
2030
2034
  };
2031
2035
  exports.default = App;
@@ -1,12 +1,12 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
3
  import { useIntl } from "react-intl";
4
- import { useFetchClient, useNotification } from "@strapi/strapi/admin";
4
+ import { useFetchClient, useNotification, Page } from "@strapi/strapi/admin";
5
5
  import styled, { css, keyframes } from "styled-components";
6
- import { p as parseUserAgent, a as pluginId, g as getTranslation } from "./index-B0wQeSSu.mjs";
6
+ import { p as parseUserAgent, a as pluginId$1, g as getTranslation } from "./index-DuVZXuJh.mjs";
7
7
  import { Modal, Flex, Box, Typography, Divider, Button, Loader, SingleSelect, SingleSelectOption, Thead, Tr, Th, Tbody, Td, Table, TextInput } from "@strapi/design-system";
8
8
  import { Check, Information, Monitor, Server, Clock, Cross, Earth, Shield, Crown, Phone, Download, User, Eye, Trash, Search, Key } from "@strapi/icons";
9
- import { u as useLicense } from "./useLicense-RxDUbCoU.mjs";
9
+ import { u as useLicense } from "./useLicense-Xzo6nyh3.mjs";
10
10
  import { S as ShowHideButton, T as TertiaryButton, D as DangerButton, I as IconButtonPrimary, a as IconButtonWarning, b as IconButtonDanger } from "./StyledButtons-Cz8oYhmc.mjs";
11
11
  import { useNavigate } from "react-router-dom";
12
12
  const theme = {
@@ -145,7 +145,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
145
145
  const fetchGeolocationData = async () => {
146
146
  setGeoLoading(true);
147
147
  try {
148
- const { data } = await get(`/${pluginId}/geolocation/${session.ipAddress}`);
148
+ const { data } = await get(`/${pluginId$1}/geolocation/${session.ipAddress}`);
149
149
  setGeoData(data.data);
150
150
  } catch (err) {
151
151
  console.error("[SessionDetailModal] Error fetching geolocation:", err);
@@ -185,7 +185,7 @@ const SessionDetailModal = ({ session, onClose, onSessionTerminated }) => {
185
185
  }
186
186
  setTerminating(true);
187
187
  try {
188
- await post(`/${pluginId}/sessions/${session.id}/terminate`);
188
+ await post(`/${pluginId$1}/sessions/${session.id}/terminate`);
189
189
  toggleNotification({
190
190
  type: "success",
191
191
  message: t("notifications.success.terminated", "Session terminated successfully")
@@ -889,7 +889,7 @@ const HomePage = () => {
889
889
  const fetchSessions = async () => {
890
890
  setLoading(true);
891
891
  try {
892
- const { data } = await get(`/${pluginId}/sessions`);
892
+ const { data } = await get(`/${pluginId$1}/sessions`);
893
893
  setSessions(data.data || []);
894
894
  } catch (err) {
895
895
  console.error("[SessionManager] Error fetching sessions:", err);
@@ -902,7 +902,7 @@ const HomePage = () => {
902
902
  return;
903
903
  }
904
904
  try {
905
- await post(`/${pluginId}/sessions/${sessionId}/terminate`);
905
+ await post(`/${pluginId$1}/sessions/${sessionId}/terminate`);
906
906
  fetchSessions();
907
907
  } catch (err) {
908
908
  console.error("[SessionManager] Error terminating session:", err);
@@ -913,7 +913,7 @@ const HomePage = () => {
913
913
  return;
914
914
  }
915
915
  try {
916
- await del(`/${pluginId}/sessions/${sessionId}`);
916
+ await del(`/${pluginId$1}/sessions/${sessionId}`);
917
917
  fetchSessions();
918
918
  toggleNotification({
919
919
  type: "success",
@@ -1645,7 +1645,7 @@ const LicenseGuard = ({ children }) => {
1645
1645
  const checkLicenseStatus = async () => {
1646
1646
  setIsChecking(true);
1647
1647
  try {
1648
- const response = await get(`/${pluginId}/license/status`);
1648
+ const response = await get(`/${pluginId$1}/license/status`);
1649
1649
  if (response.data.valid) {
1650
1650
  setNeedsLicense(false);
1651
1651
  } else {
@@ -1662,7 +1662,7 @@ const LicenseGuard = ({ children }) => {
1662
1662
  e.preventDefault();
1663
1663
  setIsCreating(true);
1664
1664
  try {
1665
- const response = await post(`/${pluginId}/license/auto-create`, {});
1665
+ const response = await post(`/${pluginId$1}/license/auto-create`, {});
1666
1666
  if (response.data && response.data.success) {
1667
1667
  toggleNotification({
1668
1668
  type: "success",
@@ -1696,7 +1696,7 @@ const LicenseGuard = ({ children }) => {
1696
1696
  }
1697
1697
  setIsCreating(true);
1698
1698
  try {
1699
- const response = await post(`/${pluginId}/license/create`, formData);
1699
+ const response = await post(`/${pluginId$1}/license/create`, formData);
1700
1700
  if (response.data && response.data.success) {
1701
1701
  toggleNotification({
1702
1702
  type: "success",
@@ -1729,7 +1729,7 @@ const LicenseGuard = ({ children }) => {
1729
1729
  }
1730
1730
  setIsCreating(true);
1731
1731
  try {
1732
- const response = await post(`/${pluginId}/license/store-key`, {
1732
+ const response = await post(`/${pluginId$1}/license/store-key`, {
1733
1733
  licenseKey: existingLicenseKey.trim(),
1734
1734
  email: existingEmail.trim()
1735
1735
  });
@@ -2021,8 +2021,12 @@ const LicenseGuard = ({ children }) => {
2021
2021
  }
2022
2022
  return /* @__PURE__ */ jsx(Fragment, { children });
2023
2023
  };
2024
+ const pluginId = "magic-sessionmanager";
2025
+ const pluginPermissions = {
2026
+ access: [{ action: `plugin::${pluginId}.access`, subject: null }]
2027
+ };
2024
2028
  const App = () => {
2025
- return /* @__PURE__ */ jsx(LicenseGuard, { children: /* @__PURE__ */ jsx(HomePage, {}) });
2029
+ return /* @__PURE__ */ jsx(Page.Protect, { permissions: pluginPermissions.access, children: /* @__PURE__ */ jsx(LicenseGuard, { children: /* @__PURE__ */ jsx(HomePage, {}) }) });
2026
2030
  };
2027
2031
  export {
2028
2032
  App as default
@@ -6,7 +6,7 @@ const designSystem = require("@strapi/design-system");
6
6
  const admin = require("@strapi/strapi/admin");
7
7
  const icons = require("@strapi/icons");
8
8
  const styled = require("styled-components");
9
- const index = require("./index-BEh2DizI.js");
9
+ const index = require("./index-CKrO7KSQ.js");
10
10
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
11
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
12
12
  const theme = {
@@ -4,7 +4,7 @@ import { Loader, Box, Alert, Flex, Typography, Button, Badge, Accordion } from "
4
4
  import { useFetchClient, useNotification } from "@strapi/strapi/admin";
5
5
  import { ArrowClockwise, Duplicate, Download, User, Shield, Sparkle, ChartBubble } from "@strapi/icons";
6
6
  import styled, { css, keyframes } from "styled-components";
7
- import { a as pluginId } from "./index-B0wQeSSu.mjs";
7
+ import { a as pluginId } from "./index-DuVZXuJh.mjs";
8
8
  const theme = {
9
9
  borderRadius: { lg: "12px" }
10
10
  };
@@ -6,7 +6,7 @@ const reactIntl = require("react-intl");
6
6
  const designSystem = require("@strapi/design-system");
7
7
  const icons = require("@strapi/icons");
8
8
  const admin = require("@strapi/strapi/admin");
9
- const index = require("./index-BEh2DizI.js");
9
+ const index = require("./index-CKrO7KSQ.js");
10
10
  const OnlineUsersWidget = () => {
11
11
  const { formatMessage } = reactIntl.useIntl();
12
12
  const { get } = admin.useFetchClient();
@@ -4,7 +4,7 @@ import { useIntl } from "react-intl";
4
4
  import { Box, Typography, Flex, Grid } from "@strapi/design-system";
5
5
  import { Check, Cross, Clock, User } from "@strapi/icons";
6
6
  import { useFetchClient } from "@strapi/strapi/admin";
7
- import { g as getTranslation } from "./index-B0wQeSSu.mjs";
7
+ import { g as getTranslation } from "./index-DuVZXuJh.mjs";
8
8
  const OnlineUsersWidget = () => {
9
9
  const { formatMessage } = useIntl();
10
10
  const { get } = useFetchClient();
@@ -3,10 +3,10 @@ import { useState, useEffect } from "react";
3
3
  import { useIntl } from "react-intl";
4
4
  import { Flex, Loader, Typography, Button, Box, Badge, Accordion, Grid, SingleSelect, SingleSelectOption, Divider, Alert, TextInput, Toggle, NumberInput, Checkbox, Tabs } from "@strapi/design-system";
5
5
  import { useFetchClient, useNotification } from "@strapi/strapi/admin";
6
- import { Check, Information, Cog, Trash, Shield, Code, Duplicate, Mail } from "@strapi/icons";
6
+ import { Check, Information, Cog, Trash, Shield, Code, Duplicate, Clock, Mail } from "@strapi/icons";
7
7
  import styled, { css, keyframes } from "styled-components";
8
- import { a as pluginId, g as getTranslation } from "./index-B0wQeSSu.mjs";
9
- import { u as useLicense } from "./useLicense-RxDUbCoU.mjs";
8
+ import { a as pluginId, g as getTranslation } from "./index-DuVZXuJh.mjs";
9
+ import { u as useLicense } from "./useLicense-Xzo6nyh3.mjs";
10
10
  import { D as DangerButton, S as ShowHideButton, G as GradientButton, C as CopyButton, T as TertiaryButton, c as SecondaryButton } from "./StyledButtons-Cz8oYhmc.mjs";
11
11
  const theme = {
12
12
  borderRadius: { md: "8px", lg: "12px" }
@@ -345,6 +345,9 @@ const SettingsPage = () => {
345
345
  cleanupInterval: 30,
346
346
  lastSeenRateLimit: 30,
347
347
  retentionDays: 90,
348
+ maxSessionAgeDays: 30,
349
+ strictSessionEnforcement: false,
350
+ trustedProxies: false,
348
351
  enableGeolocation: true,
349
352
  enableSecurityScoring: true,
350
353
  blockSuspiciousSessions: false,
@@ -783,6 +786,119 @@ const SettingsPage = () => {
783
786
  ] })
784
787
  }
785
788
  ),
789
+ /* @__PURE__ */ jsx(
790
+ Box,
791
+ {
792
+ background: "neutral0",
793
+ padding: 6,
794
+ style: {
795
+ borderRadius: theme.borderRadius.lg,
796
+ marginBottom: "32px",
797
+ border: `2px solid ${settings.strictSessionEnforcement ? "rgba(220, 38, 38, 0.25)" : "rgba(2, 132, 199, 0.12)"}`,
798
+ background: settings.strictSessionEnforcement ? "rgba(220, 38, 38, 0.04)" : "rgba(2, 132, 199, 0.04)"
799
+ },
800
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 4, children: [
801
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 3, children: [
802
+ /* @__PURE__ */ jsx(Shield, { style: { width: 24, height: 24, color: settings.strictSessionEnforcement ? "var(--colors-danger600, #DC2626)" : "var(--colors-primary600, #0284C7)" } }),
803
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "bold", children: t("settings.security.enforcement.title", "Session Enforcement Policy") }),
804
+ /* @__PURE__ */ jsx(Badge, { backgroundColor: settings.strictSessionEnforcement ? "danger100" : "neutral100", textColor: settings.strictSessionEnforcement ? "danger700" : "neutral700", children: settings.strictSessionEnforcement ? t("settings.security.enforcement.badgeStrict", "STRICT") : t("settings.security.enforcement.badgeRelaxed", "RELAXED") })
805
+ ] }),
806
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", style: { lineHeight: 1.6 }, children: t("settings.security.enforcement.description", "Controls how aggressively JWT tokens are tied to an active session record. Strict mode rejects any token without a matching session, which is more secure but breaks tokens issued before this plugin was installed.") }),
807
+ /* @__PURE__ */ jsxs(Grid.Root, { gap: 6, children: [
808
+ /* @__PURE__ */ jsx(Grid.Item, { col: 12, s: 12, children: /* @__PURE__ */ jsx(
809
+ ToggleCard,
810
+ {
811
+ $active: settings.strictSessionEnforcement,
812
+ onClick: () => handleChange("strictSessionEnforcement", !settings.strictSessionEnforcement),
813
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "row", gap: 4, style: { width: "100%" }, alignItems: "center", children: [
814
+ /* @__PURE__ */ jsx(GreenToggle, { $isActive: settings.strictSessionEnforcement, children: /* @__PURE__ */ jsx(
815
+ Toggle,
816
+ {
817
+ checked: settings.strictSessionEnforcement,
818
+ onChange: () => handleChange("strictSessionEnforcement", !settings.strictSessionEnforcement)
819
+ }
820
+ ) }),
821
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 1, style: { flex: 1 }, children: [
822
+ /* @__PURE__ */ jsx(
823
+ Typography,
824
+ {
825
+ variant: "delta",
826
+ fontWeight: "bold",
827
+ textColor: settings.strictSessionEnforcement ? "success700" : "neutral800",
828
+ style: { fontSize: "15px" },
829
+ children: t("settings.security.enforcement.strict.title", "Strict Session Enforcement")
830
+ }
831
+ ),
832
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px", lineHeight: "1.5" }, children: t("settings.security.enforcement.strict.description", "Reject every authenticated request that does not have a matching session record. Recommended for production.") })
833
+ ] })
834
+ ] })
835
+ }
836
+ ) }),
837
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxs(Box, { children: [
838
+ /* @__PURE__ */ jsxs(Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: [
839
+ /* @__PURE__ */ jsx(Clock, { style: { width: 14, height: 14, verticalAlign: "middle", marginRight: 6 } }),
840
+ t("settings.security.enforcement.maxAge.title", "Max Session Age")
841
+ ] }),
842
+ /* @__PURE__ */ jsxs(
843
+ SingleSelect,
844
+ {
845
+ value: String(settings.maxSessionAgeDays),
846
+ onChange: (value) => handleChange("maxSessionAgeDays", parseInt(value)),
847
+ children: [
848
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "1", children: t("settings.security.enforcement.maxAge.1day", "1 day (Very Strict)") }),
849
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "7", children: t("settings.security.enforcement.maxAge.7days", "7 days") }),
850
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "14", children: t("settings.security.enforcement.maxAge.14days", "14 days") }),
851
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "30", children: t("settings.security.enforcement.maxAge.30days", "30 days (Recommended)") }),
852
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "60", children: t("settings.security.enforcement.maxAge.60days", "60 days") }),
853
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "90", children: t("settings.security.enforcement.maxAge.90days", "90 days") }),
854
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "180", children: t("settings.security.enforcement.maxAge.180days", "180 days") }),
855
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "365", children: t("settings.security.enforcement.maxAge.365days", "1 year") })
856
+ ]
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "11px", marginTop: "8px" }, children: t("settings.security.enforcement.maxAge.hint", "Sessions older than {days} days are automatically terminated, even if still active", { days: settings.maxSessionAgeDays }) })
860
+ ] }) }),
861
+ /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsx(
862
+ ToggleCard,
863
+ {
864
+ $active: settings.trustedProxies,
865
+ onClick: () => handleChange("trustedProxies", !settings.trustedProxies),
866
+ children: /* @__PURE__ */ jsxs(Flex, { direction: "row", gap: 4, style: { width: "100%" }, alignItems: "center", children: [
867
+ /* @__PURE__ */ jsx(GreenToggle, { $isActive: settings.trustedProxies, children: /* @__PURE__ */ jsx(
868
+ Toggle,
869
+ {
870
+ checked: settings.trustedProxies,
871
+ onChange: () => handleChange("trustedProxies", !settings.trustedProxies)
872
+ }
873
+ ) }),
874
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 1, style: { flex: 1 }, children: [
875
+ /* @__PURE__ */ jsx(
876
+ Typography,
877
+ {
878
+ variant: "delta",
879
+ fontWeight: "bold",
880
+ textColor: settings.trustedProxies ? "success700" : "neutral800",
881
+ style: { fontSize: "15px" },
882
+ children: t("settings.security.enforcement.trustedProxies.title", "Trust Upstream Proxy")
883
+ }
884
+ ),
885
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px", lineHeight: "1.5" }, children: t("settings.security.enforcement.trustedProxies.description", "Only enable when Strapi sits behind a trusted reverse proxy (nginx, Cloudflare). Otherwise clients can spoof X-Forwarded-For.") })
886
+ ] })
887
+ ] })
888
+ }
889
+ ) })
890
+ ] }),
891
+ !settings.strictSessionEnforcement && /* @__PURE__ */ jsx(
892
+ Alert,
893
+ {
894
+ variant: "warning",
895
+ title: t("settings.security.enforcement.warning.title", "Running in Relaxed Mode"),
896
+ children: t("settings.security.enforcement.warning.body", "Tokens without a matching session record are allowed through. Manual session termination is still enforced via the token hash, but we strongly recommend enabling strict mode in production.")
897
+ }
898
+ )
899
+ ] })
900
+ }
901
+ ),
786
902
  /* @__PURE__ */ jsx(Box, { background: "neutral100", padding: 5, style: { borderRadius: theme.borderRadius.md, marginBottom: "32px" }, children: /* @__PURE__ */ jsxs(Grid.Root, { gap: 4, children: [
787
903
  /* @__PURE__ */ jsx(Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsx(
788
904
  ToggleCard,
@@ -7,8 +7,8 @@ const designSystem = require("@strapi/design-system");
7
7
  const admin = require("@strapi/strapi/admin");
8
8
  const icons = require("@strapi/icons");
9
9
  const styled = require("styled-components");
10
- const index = require("./index-BEh2DizI.js");
11
- const useLicense = require("./useLicense-DFdVp_qI.js");
10
+ const index = require("./index-CKrO7KSQ.js");
11
+ const useLicense = require("./useLicense-DHAFqFSZ.js");
12
12
  const StyledButtons = require("./StyledButtons-DDuxnYz8.js");
13
13
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
14
14
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
@@ -349,6 +349,9 @@ const SettingsPage = () => {
349
349
  cleanupInterval: 30,
350
350
  lastSeenRateLimit: 30,
351
351
  retentionDays: 90,
352
+ maxSessionAgeDays: 30,
353
+ strictSessionEnforcement: false,
354
+ trustedProxies: false,
352
355
  enableGeolocation: true,
353
356
  enableSecurityScoring: true,
354
357
  blockSuspiciousSessions: false,
@@ -787,6 +790,119 @@ const SettingsPage = () => {
787
790
  ] })
788
791
  }
789
792
  ),
793
+ /* @__PURE__ */ jsxRuntime.jsx(
794
+ designSystem.Box,
795
+ {
796
+ background: "neutral0",
797
+ padding: 6,
798
+ style: {
799
+ borderRadius: theme.borderRadius.lg,
800
+ marginBottom: "32px",
801
+ border: `2px solid ${settings.strictSessionEnforcement ? "rgba(220, 38, 38, 0.25)" : "rgba(2, 132, 199, 0.12)"}`,
802
+ background: settings.strictSessionEnforcement ? "rgba(220, 38, 38, 0.04)" : "rgba(2, 132, 199, 0.04)"
803
+ },
804
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, children: [
805
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 3, children: [
806
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Shield, { style: { width: 24, height: 24, color: settings.strictSessionEnforcement ? "var(--colors-danger600, #DC2626)" : "var(--colors-primary600, #0284C7)" } }),
807
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", children: t("settings.security.enforcement.title", "Session Enforcement Policy") }),
808
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: settings.strictSessionEnforcement ? "danger100" : "neutral100", textColor: settings.strictSessionEnforcement ? "danger700" : "neutral700", children: settings.strictSessionEnforcement ? t("settings.security.enforcement.badgeStrict", "STRICT") : t("settings.security.enforcement.badgeRelaxed", "RELAXED") })
809
+ ] }),
810
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", style: { lineHeight: 1.6 }, children: t("settings.security.enforcement.description", "Controls how aggressively JWT tokens are tied to an active session record. Strict mode rejects any token without a matching session, which is more secure but breaks tokens issued before this plugin was installed.") }),
811
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 6, children: [
812
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 12, s: 12, children: /* @__PURE__ */ jsxRuntime.jsx(
813
+ ToggleCard,
814
+ {
815
+ $active: settings.strictSessionEnforcement,
816
+ onClick: () => handleChange("strictSessionEnforcement", !settings.strictSessionEnforcement),
817
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "row", gap: 4, style: { width: "100%" }, alignItems: "center", children: [
818
+ /* @__PURE__ */ jsxRuntime.jsx(GreenToggle, { $isActive: settings.strictSessionEnforcement, children: /* @__PURE__ */ jsxRuntime.jsx(
819
+ designSystem.Toggle,
820
+ {
821
+ checked: settings.strictSessionEnforcement,
822
+ onChange: () => handleChange("strictSessionEnforcement", !settings.strictSessionEnforcement)
823
+ }
824
+ ) }),
825
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, style: { flex: 1 }, children: [
826
+ /* @__PURE__ */ jsxRuntime.jsx(
827
+ designSystem.Typography,
828
+ {
829
+ variant: "delta",
830
+ fontWeight: "bold",
831
+ textColor: settings.strictSessionEnforcement ? "success700" : "neutral800",
832
+ style: { fontSize: "15px" },
833
+ children: t("settings.security.enforcement.strict.title", "Strict Session Enforcement")
834
+ }
835
+ ),
836
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px", lineHeight: "1.5" }, children: t("settings.security.enforcement.strict.description", "Reject every authenticated request that does not have a matching session record. Recommended for production.") })
837
+ ] })
838
+ ] })
839
+ }
840
+ ) }),
841
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
842
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", fontWeight: "bold", style: { marginBottom: "8px", display: "block" }, children: [
843
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Clock, { style: { width: 14, height: 14, verticalAlign: "middle", marginRight: 6 } }),
844
+ t("settings.security.enforcement.maxAge.title", "Max Session Age")
845
+ ] }),
846
+ /* @__PURE__ */ jsxRuntime.jsxs(
847
+ designSystem.SingleSelect,
848
+ {
849
+ value: String(settings.maxSessionAgeDays),
850
+ onChange: (value) => handleChange("maxSessionAgeDays", parseInt(value)),
851
+ children: [
852
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "1", children: t("settings.security.enforcement.maxAge.1day", "1 day (Very Strict)") }),
853
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "7", children: t("settings.security.enforcement.maxAge.7days", "7 days") }),
854
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "14", children: t("settings.security.enforcement.maxAge.14days", "14 days") }),
855
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "30", children: t("settings.security.enforcement.maxAge.30days", "30 days (Recommended)") }),
856
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "60", children: t("settings.security.enforcement.maxAge.60days", "60 days") }),
857
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "90", children: t("settings.security.enforcement.maxAge.90days", "90 days") }),
858
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "180", children: t("settings.security.enforcement.maxAge.180days", "180 days") }),
859
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "365", children: t("settings.security.enforcement.maxAge.365days", "1 year") })
860
+ ]
861
+ }
862
+ ),
863
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "11px", marginTop: "8px" }, children: t("settings.security.enforcement.maxAge.hint", "Sessions older than {days} days are automatically terminated, even if still active", { days: settings.maxSessionAgeDays }) })
864
+ ] }) }),
865
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsx(
866
+ ToggleCard,
867
+ {
868
+ $active: settings.trustedProxies,
869
+ onClick: () => handleChange("trustedProxies", !settings.trustedProxies),
870
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "row", gap: 4, style: { width: "100%" }, alignItems: "center", children: [
871
+ /* @__PURE__ */ jsxRuntime.jsx(GreenToggle, { $isActive: settings.trustedProxies, children: /* @__PURE__ */ jsxRuntime.jsx(
872
+ designSystem.Toggle,
873
+ {
874
+ checked: settings.trustedProxies,
875
+ onChange: () => handleChange("trustedProxies", !settings.trustedProxies)
876
+ }
877
+ ) }),
878
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, style: { flex: 1 }, children: [
879
+ /* @__PURE__ */ jsxRuntime.jsx(
880
+ designSystem.Typography,
881
+ {
882
+ variant: "delta",
883
+ fontWeight: "bold",
884
+ textColor: settings.trustedProxies ? "success700" : "neutral800",
885
+ style: { fontSize: "15px" },
886
+ children: t("settings.security.enforcement.trustedProxies.title", "Trust Upstream Proxy")
887
+ }
888
+ ),
889
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { fontSize: "12px", lineHeight: "1.5" }, children: t("settings.security.enforcement.trustedProxies.description", "Only enable when Strapi sits behind a trusted reverse proxy (nginx, Cloudflare). Otherwise clients can spoof X-Forwarded-For.") })
890
+ ] })
891
+ ] })
892
+ }
893
+ ) })
894
+ ] }),
895
+ !settings.strictSessionEnforcement && /* @__PURE__ */ jsxRuntime.jsx(
896
+ designSystem.Alert,
897
+ {
898
+ variant: "warning",
899
+ title: t("settings.security.enforcement.warning.title", "Running in Relaxed Mode"),
900
+ children: t("settings.security.enforcement.warning.body", "Tokens without a matching session record are allowed through. Manual session termination is still enforced via the token hash, but we strongly recommend enabling strict mode in production.")
901
+ }
902
+ )
903
+ ] })
904
+ }
905
+ ),
790
906
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral100", padding: 5, style: { borderRadius: theme.borderRadius.md, marginBottom: "32px" }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 4, children: [
791
907
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsx(
792
908
  ToggleCard,
@@ -6,7 +6,7 @@ const admin = require("@strapi/strapi/admin");
6
6
  const styled = require("styled-components");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const icons = require("@strapi/icons");
9
- const index = require("./index-BEh2DizI.js");
9
+ const index = require("./index-CKrO7KSQ.js");
10
10
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
11
  const styled__default = /* @__PURE__ */ _interopDefault(styled);
12
12
  const Container = styled__default.default(designSystem.Box)`
@@ -4,7 +4,7 @@ import { useFetchClient, useNotification } from "@strapi/strapi/admin";
4
4
  import styled from "styled-components";
5
5
  import { Flex, Typography, Box, Badge, Button } from "@strapi/design-system";
6
6
  import { Check, Cross, Sparkle, Lightning, Rocket } from "@strapi/icons";
7
- import { a as pluginId } from "./index-B0wQeSSu.mjs";
7
+ import { a as pluginId } from "./index-DuVZXuJh.mjs";
8
8
  const Container = styled(Box)`
9
9
  padding: 32px;
10
10
  max-width: 1400px;
@@ -436,6 +436,7 @@ const prefixPluginTranslations = (data, pluginId2) => {
436
436
  const name = pluginPkg.strapi.name;
437
437
  const index = {
438
438
  register(app) {
439
+ const pluginPermissions = [{ action: `plugin::${pluginId}.access`, subject: null }];
439
440
  app.addMenuLink({
440
441
  to: `plugins/${pluginId}`,
441
442
  icon: PluginIcon,
@@ -443,7 +444,8 @@ const index = {
443
444
  id: `${pluginId}.plugin.name`,
444
445
  defaultMessage: pluginPkg.strapi.displayName
445
446
  },
446
- Component: () => Promise.resolve().then(() => require("./App-CrdNlRlL.js"))
447
+ Component: () => Promise.resolve().then(() => require("./App-Bir8yK_r.js")),
448
+ permissions: pluginPermissions
447
449
  });
448
450
  app.createSettingSection(
449
451
  {
@@ -459,7 +461,8 @@ const index = {
459
461
  },
460
462
  id: "upgrade",
461
463
  to: `${pluginId}/upgrade`,
462
- Component: () => Promise.resolve().then(() => require("./UpgradePage-B8kz6Cyz.js"))
464
+ Component: () => Promise.resolve().then(() => require("./UpgradePage-KqUN7mDh.js")),
465
+ permissions: pluginPermissions
463
466
  },
464
467
  {
465
468
  intlLabel: {
@@ -468,7 +471,8 @@ const index = {
468
471
  },
469
472
  id: "general",
470
473
  to: `${pluginId}/general`,
471
- Component: () => Promise.resolve().then(() => require("./Settings-Cy-6vah_.js"))
474
+ Component: () => Promise.resolve().then(() => require("./Settings-CO3-iggu.js")),
475
+ permissions: pluginPermissions
472
476
  },
473
477
  {
474
478
  intlLabel: {
@@ -477,7 +481,8 @@ const index = {
477
481
  },
478
482
  id: "analytics",
479
483
  to: `${pluginId}/analytics`,
480
- Component: () => Promise.resolve().then(() => require("./Analytics-mFarhu5A.js"))
484
+ Component: () => Promise.resolve().then(() => require("./Analytics-B7t0WvG7.js")),
485
+ permissions: pluginPermissions
481
486
  },
482
487
  {
483
488
  intlLabel: {
@@ -486,7 +491,8 @@ const index = {
486
491
  },
487
492
  id: "license",
488
493
  to: `${pluginId}/license`,
489
- Component: () => Promise.resolve().then(() => require("./License-BI04KWfw.js"))
494
+ Component: () => Promise.resolve().then(() => require("./License-B56Xklj2.js")),
495
+ permissions: pluginPermissions
490
496
  }
491
497
  ]
492
498
  );
@@ -504,7 +510,7 @@ const index = {
504
510
  defaultMessage: "Online Users"
505
511
  },
506
512
  component: async () => {
507
- const component = await Promise.resolve().then(() => require("./OnlineUsersWidget-D1B_2ge5.js"));
513
+ const component = await Promise.resolve().then(() => require("./OnlineUsersWidget-DTEzguhS.js"));
508
514
  return component.default;
509
515
  },
510
516
  id: "online-users-widget",