docusaurus-roles-plugin 1.0.0-beta.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/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # Docusaurus Roles Plugin
2
+
3
+ Role-based access control (RBAC) for **Docusaurus docs and blogs**, using front matter and a runtime role provider.
4
+
5
+ This plugin allows you to **conditionally hide or forbid access** to content (docs, blog posts, nav items, and sidebars) based on user roles that *you* define and resolve.
6
+
7
+ > [!CAUTION]
8
+ > This plugin **does not provide true security**. It operates at the **UI and routing layer only**.
9
+ >
10
+ > **You are responsible for protecting your content at the hosting / network level** (for example, via authentication, authorization rules, or server-side enforcement).
11
+ >
12
+ > Treat this plugin as a **presentation and UX layer**, not a security boundary.
13
+
14
+ ---
15
+
16
+ ## Features
17
+
18
+ - 🔒 Restrict access to **individual docs and blog posts**
19
+ - 🧭 Hide or filter **navbar items**
20
+ - 📚 Restrict **doc and blog sidebars**
21
+ - 🧩 Works with async role resolution (e.g. auth providers, APIs)
22
+ - 🏷️ Role requirements defined directly in **front matter**
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install docusaurus-roles-plugin
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Configuration
35
+
36
+ ### 1. Add the plugin to `docusaurus.config.ts`
37
+
38
+ ```ts
39
+ // docusaurus.config.ts
40
+ import { protectBlogSidebar, protectDocSidebar } from "docusaurus-roles-plugin";
41
+ import type { Config } from "@docusaurus/types";
42
+ import type * as Preset from "@docusaurus/preset-classic";
43
+
44
+ const config: Config = {
45
+ presets: [
46
+ [
47
+ "classic",
48
+ {
49
+ docs: {
50
+ sidebarItemsGenerator: protectDocSidebar,
51
+ },
52
+ blog: {
53
+ processBlogPosts: protectBlogSidebar,
54
+ },
55
+ } satisfies Preset.Options,
56
+ ],
57
+ ],
58
+
59
+ plugins: [
60
+ [
61
+ "docusaurus-roles-plugin",
62
+ {
63
+ // Defaults
64
+ unauthorizedBehavior: "forbidden"
65
+ } satisfies RolesPluginOptions,
66
+ ],
67
+ ],
68
+ };
69
+
70
+ export default config;
71
+ ```
72
+
73
+ ---
74
+
75
+ ### 2. Provide user roles at runtime
76
+
77
+ Create or modify `/src/theme/Root.tsx` and wrap your site with the `RolesProvider`.
78
+
79
+ ```tsx
80
+ import React from "react";
81
+ import { RolesProvider } from "docusaurus-roles-plugin/runtime";
82
+
83
+ export default function Root({ children }: { children: React.ReactNode }) {
84
+ return (
85
+ <RolesProvider
86
+ roles={async () => {
87
+ // Retrieve roles for current user.
88
+ return ["admin", "editor"];
89
+ }}
90
+ >
91
+ {children}
92
+ </RolesProvider>
93
+ );
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Restricting Docs or Blog Posts
100
+
101
+ ```md
102
+ ---
103
+ required_roles:
104
+ - admin
105
+ - editor
106
+ required_roles_mode: all
107
+ ---
108
+ ```
109
+
110
+ ### Fields
111
+
112
+ | Field | Description |
113
+ | ------------------- | ------------------------------------------- |
114
+ | required_roles | Array of roles required to view the content |
115
+ | required_roles_mode | "all" (default) or "any" |
116
+
117
+ **Examples**
118
+
119
+ * **all**: User must have *every* listed role
120
+ * **any**: User must have *at least one* listed role
121
+
122
+ ## Restricting Navbar items
123
+
124
+ ```ts
125
+ // docusaurus.config.ts
126
+ const config: Config = {
127
+ // ...
128
+ themeConfig: {
129
+ // ...
130
+ navbar: {
131
+ items: [
132
+ {
133
+ type: "docSidebar",
134
+ sidebarId: "tutorialSidebar",
135
+ position: "left",
136
+ label: "Tutorial",
137
+ // NOTE: This is camel case not snake_case like FrontMatter to match conventions
138
+ requiredRoles: ["...", /* ... */],
139
+ requiredRolesMode: "any"
140
+ },
141
+ { to: "/blog", label: "Blog", position: "left" },
142
+ {
143
+ href: "https://github.com/facebook/docusaurus",
144
+ label: "GitHub",
145
+ position: "right",
146
+ },
147
+ ],
148
+ },
149
+ }
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Unauthorized Behavior
156
+
157
+ You can control what happens when a user lacks required roles:
158
+
159
+ ```ts
160
+ unauthorizedBehavior: "forbidden" | "redirect";
161
+ redirectTo: "/404" | "...";
162
+ ```
163
+
164
+ * **forrbidden** → Renders swizzlable (customizable) forbidden component.
165
+ * **redirect** → Redirects the user to a custom route.
166
+
167
+ ---
168
+
169
+ ## Azure Static Web Apps (Example Use Case)
170
+
171
+ *This is the primary environment this plugin was designed for.*
172
+
173
+ ### Why Azure Static Web Apps?
174
+
175
+ Azure Static Web Apps provides built-in:
176
+
177
+ * Authentication (Entra ID / Azure AD)
178
+ * Authorization via role claims
179
+ * Role exposure to the frontend
180
+
181
+ Relevant documentation:
182
+
183
+ * [https://learn.microsoft.com/en-us/azure/static-web-apps/authentication-authorization](https://learn.microsoft.com/en-us/azure/static-web-apps/authentication-authorization)
184
+ * [https://learn.microsoft.com/en-us/azure/static-web-apps/user-information](https://learn.microsoft.com/en-us/azure/static-web-apps/user-information)
185
+
186
+ ---
187
+
188
+ ### Example `staticwebapp.config.json`
189
+
190
+ ```json
191
+ {
192
+ "routes": [
193
+ {
194
+ "route": "/",
195
+ "allowedRoles": ["anonymous", "authenticated"]
196
+ }
197
+ ],
198
+ "responseOverrides": {
199
+ "401": {
200
+ "redirect": "/login",
201
+ "statusCode": 302
202
+ },
203
+ "403": {
204
+ "rewrite": "/unauthorized.html"
205
+ }
206
+ },
207
+ "auth": {
208
+ "identityProviders": {
209
+ "azureActiveDirectory": {
210
+ "registration": {
211
+ "openIdIssuer": "https://login.microsoftonline.com/***/v2.0",
212
+ "clientIdSettingName": "***",
213
+ "clientSecretSettingName": "***"
214
+ },
215
+ "login": {
216
+ "loginParameters": [
217
+ "scope=openid profile email"
218
+ ]
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+ ```
225
+
226
+ Your `RolesProvider` can map Azure-provided role claims directly into the plugin.
227
+
228
+ ---
229
+
230
+ ## Philosophy & Scope
231
+
232
+ * This plugin is intentionally **opinionated**
233
+ * It solves **content gating**, not authentication
234
+ * It is designed to integrate with **existing auth systems**
235
+ * No assumptions are made about identity providers
236
+
237
+ If your needs differ, feel free to open an issue or fork.
238
+
239
+ ---
240
+
241
+ ## Contributing
242
+
243
+ This plugin primarily serves my own use cases, but suggestions and improvements are welcome.
244
+
245
+ * 🐛 Bug reports
246
+ * 💡 Feature ideas
247
+ * 🧩 Integration examples
248
+
249
+ Please open an issue with context about your setup.
package/dist/index.cjs ADDED
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ addDocSidebarCustomProps: () => addDocSidebarCustomProps,
34
+ default: () => pluginRoles,
35
+ protectBlogSidebar: () => protectBlogSidebar,
36
+ protectDocSidebar: () => protectDocSidebar,
37
+ setRolesByPermaLink: () => setRolesByPermaLink
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+ var import_path = __toESM(require("path"), 1);
41
+
42
+ // src/options.ts
43
+ function getRoleFrontMatter(frontMatter) {
44
+ if (!frontMatter || typeof frontMatter !== "object") return {};
45
+ return frontMatter;
46
+ }
47
+ function getRoleRequirements(frontMatter) {
48
+ const roleFrontMatter = getRoleFrontMatter(frontMatter);
49
+ return {
50
+ requiredRoles: roleFrontMatter.required_roles ?? [],
51
+ requiredRolesMode: roleFrontMatter.required_roles_mode ?? "all"
52
+ };
53
+ }
54
+
55
+ // src/index.ts
56
+ var addDocSidebarCustomProps = (docs) => {
57
+ var _a;
58
+ for (const doc of docs) {
59
+ const roles = doc.frontMatter.required_roles;
60
+ if (!roles) continue;
61
+ const props = (_a = doc.frontMatter).sidebar_custom_props ?? (_a.sidebar_custom_props = {});
62
+ props.required_roles = roles;
63
+ props.required_roles_mode = doc.frontMatter.required_roles_mode;
64
+ }
65
+ };
66
+ var protectDocSidebar = async ({
67
+ defaultSidebarItemsGenerator,
68
+ ...args
69
+ }) => {
70
+ addDocSidebarCustomProps(args.docs);
71
+ return defaultSidebarItemsGenerator(args);
72
+ };
73
+ var rolesByPermalink = [];
74
+ function createDeferred() {
75
+ let resolve;
76
+ let reject;
77
+ const promise = new Promise((res, rej) => {
78
+ resolve = res;
79
+ reject = rej;
80
+ });
81
+ return { promise, resolve, reject };
82
+ }
83
+ var blogRolesReady = createDeferred();
84
+ var setRolesByPermaLink = (blogPosts) => {
85
+ blogRolesReady = createDeferred();
86
+ rolesByPermalink = blogPosts.map((bp) => ({
87
+ permalink: bp.metadata.permalink,
88
+ ...getRoleRequirements(bp.metadata.frontMatter)
89
+ }));
90
+ blogRolesReady.resolve();
91
+ };
92
+ var protectBlogSidebar = async ({ blogPosts }) => {
93
+ setRolesByPermaLink(blogPosts);
94
+ return blogPosts;
95
+ };
96
+ function pluginRoles(_context, options) {
97
+ return {
98
+ name: "docusaurus-roles-plugin",
99
+ getThemePath() {
100
+ return import_path.default.resolve(__dirname, "../src/theme");
101
+ },
102
+ async contentLoaded({ actions }) {
103
+ await blogRolesReady.promise;
104
+ actions.setGlobalData({
105
+ rolesByPermalink,
106
+ unauthorizedBehavior: options.unauthorizedBehavior ?? "notFound",
107
+ redirectTo: options.redirectTo ?? "/access-denied"
108
+ });
109
+ }
110
+ };
111
+ }
112
+ // Annotate the CommonJS export names for ESM import in node:
113
+ 0 && (module.exports = {
114
+ addDocSidebarCustomProps,
115
+ protectBlogSidebar,
116
+ protectDocSidebar,
117
+ setRolesByPermaLink
118
+ });
119
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/options.ts"],"sourcesContent":["import path from \"path\";\nimport type { LoadContext, Plugin } from \"@docusaurus/types\";\nimport {\n getRoleRequirements,\n type RolesPluginOptions,\n type RoleEntry,\n} from \"./options\";\nimport { BlogPost, ProcessBlogPostsFn } from \"@docusaurus/plugin-content-blog\";\nimport {\n SidebarItemsGeneratorDoc,\n SidebarItemsGeneratorOption,\n} from \"@docusaurus/plugin-content-docs/src/sidebars/types.js\";\n\nexport { RolesPluginOptions } from \"./options\";\n\n/**\n * Used to apply FrontMatter required_roles and required_roles_mode to\n * sidebar_custom_props.required_roles and sidebar_custom_props.required_roles_mode.\n *\n * Should use protectDocSidebar for simple use cases.\n *\n * @param docs - The docs to transform.\n */\nexport const addDocSidebarCustomProps = (docs: SidebarItemsGeneratorDoc[]) => {\n for (const doc of docs) {\n const roles = doc.frontMatter.required_roles;\n if (!roles) continue;\n\n const props = (doc.frontMatter.sidebar_custom_props ??= {});\n props.required_roles = roles;\n props.required_roles_mode = doc.frontMatter.required_roles_mode;\n }\n};\n\n/**\n * Used to protect docs sidebar is a plug and play for sidebarItemsGenerator\n */\nexport const protectDocSidebar: SidebarItemsGeneratorOption = async ({\n defaultSidebarItemsGenerator,\n ...args\n}) => {\n addDocSidebarCustomProps(args.docs);\n\n return defaultSidebarItemsGenerator(args);\n};\n\nlet rolesByPermalink: RoleEntry[] = [];\n\nfunction createDeferred<T>() {\n let resolve!: (value: T) => void;\n let reject!: (reason?: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve, reject };\n}\n\nlet blogRolesReady = createDeferred<void>();\n\n/**\n * Used to set rolesByPermalink which is used by overriden BlogSidebar\n * to protect blog sidebar.\n */\nexport const setRolesByPermaLink = (blogPosts: BlogPost[]) => {\n blogRolesReady = createDeferred<void>();\n\n rolesByPermalink = blogPosts.map((bp) => ({\n permalink: bp.metadata.permalink,\n ...getRoleRequirements(bp.metadata.frontMatter),\n }));\n\n blogRolesReady.resolve();\n};\n\n/**\n * Used to protect blog sidebar is a plug and play for processBlogPosts.\n */\nexport const protectBlogSidebar: ProcessBlogPostsFn = async ({ blogPosts }) => {\n setRolesByPermaLink(blogPosts);\n return blogPosts;\n};\n\nexport default function pluginRoles(\n _context: LoadContext,\n options: RolesPluginOptions,\n): Plugin {\n return {\n name: \"docusaurus-roles-plugin\",\n\n getThemePath() {\n return path.resolve(__dirname, \"../src/theme\");\n },\n\n async contentLoaded({ actions }) {\n await blogRolesReady.promise;\n\n actions.setGlobalData({\n rolesByPermalink,\n unauthorizedBehavior: options.unauthorizedBehavior ?? \"notFound\",\n redirectTo: options.redirectTo ?? \"/access-denied\",\n });\n },\n };\n}\n","export type UnauthorizedBehavior = \"forbidden\" | \"redirect\";\n\nexport type RolesPluginOptions = {\n unauthorizedBehavior?: UnauthorizedBehavior;\n redirectTo?: string; // default '/access-denied'\n};\n\nexport type InternalRolesPluginOptions = {\n rolesByPermalink: RoleEntry[];\n} & RolesPluginOptions;\n\nexport type RolesMode = \"all\" | \"any\";\n\nexport type RoleRequirements = {\n requiredRoles: string[];\n requiredRolesMode: RolesMode;\n};\n\nexport type RoleFrontMatter = {\n required_roles?: string[];\n required_roles_mode?: RolesMode;\n};\n\nexport type RoleEntry = RoleRequirements & {\n permalink: string;\n};\n\nexport function getRoleFrontMatter(frontMatter: unknown): RoleFrontMatter {\n if (!frontMatter || typeof frontMatter !== \"object\") return {};\n return frontMatter as RoleFrontMatter;\n}\n\nexport function getRoleRequirements(frontMatter: unknown): RoleRequirements {\n const roleFrontMatter = getRoleFrontMatter(frontMatter);\n return {\n requiredRoles: roleFrontMatter.required_roles ?? [],\n requiredRolesMode: roleFrontMatter.required_roles_mode ?? \"all\",\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAiB;;;AC2BV,SAAS,mBAAmB,aAAuC;AACxE,MAAI,CAAC,eAAe,OAAO,gBAAgB,SAAU,QAAO,CAAC;AAC7D,SAAO;AACT;AAEO,SAAS,oBAAoB,aAAwC;AAC1E,QAAM,kBAAkB,mBAAmB,WAAW;AACtD,SAAO;AAAA,IACL,eAAe,gBAAgB,kBAAkB,CAAC;AAAA,IAClD,mBAAmB,gBAAgB,uBAAuB;AAAA,EAC5D;AACF;;;ADfO,IAAM,2BAA2B,CAAC,SAAqC;AAvB9E;AAwBE,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,IAAI,YAAY;AAC9B,QAAI,CAAC,MAAO;AAEZ,UAAM,SAAS,SAAI,aAAY,yBAAhB,GAAgB,uBAAyB,CAAC;AACzD,UAAM,iBAAiB;AACvB,UAAM,sBAAsB,IAAI,YAAY;AAAA,EAC9C;AACF;AAKO,IAAM,oBAAiD,OAAO;AAAA,EACnE;AAAA,EACA,GAAG;AACL,MAAM;AACJ,2BAAyB,KAAK,IAAI;AAElC,SAAO,6BAA6B,IAAI;AAC1C;AAEA,IAAI,mBAAgC,CAAC;AAErC,SAAS,iBAAoB;AAC3B,MAAI;AACJ,MAAI;AACJ,QAAM,UAAU,IAAI,QAAW,CAAC,KAAK,QAAQ;AAC3C,cAAU;AACV,aAAS;AAAA,EACX,CAAC;AACD,SAAO,EAAE,SAAS,SAAS,OAAO;AACpC;AAEA,IAAI,iBAAiB,eAAqB;AAMnC,IAAM,sBAAsB,CAAC,cAA0B;AAC5D,mBAAiB,eAAqB;AAEtC,qBAAmB,UAAU,IAAI,CAAC,QAAQ;AAAA,IACxC,WAAW,GAAG,SAAS;AAAA,IACvB,GAAG,oBAAoB,GAAG,SAAS,WAAW;AAAA,EAChD,EAAE;AAEF,iBAAe,QAAQ;AACzB;AAKO,IAAM,qBAAyC,OAAO,EAAE,UAAU,MAAM;AAC7E,sBAAoB,SAAS;AAC7B,SAAO;AACT;AAEe,SAAR,YACL,UACA,SACQ;AACR,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,eAAe;AACb,aAAO,YAAAA,QAAK,QAAQ,WAAW,cAAc;AAAA,IAC/C;AAAA,IAEA,MAAM,cAAc,EAAE,QAAQ,GAAG;AAC/B,YAAM,eAAe;AAErB,cAAQ,cAAc;AAAA,QACpB;AAAA,QACA,sBAAsB,QAAQ,wBAAwB;AAAA,QACtD,YAAY,QAAQ,cAAc;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":["path"]}
@@ -0,0 +1,35 @@
1
+ import { LoadContext, Plugin } from '@docusaurus/types';
2
+ import { BlogPost, ProcessBlogPostsFn } from '@docusaurus/plugin-content-blog';
3
+ import { SidebarItemsGeneratorDoc, SidebarItemsGeneratorOption } from '@docusaurus/plugin-content-docs/src/sidebars/types.js';
4
+
5
+ type UnauthorizedBehavior = "forbidden" | "redirect";
6
+ type RolesPluginOptions = {
7
+ unauthorizedBehavior?: UnauthorizedBehavior;
8
+ redirectTo?: string;
9
+ };
10
+
11
+ /**
12
+ * Used to apply FrontMatter required_roles and required_roles_mode to
13
+ * sidebar_custom_props.required_roles and sidebar_custom_props.required_roles_mode.
14
+ *
15
+ * Should use protectDocSidebar for simple use cases.
16
+ *
17
+ * @param docs - The docs to transform.
18
+ */
19
+ declare const addDocSidebarCustomProps: (docs: SidebarItemsGeneratorDoc[]) => void;
20
+ /**
21
+ * Used to protect docs sidebar is a plug and play for sidebarItemsGenerator
22
+ */
23
+ declare const protectDocSidebar: SidebarItemsGeneratorOption;
24
+ /**
25
+ * Used to set rolesByPermalink which is used by overriden BlogSidebar
26
+ * to protect blog sidebar.
27
+ */
28
+ declare const setRolesByPermaLink: (blogPosts: BlogPost[]) => void;
29
+ /**
30
+ * Used to protect blog sidebar is a plug and play for processBlogPosts.
31
+ */
32
+ declare const protectBlogSidebar: ProcessBlogPostsFn;
33
+ declare function pluginRoles(_context: LoadContext, options: RolesPluginOptions): Plugin;
34
+
35
+ export { type RolesPluginOptions, addDocSidebarCustomProps, pluginRoles as default, protectBlogSidebar, protectDocSidebar, setRolesByPermaLink };
@@ -0,0 +1,35 @@
1
+ import { LoadContext, Plugin } from '@docusaurus/types';
2
+ import { BlogPost, ProcessBlogPostsFn } from '@docusaurus/plugin-content-blog';
3
+ import { SidebarItemsGeneratorDoc, SidebarItemsGeneratorOption } from '@docusaurus/plugin-content-docs/src/sidebars/types.js';
4
+
5
+ type UnauthorizedBehavior = "forbidden" | "redirect";
6
+ type RolesPluginOptions = {
7
+ unauthorizedBehavior?: UnauthorizedBehavior;
8
+ redirectTo?: string;
9
+ };
10
+
11
+ /**
12
+ * Used to apply FrontMatter required_roles and required_roles_mode to
13
+ * sidebar_custom_props.required_roles and sidebar_custom_props.required_roles_mode.
14
+ *
15
+ * Should use protectDocSidebar for simple use cases.
16
+ *
17
+ * @param docs - The docs to transform.
18
+ */
19
+ declare const addDocSidebarCustomProps: (docs: SidebarItemsGeneratorDoc[]) => void;
20
+ /**
21
+ * Used to protect docs sidebar is a plug and play for sidebarItemsGenerator
22
+ */
23
+ declare const protectDocSidebar: SidebarItemsGeneratorOption;
24
+ /**
25
+ * Used to set rolesByPermalink which is used by overriden BlogSidebar
26
+ * to protect blog sidebar.
27
+ */
28
+ declare const setRolesByPermaLink: (blogPosts: BlogPost[]) => void;
29
+ /**
30
+ * Used to protect blog sidebar is a plug and play for processBlogPosts.
31
+ */
32
+ declare const protectBlogSidebar: ProcessBlogPostsFn;
33
+ declare function pluginRoles(_context: LoadContext, options: RolesPluginOptions): Plugin;
34
+
35
+ export { type RolesPluginOptions, addDocSidebarCustomProps, pluginRoles as default, protectBlogSidebar, protectDocSidebar, setRolesByPermaLink };
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ // src/index.ts
2
+ import path from "path";
3
+
4
+ // src/options.ts
5
+ function getRoleFrontMatter(frontMatter) {
6
+ if (!frontMatter || typeof frontMatter !== "object") return {};
7
+ return frontMatter;
8
+ }
9
+ function getRoleRequirements(frontMatter) {
10
+ const roleFrontMatter = getRoleFrontMatter(frontMatter);
11
+ return {
12
+ requiredRoles: roleFrontMatter.required_roles ?? [],
13
+ requiredRolesMode: roleFrontMatter.required_roles_mode ?? "all"
14
+ };
15
+ }
16
+
17
+ // src/index.ts
18
+ var addDocSidebarCustomProps = (docs) => {
19
+ var _a;
20
+ for (const doc of docs) {
21
+ const roles = doc.frontMatter.required_roles;
22
+ if (!roles) continue;
23
+ const props = (_a = doc.frontMatter).sidebar_custom_props ?? (_a.sidebar_custom_props = {});
24
+ props.required_roles = roles;
25
+ props.required_roles_mode = doc.frontMatter.required_roles_mode;
26
+ }
27
+ };
28
+ var protectDocSidebar = async ({
29
+ defaultSidebarItemsGenerator,
30
+ ...args
31
+ }) => {
32
+ addDocSidebarCustomProps(args.docs);
33
+ return defaultSidebarItemsGenerator(args);
34
+ };
35
+ var rolesByPermalink = [];
36
+ function createDeferred() {
37
+ let resolve;
38
+ let reject;
39
+ const promise = new Promise((res, rej) => {
40
+ resolve = res;
41
+ reject = rej;
42
+ });
43
+ return { promise, resolve, reject };
44
+ }
45
+ var blogRolesReady = createDeferred();
46
+ var setRolesByPermaLink = (blogPosts) => {
47
+ blogRolesReady = createDeferred();
48
+ rolesByPermalink = blogPosts.map((bp) => ({
49
+ permalink: bp.metadata.permalink,
50
+ ...getRoleRequirements(bp.metadata.frontMatter)
51
+ }));
52
+ blogRolesReady.resolve();
53
+ };
54
+ var protectBlogSidebar = async ({ blogPosts }) => {
55
+ setRolesByPermaLink(blogPosts);
56
+ return blogPosts;
57
+ };
58
+ function pluginRoles(_context, options) {
59
+ return {
60
+ name: "docusaurus-roles-plugin",
61
+ getThemePath() {
62
+ return path.resolve(__dirname, "../src/theme");
63
+ },
64
+ async contentLoaded({ actions }) {
65
+ await blogRolesReady.promise;
66
+ actions.setGlobalData({
67
+ rolesByPermalink,
68
+ unauthorizedBehavior: options.unauthorizedBehavior ?? "notFound",
69
+ redirectTo: options.redirectTo ?? "/access-denied"
70
+ });
71
+ }
72
+ };
73
+ }
74
+ export {
75
+ addDocSidebarCustomProps,
76
+ pluginRoles as default,
77
+ protectBlogSidebar,
78
+ protectDocSidebar,
79
+ setRolesByPermaLink
80
+ };
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/options.ts"],"sourcesContent":["import path from \"path\";\nimport type { LoadContext, Plugin } from \"@docusaurus/types\";\nimport {\n getRoleRequirements,\n type RolesPluginOptions,\n type RoleEntry,\n} from \"./options\";\nimport { BlogPost, ProcessBlogPostsFn } from \"@docusaurus/plugin-content-blog\";\nimport {\n SidebarItemsGeneratorDoc,\n SidebarItemsGeneratorOption,\n} from \"@docusaurus/plugin-content-docs/src/sidebars/types.js\";\n\nexport { RolesPluginOptions } from \"./options\";\n\n/**\n * Used to apply FrontMatter required_roles and required_roles_mode to\n * sidebar_custom_props.required_roles and sidebar_custom_props.required_roles_mode.\n *\n * Should use protectDocSidebar for simple use cases.\n *\n * @param docs - The docs to transform.\n */\nexport const addDocSidebarCustomProps = (docs: SidebarItemsGeneratorDoc[]) => {\n for (const doc of docs) {\n const roles = doc.frontMatter.required_roles;\n if (!roles) continue;\n\n const props = (doc.frontMatter.sidebar_custom_props ??= {});\n props.required_roles = roles;\n props.required_roles_mode = doc.frontMatter.required_roles_mode;\n }\n};\n\n/**\n * Used to protect docs sidebar is a plug and play for sidebarItemsGenerator\n */\nexport const protectDocSidebar: SidebarItemsGeneratorOption = async ({\n defaultSidebarItemsGenerator,\n ...args\n}) => {\n addDocSidebarCustomProps(args.docs);\n\n return defaultSidebarItemsGenerator(args);\n};\n\nlet rolesByPermalink: RoleEntry[] = [];\n\nfunction createDeferred<T>() {\n let resolve!: (value: T) => void;\n let reject!: (reason?: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve, reject };\n}\n\nlet blogRolesReady = createDeferred<void>();\n\n/**\n * Used to set rolesByPermalink which is used by overriden BlogSidebar\n * to protect blog sidebar.\n */\nexport const setRolesByPermaLink = (blogPosts: BlogPost[]) => {\n blogRolesReady = createDeferred<void>();\n\n rolesByPermalink = blogPosts.map((bp) => ({\n permalink: bp.metadata.permalink,\n ...getRoleRequirements(bp.metadata.frontMatter),\n }));\n\n blogRolesReady.resolve();\n};\n\n/**\n * Used to protect blog sidebar is a plug and play for processBlogPosts.\n */\nexport const protectBlogSidebar: ProcessBlogPostsFn = async ({ blogPosts }) => {\n setRolesByPermaLink(blogPosts);\n return blogPosts;\n};\n\nexport default function pluginRoles(\n _context: LoadContext,\n options: RolesPluginOptions,\n): Plugin {\n return {\n name: \"docusaurus-roles-plugin\",\n\n getThemePath() {\n return path.resolve(__dirname, \"../src/theme\");\n },\n\n async contentLoaded({ actions }) {\n await blogRolesReady.promise;\n\n actions.setGlobalData({\n rolesByPermalink,\n unauthorizedBehavior: options.unauthorizedBehavior ?? \"notFound\",\n redirectTo: options.redirectTo ?? \"/access-denied\",\n });\n },\n };\n}\n","export type UnauthorizedBehavior = \"forbidden\" | \"redirect\";\n\nexport type RolesPluginOptions = {\n unauthorizedBehavior?: UnauthorizedBehavior;\n redirectTo?: string; // default '/access-denied'\n};\n\nexport type InternalRolesPluginOptions = {\n rolesByPermalink: RoleEntry[];\n} & RolesPluginOptions;\n\nexport type RolesMode = \"all\" | \"any\";\n\nexport type RoleRequirements = {\n requiredRoles: string[];\n requiredRolesMode: RolesMode;\n};\n\nexport type RoleFrontMatter = {\n required_roles?: string[];\n required_roles_mode?: RolesMode;\n};\n\nexport type RoleEntry = RoleRequirements & {\n permalink: string;\n};\n\nexport function getRoleFrontMatter(frontMatter: unknown): RoleFrontMatter {\n if (!frontMatter || typeof frontMatter !== \"object\") return {};\n return frontMatter as RoleFrontMatter;\n}\n\nexport function getRoleRequirements(frontMatter: unknown): RoleRequirements {\n const roleFrontMatter = getRoleFrontMatter(frontMatter);\n return {\n requiredRoles: roleFrontMatter.required_roles ?? [],\n requiredRolesMode: roleFrontMatter.required_roles_mode ?? \"all\",\n };\n}\n"],"mappings":";AAAA,OAAO,UAAU;;;AC2BV,SAAS,mBAAmB,aAAuC;AACxE,MAAI,CAAC,eAAe,OAAO,gBAAgB,SAAU,QAAO,CAAC;AAC7D,SAAO;AACT;AAEO,SAAS,oBAAoB,aAAwC;AAC1E,QAAM,kBAAkB,mBAAmB,WAAW;AACtD,SAAO;AAAA,IACL,eAAe,gBAAgB,kBAAkB,CAAC;AAAA,IAClD,mBAAmB,gBAAgB,uBAAuB;AAAA,EAC5D;AACF;;;ADfO,IAAM,2BAA2B,CAAC,SAAqC;AAvB9E;AAwBE,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,IAAI,YAAY;AAC9B,QAAI,CAAC,MAAO;AAEZ,UAAM,SAAS,SAAI,aAAY,yBAAhB,GAAgB,uBAAyB,CAAC;AACzD,UAAM,iBAAiB;AACvB,UAAM,sBAAsB,IAAI,YAAY;AAAA,EAC9C;AACF;AAKO,IAAM,oBAAiD,OAAO;AAAA,EACnE;AAAA,EACA,GAAG;AACL,MAAM;AACJ,2BAAyB,KAAK,IAAI;AAElC,SAAO,6BAA6B,IAAI;AAC1C;AAEA,IAAI,mBAAgC,CAAC;AAErC,SAAS,iBAAoB;AAC3B,MAAI;AACJ,MAAI;AACJ,QAAM,UAAU,IAAI,QAAW,CAAC,KAAK,QAAQ;AAC3C,cAAU;AACV,aAAS;AAAA,EACX,CAAC;AACD,SAAO,EAAE,SAAS,SAAS,OAAO;AACpC;AAEA,IAAI,iBAAiB,eAAqB;AAMnC,IAAM,sBAAsB,CAAC,cAA0B;AAC5D,mBAAiB,eAAqB;AAEtC,qBAAmB,UAAU,IAAI,CAAC,QAAQ;AAAA,IACxC,WAAW,GAAG,SAAS;AAAA,IACvB,GAAG,oBAAoB,GAAG,SAAS,WAAW;AAAA,EAChD,EAAE;AAEF,iBAAe,QAAQ;AACzB;AAKO,IAAM,qBAAyC,OAAO,EAAE,UAAU,MAAM;AAC7E,sBAAoB,SAAS;AAC7B,SAAO;AACT;AAEe,SAAR,YACL,UACA,SACQ;AACR,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,eAAe;AACb,aAAO,KAAK,QAAQ,WAAW,cAAc;AAAA,IAC/C;AAAA,IAEA,MAAM,cAAc,EAAE,QAAQ,GAAG;AAC/B,YAAM,eAAe;AAErB,cAAQ,cAAc;AAAA,QACpB;AAAA,QACA,sBAAsB,QAAQ,wBAAwB;AAAA,QACtD,YAAY,QAAQ,cAAc;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}