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 +249 -0
- package/dist/index.cjs +119 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/index.cjs +202 -0
- package/dist/runtime/index.cjs.map +1 -0
- package/dist/runtime/index.d.cts +43 -0
- package/dist/runtime/index.d.ts +43 -0
- package/dist/runtime/index.js +161 -0
- package/dist/runtime/index.js.map +1 -0
- package/package.json +52 -0
- package/src/theme/BlogListPage/index.tsx +20 -0
- package/src/theme/BlogPostItem/Forbidden.tsx +17 -0
- package/src/theme/BlogPostItem/index.tsx +22 -0
- package/src/theme/BlogSidebar/index.tsx +26 -0
- package/src/theme/DocItem/Forbidden.tsx +15 -0
- package/src/theme/DocItem/index.tsx +20 -0
- package/src/theme/DocSidebar/index.tsx +29 -0
- package/src/theme/NavbarItem/index.tsx +16 -0
- package/src/theme/RoleGate.tsx +53 -0
- package/src/theme/RootWarning.tsx +18 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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":[]}
|