decap-cms-oauth-astro 0.1.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/decap-cms-oauth-astro.d.ts +17 -0
- package/dist/main.cjs +148 -0
- package/dist/main.js +150 -0
- package/package.json +58 -0
- package/src/admin.astro +18 -0
- package/src/config.ts +12 -0
- package/src/env.d.ts +7 -0
- package/src/main.ts +185 -0
- package/src/oauth/callback.ts +58 -0
- package/src/oauth/index.ts +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Doruk Gezici
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1 align="center">decap-cms-oauth-astro</h1>
|
|
3
|
+
<p align="center">Astro integration for <a href="https://decapcms.org" target="_blank">Decap</a>/<a href="https://github.com/sveltia/sveltia-cms" target="_blank">Sveltia</a> CMS with custom OAuth backend</p>
|
|
4
|
+
<br/>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
A integration plugin that mounts the Decap CMS (or any compatible CMS like Sveltia) admin dashboard and custom OAuth authentication backend routes to `/oauth`&`/oauth/callback` using GitHub as the provider.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx astro add decap-cms-oauth-astro
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
1. Put your `config.yml` file in `public/admin/config.yml` (see [Decap CMS Docs](https://decapcms.org/docs/add-to-your-site/#configuration) for more info)
|
|
20
|
+
```yml
|
|
21
|
+
backend:
|
|
22
|
+
name: github
|
|
23
|
+
repo: Foxie-404/decap-cms-oauth-astro # change this to your repo
|
|
24
|
+
branch: main # change this to your branch
|
|
25
|
+
site_domain: decap-cms-oauth-astro.vercel.app # change this to your domain
|
|
26
|
+
base_url: https://decap-cms-oauth-astro.vercel.app # change this to your prod URL
|
|
27
|
+
auth_endpoint: oauth # the oauth route provided by the integration
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. On GitHub, go to Settings > Developer Settings > OAuth apps > New OAuth app. Or use this [direct link](https://github.com/settings/applications/new).
|
|
31
|
+
**Homepage URL**: This must be the prod URL of your application.
|
|
32
|
+
**Authorization callback URL**: This must be the prod URL of your application followed by `/oauth/callback`.
|
|
33
|
+
**Homepage URL**: This must be the prod URL of your application.
|
|
34
|
+
**Authorization callback URL**: This must be the prod URL of your application followed by `/oauth/callback`.
|
|
35
|
+
Once registered, click on the **Generate a new client secret** button. The app’s **Client ID** and **Client Secret** will be displayed.
|
|
36
|
+
Then navigate to `https://github.com/apps/<app slug>/installations/new` to install it on the repo. You can scope the access tokens further if wanted - details on [this page](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app#using-the-web-application-flow-to-generate-a-user-access-token)
|
|
37
|
+
```bash
|
|
38
|
+
curl -s 'https://api.github.com/repos/<owner>/<repo>' | jq .id
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You can then use this ID for the `OAUTH_GITHUB_REPO_ID` environment variable.
|
|
42
|
+
|
|
43
|
+
4. Set env variables
|
|
44
|
+
```bash
|
|
45
|
+
# GitHub OAuth App & GitHub App
|
|
46
|
+
OAUTH_GITHUB_CLIENT_ID=
|
|
47
|
+
OAUTH_GITHUB_CLIENT_SECRET=
|
|
48
|
+
# GitHub App only
|
|
49
|
+
OAUTH_GITHUB_REPO_ID=
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Custom Config Path
|
|
54
|
+
|
|
55
|
+
By default, the integration looks for the Decap CMS configuration at `public/admin/config.yml`. You can customize this path using the `configPath` option:
|
|
56
|
+
```js
|
|
57
|
+
decapCmsOauth({
|
|
58
|
+
configPath: ".decap.yml" // Path relative to project root
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Whitelist and Validation
|
|
64
|
+
|
|
65
|
+
To ensure compatibility and security, only official Decap CMS fields are allowed in the configuration. These include: `backend`, `site_url`, `display_url`, `logo`, `logo_url`, `media_folder`, `public_folder`, `collections`, `publish_mode`, `show_preview_links`, `slug`, `local_backend`, `i18n`, `media_library`, `editor`, `search`, `locale`.
|
|
66
|
+
|
|
67
|
+
The fields `backend` and `collections` are **required**. If they are missing or if the configuration file is invalid, the integration will throw an error to terminate the build process.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## Acknowledgements
|
|
71
|
+
|
|
72
|
+
- [astro-decap-cms-oauth](dorukgezici/astro-decap-cms-oauth)
|
|
73
|
+
- [Decap CMS](https://decapcms.org/)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AstroIntegration } from 'astro';
|
|
2
|
+
|
|
3
|
+
declare function decapCMS(options?: DecapCMSOptions): AstroIntegration;
|
|
4
|
+
export default decapCMS;
|
|
5
|
+
|
|
6
|
+
declare interface DecapCMSOptions {
|
|
7
|
+
configPath?: string;
|
|
8
|
+
decapCMSSrcUrl?: string;
|
|
9
|
+
decapCMSVersion?: string;
|
|
10
|
+
adminDisabled?: boolean;
|
|
11
|
+
adminRoute?: string;
|
|
12
|
+
enable?: boolean;
|
|
13
|
+
oauthLoginRoute?: string;
|
|
14
|
+
oauthCallbackRoute?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { }
|
package/dist/main.cjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
(function(global, factory) {
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory(require("astro/config"), require("node:fs"), require("node:path"), require("node:url"), require("js-yaml")) : typeof define === "function" && define.amd ? define(["astro/config", "node:fs", "node:path", "node:url", "js-yaml"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.DecapCMSOAuthAstro = factory(global.config, global.fs, global.path, global.node_url, global.yaml));
|
|
3
|
+
})(this, function(config, fs, path, node_url, yaml) {
|
|
4
|
+
"use strict";
|
|
5
|
+
const defaultOptions = {
|
|
6
|
+
configPath: "public/admin/config.yml",
|
|
7
|
+
decapCMSSrcUrl: "",
|
|
8
|
+
decapCMSVersion: "3.3.3",
|
|
9
|
+
adminDisabled: false,
|
|
10
|
+
adminRoute: "/admin",
|
|
11
|
+
enable: true,
|
|
12
|
+
oauthLoginRoute: "/oauth",
|
|
13
|
+
oauthCallbackRoute: "/oauth/callback"
|
|
14
|
+
};
|
|
15
|
+
const WHITELIST = [
|
|
16
|
+
"backend",
|
|
17
|
+
"site_url",
|
|
18
|
+
"display_url",
|
|
19
|
+
"logo",
|
|
20
|
+
"logo_url",
|
|
21
|
+
"media_folder",
|
|
22
|
+
"public_folder",
|
|
23
|
+
"collections",
|
|
24
|
+
"publish_mode",
|
|
25
|
+
"show_preview_links",
|
|
26
|
+
"slug",
|
|
27
|
+
"local_backend",
|
|
28
|
+
"i18n",
|
|
29
|
+
"media_library",
|
|
30
|
+
"editor",
|
|
31
|
+
"search",
|
|
32
|
+
"locale"
|
|
33
|
+
];
|
|
34
|
+
function decapCMS(options = {}) {
|
|
35
|
+
const {
|
|
36
|
+
configPath,
|
|
37
|
+
decapCMSSrcUrl,
|
|
38
|
+
decapCMSVersion,
|
|
39
|
+
adminDisabled,
|
|
40
|
+
adminRoute,
|
|
41
|
+
enable,
|
|
42
|
+
oauthLoginRoute,
|
|
43
|
+
oauthCallbackRoute
|
|
44
|
+
} = {
|
|
45
|
+
...defaultOptions,
|
|
46
|
+
...options
|
|
47
|
+
};
|
|
48
|
+
if (!(adminRoute == null ? void 0 : adminRoute.startsWith("/")) || !(oauthLoginRoute == null ? void 0 : oauthLoginRoute.startsWith("/")) || !(oauthCallbackRoute == null ? void 0 : oauthCallbackRoute.startsWith("/"))) {
|
|
49
|
+
throw new Error('`adminRoute`, `oauthLoginRoute` and `oauthCallbackRoute` options must start with "/"');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
name: "decap-cms-oauth-astro",
|
|
53
|
+
hooks: {
|
|
54
|
+
"astro:config:setup": async ({ injectRoute, updateConfig, config: config$1 }) => {
|
|
55
|
+
const env = { validateSecrets: true, schema: {} };
|
|
56
|
+
let validatedConfigYaml = "";
|
|
57
|
+
if (!adminDisabled) {
|
|
58
|
+
const rootDir = node_url.fileURLToPath(config$1.root);
|
|
59
|
+
let absoluteConfigPath = path.resolve(rootDir, configPath);
|
|
60
|
+
if (!fs.existsSync(absoluteConfigPath)) {
|
|
61
|
+
const fallbackPath = path.resolve(rootDir, "public/admin/config.yml");
|
|
62
|
+
if (fs.existsSync(fallbackPath)) {
|
|
63
|
+
absoluteConfigPath = fallbackPath;
|
|
64
|
+
} else {
|
|
65
|
+
throw new Error(`Decap CMS config file not found at ${configPath} or ${fallbackPath}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const fileContent = fs.readFileSync(absoluteConfigPath, "utf8");
|
|
70
|
+
const parsedConfig = yaml.load(fileContent);
|
|
71
|
+
if (!parsedConfig.backend || !parsedConfig.collections) {
|
|
72
|
+
console.error("Decap CMS configuration is missing required fields: 'backend' or 'collections'. Admin dashboard will be disabled.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const filteredConfig = {};
|
|
76
|
+
for (const key of WHITELIST) {
|
|
77
|
+
if (key in parsedConfig) {
|
|
78
|
+
filteredConfig[key] = parsedConfig[key];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
validatedConfigYaml = yaml.dump(filteredConfig);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error(`Failed to parse Decap CMS config: ${e}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
injectRoute({
|
|
87
|
+
pattern: adminRoute,
|
|
88
|
+
entrypoint: "decap-cms-oauth-astro/src/admin.astro"
|
|
89
|
+
});
|
|
90
|
+
injectRoute({
|
|
91
|
+
pattern: `${adminRoute}/config.yml`,
|
|
92
|
+
entrypoint: "decap-cms-oauth-astro/src/config.ts"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (enable) {
|
|
96
|
+
env.schema.OAUTH_GITHUB_CLIENT_ID = config.envField.string({
|
|
97
|
+
context: "server",
|
|
98
|
+
access: "secret"
|
|
99
|
+
});
|
|
100
|
+
env.schema.OAUTH_GITHUB_CLIENT_SECRET = config.envField.string({
|
|
101
|
+
context: "server",
|
|
102
|
+
access: "secret"
|
|
103
|
+
});
|
|
104
|
+
env.schema.OAUTH_GITHUB_REPO_ID = config.envField.string({
|
|
105
|
+
context: "server",
|
|
106
|
+
access: "secret",
|
|
107
|
+
optional: true,
|
|
108
|
+
default: ""
|
|
109
|
+
});
|
|
110
|
+
injectRoute({
|
|
111
|
+
pattern: oauthLoginRoute,
|
|
112
|
+
entrypoint: "decap-cms-oauth-astro/src/oauth/index.ts"
|
|
113
|
+
});
|
|
114
|
+
injectRoute({
|
|
115
|
+
pattern: oauthCallbackRoute,
|
|
116
|
+
entrypoint: "decap-cms-oauth-astro/src/oauth/callback.ts"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
updateConfig({
|
|
120
|
+
env,
|
|
121
|
+
vite: {
|
|
122
|
+
plugins: [
|
|
123
|
+
{
|
|
124
|
+
name: "decap-cms-config",
|
|
125
|
+
resolveId(id) {
|
|
126
|
+
if (id === "virtual:decap-cms-config") {
|
|
127
|
+
return "\0virtual:decap-cms-config";
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
load(id) {
|
|
131
|
+
if (id === "\0virtual:decap-cms-config") {
|
|
132
|
+
return `
|
|
133
|
+
export const configYaml = ${JSON.stringify(validatedConfigYaml)};
|
|
134
|
+
export const decapCMSSrcUrl = ${JSON.stringify(decapCMSSrcUrl)};
|
|
135
|
+
export const decapCMSVersion = ${JSON.stringify(decapCMSVersion)};
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return decapCMS;
|
|
148
|
+
});
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { envField } from "astro/config";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
configPath: "public/admin/config.yml",
|
|
8
|
+
decapCMSSrcUrl: "",
|
|
9
|
+
decapCMSVersion: "3.3.3",
|
|
10
|
+
adminDisabled: false,
|
|
11
|
+
adminRoute: "/admin",
|
|
12
|
+
enable: true,
|
|
13
|
+
oauthLoginRoute: "/oauth",
|
|
14
|
+
oauthCallbackRoute: "/oauth/callback"
|
|
15
|
+
};
|
|
16
|
+
const WHITELIST = [
|
|
17
|
+
"backend",
|
|
18
|
+
"site_url",
|
|
19
|
+
"display_url",
|
|
20
|
+
"logo",
|
|
21
|
+
"logo_url",
|
|
22
|
+
"media_folder",
|
|
23
|
+
"public_folder",
|
|
24
|
+
"collections",
|
|
25
|
+
"publish_mode",
|
|
26
|
+
"show_preview_links",
|
|
27
|
+
"slug",
|
|
28
|
+
"local_backend",
|
|
29
|
+
"i18n",
|
|
30
|
+
"media_library",
|
|
31
|
+
"editor",
|
|
32
|
+
"search",
|
|
33
|
+
"locale"
|
|
34
|
+
];
|
|
35
|
+
function decapCMS(options = {}) {
|
|
36
|
+
const {
|
|
37
|
+
configPath,
|
|
38
|
+
decapCMSSrcUrl,
|
|
39
|
+
decapCMSVersion,
|
|
40
|
+
adminDisabled,
|
|
41
|
+
adminRoute,
|
|
42
|
+
enable,
|
|
43
|
+
oauthLoginRoute,
|
|
44
|
+
oauthCallbackRoute
|
|
45
|
+
} = {
|
|
46
|
+
...defaultOptions,
|
|
47
|
+
...options
|
|
48
|
+
};
|
|
49
|
+
if (!(adminRoute == null ? void 0 : adminRoute.startsWith("/")) || !(oauthLoginRoute == null ? void 0 : oauthLoginRoute.startsWith("/")) || !(oauthCallbackRoute == null ? void 0 : oauthCallbackRoute.startsWith("/"))) {
|
|
50
|
+
throw new Error('`adminRoute`, `oauthLoginRoute` and `oauthCallbackRoute` options must start with "/"');
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
name: "decap-cms-oauth-astro",
|
|
54
|
+
hooks: {
|
|
55
|
+
"astro:config:setup": async ({ injectRoute, updateConfig, config }) => {
|
|
56
|
+
const env = { validateSecrets: true, schema: {} };
|
|
57
|
+
let validatedConfigYaml = "";
|
|
58
|
+
if (!adminDisabled) {
|
|
59
|
+
const rootDir = fileURLToPath(config.root);
|
|
60
|
+
let absoluteConfigPath = path.resolve(rootDir, configPath);
|
|
61
|
+
if (!fs.existsSync(absoluteConfigPath)) {
|
|
62
|
+
const fallbackPath = path.resolve(rootDir, "public/admin/config.yml");
|
|
63
|
+
if (fs.existsSync(fallbackPath)) {
|
|
64
|
+
absoluteConfigPath = fallbackPath;
|
|
65
|
+
} else {
|
|
66
|
+
throw new Error(`Decap CMS config file not found at ${configPath} or ${fallbackPath}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const fileContent = fs.readFileSync(absoluteConfigPath, "utf8");
|
|
71
|
+
const parsedConfig = yaml.load(fileContent);
|
|
72
|
+
if (!parsedConfig.backend || !parsedConfig.collections) {
|
|
73
|
+
console.error("Decap CMS configuration is missing required fields: 'backend' or 'collections'. Admin dashboard will be disabled.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const filteredConfig = {};
|
|
77
|
+
for (const key of WHITELIST) {
|
|
78
|
+
if (key in parsedConfig) {
|
|
79
|
+
filteredConfig[key] = parsedConfig[key];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
validatedConfigYaml = yaml.dump(filteredConfig);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error(`Failed to parse Decap CMS config: ${e}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
injectRoute({
|
|
88
|
+
pattern: adminRoute,
|
|
89
|
+
entrypoint: "decap-cms-oauth-astro/src/admin.astro"
|
|
90
|
+
});
|
|
91
|
+
injectRoute({
|
|
92
|
+
pattern: `${adminRoute}/config.yml`,
|
|
93
|
+
entrypoint: "decap-cms-oauth-astro/src/config.ts"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (enable) {
|
|
97
|
+
env.schema.OAUTH_GITHUB_CLIENT_ID = envField.string({
|
|
98
|
+
context: "server",
|
|
99
|
+
access: "secret"
|
|
100
|
+
});
|
|
101
|
+
env.schema.OAUTH_GITHUB_CLIENT_SECRET = envField.string({
|
|
102
|
+
context: "server",
|
|
103
|
+
access: "secret"
|
|
104
|
+
});
|
|
105
|
+
env.schema.OAUTH_GITHUB_REPO_ID = envField.string({
|
|
106
|
+
context: "server",
|
|
107
|
+
access: "secret",
|
|
108
|
+
optional: true,
|
|
109
|
+
default: ""
|
|
110
|
+
});
|
|
111
|
+
injectRoute({
|
|
112
|
+
pattern: oauthLoginRoute,
|
|
113
|
+
entrypoint: "decap-cms-oauth-astro/src/oauth/index.ts"
|
|
114
|
+
});
|
|
115
|
+
injectRoute({
|
|
116
|
+
pattern: oauthCallbackRoute,
|
|
117
|
+
entrypoint: "decap-cms-oauth-astro/src/oauth/callback.ts"
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
updateConfig({
|
|
121
|
+
env,
|
|
122
|
+
vite: {
|
|
123
|
+
plugins: [
|
|
124
|
+
{
|
|
125
|
+
name: "decap-cms-config",
|
|
126
|
+
resolveId(id) {
|
|
127
|
+
if (id === "virtual:decap-cms-config") {
|
|
128
|
+
return "\0virtual:decap-cms-config";
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
load(id) {
|
|
132
|
+
if (id === "\0virtual:decap-cms-config") {
|
|
133
|
+
return `
|
|
134
|
+
export const configYaml = ${JSON.stringify(validatedConfigYaml)};
|
|
135
|
+
export const decapCMSSrcUrl = ${JSON.stringify(decapCMSSrcUrl)};
|
|
136
|
+
export const decapCMSVersion = ${JSON.stringify(decapCMSVersion)};
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
decapCMS as default
|
|
150
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "decap-cms-oauth-astro",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Add Decap CMS’s admin dashboard and a custom OAuth backend to your Astro project",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"astro-integration",
|
|
7
|
+
"decap-cms",
|
|
8
|
+
"oauth"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": "Spr_Aachen",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/Foxie-404/decap-cms-oauth-astro.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": "https://github.com/Foxie-404/decap-cms-oauth-astro/issues",
|
|
17
|
+
"homepage": "https://github.com/Foxie-404/decap-cms-oauth-astro",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"main": "./dist/main.cjs",
|
|
25
|
+
"module": "./dist/main.js",
|
|
26
|
+
"types": "./dist/decap-cms-oauth-astro.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/decap-cms-oauth-astro.d.ts",
|
|
30
|
+
"import": "./dist/main.js",
|
|
31
|
+
"require": "./dist/main.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./src/oauth/callback.ts": "./src/oauth/callback.ts",
|
|
34
|
+
"./src/oauth/index.ts": "./src/oauth/index.ts",
|
|
35
|
+
"./src/admin.astro": "./src/admin.astro",
|
|
36
|
+
"./src/config.ts": "./src/config.ts"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"dev": "vite build --watch",
|
|
40
|
+
"build": "pnpm run sync && tsc && vite build",
|
|
41
|
+
"prepublishOnly": "pnpm run build",
|
|
42
|
+
"sync": "pnpm astro sync"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/js-yaml": "^4.0.9",
|
|
46
|
+
"@types/node": "^22.10.2",
|
|
47
|
+
"astro": "^5.0.5",
|
|
48
|
+
"typescript": "^5.7.2",
|
|
49
|
+
"vite": "^6.0.3",
|
|
50
|
+
"vite-plugin-dts": "^4.3.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"astro": "^5.0.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"js-yaml": "^4.1.1"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/admin.astro
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { decapCMSVersion, decapCMSSrcUrl } from "virtual:decap-cms-config";
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!doctype html>
|
|
6
|
+
<html>
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="utf-8" />
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
|
+
<meta name="robots" content="noindex" />
|
|
11
|
+
<link href={`${Astro.url.pathname.replace(/\/$/, "")}/config.yml`} type="text/yaml" rel="cms-config-url" />
|
|
12
|
+
<title>Content Manager</title>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<!-- Include the script that builds the page and powers Decap CMS -->
|
|
16
|
+
<script is:inline src={decapCMSSrcUrl ? decapCMSSrcUrl : `https://unpkg.com/decap-cms@^${decapCMSVersion}/dist/decap-cms.js`}></script>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { APIRoute } from "astro";
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import { configYaml } from "virtual:decap-cms-config";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const GET: APIRoute = () => {
|
|
7
|
+
return new Response(configYaml, {
|
|
8
|
+
headers: {
|
|
9
|
+
"content-type": "text/yaml; charset=utf-8",
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
};
|
package/src/env.d.ts
ADDED
package/src/main.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { AstroConfig, AstroIntegration } from "astro";
|
|
2
|
+
import { envField } from "astro/config";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export interface DecapCMSOptions {
|
|
10
|
+
configPath?: string;
|
|
11
|
+
decapCMSSrcUrl?: string;
|
|
12
|
+
decapCMSVersion?: string;
|
|
13
|
+
adminDisabled?: boolean;
|
|
14
|
+
adminRoute?: string;
|
|
15
|
+
enable?: boolean;
|
|
16
|
+
oauthLoginRoute?: string;
|
|
17
|
+
oauthCallbackRoute?: string;
|
|
18
|
+
}
|
|
19
|
+
const defaultOptions: DecapCMSOptions = {
|
|
20
|
+
configPath: "public/admin/config.yml",
|
|
21
|
+
decapCMSSrcUrl: "",
|
|
22
|
+
decapCMSVersion: "3.3.3",
|
|
23
|
+
adminDisabled: false,
|
|
24
|
+
adminRoute: "/admin",
|
|
25
|
+
enable: true,
|
|
26
|
+
oauthLoginRoute: "/oauth",
|
|
27
|
+
oauthCallbackRoute: "/oauth/callback",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const WHITELIST = [
|
|
31
|
+
"backend",
|
|
32
|
+
"site_url",
|
|
33
|
+
"display_url",
|
|
34
|
+
"logo",
|
|
35
|
+
"logo_url",
|
|
36
|
+
"media_folder",
|
|
37
|
+
"public_folder",
|
|
38
|
+
"collections",
|
|
39
|
+
"publish_mode",
|
|
40
|
+
"show_preview_links",
|
|
41
|
+
"slug",
|
|
42
|
+
"local_backend",
|
|
43
|
+
"i18n",
|
|
44
|
+
"media_library",
|
|
45
|
+
"editor",
|
|
46
|
+
"search",
|
|
47
|
+
"locale",
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export default function decapCMS(options: DecapCMSOptions = {}): AstroIntegration {
|
|
51
|
+
const {
|
|
52
|
+
configPath,
|
|
53
|
+
decapCMSSrcUrl,
|
|
54
|
+
decapCMSVersion,
|
|
55
|
+
adminDisabled,
|
|
56
|
+
adminRoute,
|
|
57
|
+
enable,
|
|
58
|
+
oauthLoginRoute,
|
|
59
|
+
oauthCallbackRoute,
|
|
60
|
+
} = {
|
|
61
|
+
...defaultOptions,
|
|
62
|
+
...options,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (!adminRoute?.startsWith("/") || !oauthLoginRoute?.startsWith("/") || !oauthCallbackRoute?.startsWith("/")) {
|
|
66
|
+
throw new Error('`adminRoute`, `oauthLoginRoute` and `oauthCallbackRoute` options must start with "/"');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: "decap-cms-oauth-astro",
|
|
71
|
+
hooks: {
|
|
72
|
+
"astro:config:setup": async ({ injectRoute, updateConfig, config }) => {
|
|
73
|
+
const env: AstroConfig["env"] = { validateSecrets: true, schema: {} };
|
|
74
|
+
|
|
75
|
+
let validatedConfigYaml = "";
|
|
76
|
+
|
|
77
|
+
if (!adminDisabled) {
|
|
78
|
+
// Resolve config path
|
|
79
|
+
const rootDir = fileURLToPath(config.root);
|
|
80
|
+
let absoluteConfigPath = path.resolve(rootDir, configPath!);
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(absoluteConfigPath)) {
|
|
83
|
+
const fallbackPath = path.resolve(rootDir, "public/admin/config.yml");
|
|
84
|
+
if (fs.existsSync(fallbackPath)) {
|
|
85
|
+
absoluteConfigPath = fallbackPath;
|
|
86
|
+
} else {
|
|
87
|
+
throw new Error(`Decap CMS config file not found at ${configPath} or ${fallbackPath}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Read and validate config
|
|
92
|
+
try {
|
|
93
|
+
const fileContent = fs.readFileSync(absoluteConfigPath, "utf8");
|
|
94
|
+
const parsedConfig = yaml.load(fileContent) as Record<string, any>;
|
|
95
|
+
|
|
96
|
+
if (!parsedConfig.backend || !parsedConfig.collections) {
|
|
97
|
+
console.error("Decap CMS configuration is missing required fields: 'backend' or 'collections'. Admin dashboard will be disabled.");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Whitelist filtering
|
|
102
|
+
const filteredConfig: Record<string, any> = {};
|
|
103
|
+
for (const key of WHITELIST) {
|
|
104
|
+
if (key in parsedConfig) {
|
|
105
|
+
filteredConfig[key] = parsedConfig[key];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
validatedConfigYaml = yaml.dump(filteredConfig);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.error(`Failed to parse Decap CMS config: ${e}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// mount DecapCMS admin route
|
|
116
|
+
injectRoute({
|
|
117
|
+
pattern: adminRoute,
|
|
118
|
+
entrypoint: "decap-cms-oauth-astro/src/admin.astro",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// mount DecapCMS config route
|
|
122
|
+
injectRoute({
|
|
123
|
+
pattern: `${adminRoute}/config.yml`,
|
|
124
|
+
entrypoint: "decap-cms-oauth-astro/src/config.ts",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (enable) {
|
|
129
|
+
env.schema!.OAUTH_GITHUB_CLIENT_ID = envField.string({
|
|
130
|
+
context: "server",
|
|
131
|
+
access: "secret",
|
|
132
|
+
});
|
|
133
|
+
env.schema!.OAUTH_GITHUB_CLIENT_SECRET = envField.string({
|
|
134
|
+
context: "server",
|
|
135
|
+
access: "secret",
|
|
136
|
+
});
|
|
137
|
+
env.schema!.OAUTH_GITHUB_REPO_ID = envField.string({
|
|
138
|
+
context: "server",
|
|
139
|
+
access: "secret",
|
|
140
|
+
optional: true,
|
|
141
|
+
default: "",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// mount OAuth backend - sign in route
|
|
145
|
+
injectRoute({
|
|
146
|
+
pattern: oauthLoginRoute,
|
|
147
|
+
entrypoint: "decap-cms-oauth-astro/src/oauth/index.ts",
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// mount OAuth backend - callback route
|
|
151
|
+
injectRoute({
|
|
152
|
+
pattern: oauthCallbackRoute,
|
|
153
|
+
entrypoint: "decap-cms-oauth-astro/src/oauth/callback.ts",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// apply env schema & defaults
|
|
158
|
+
updateConfig({
|
|
159
|
+
env,
|
|
160
|
+
vite: {
|
|
161
|
+
plugins: [
|
|
162
|
+
{
|
|
163
|
+
name: "decap-cms-config",
|
|
164
|
+
resolveId(id) {
|
|
165
|
+
if (id === "virtual:decap-cms-config") {
|
|
166
|
+
return "\0virtual:decap-cms-config";
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
load(id) {
|
|
170
|
+
if (id === "\0virtual:decap-cms-config") {
|
|
171
|
+
return `
|
|
172
|
+
export const configYaml = ${JSON.stringify(validatedConfigYaml)};
|
|
173
|
+
export const decapCMSSrcUrl = ${JSON.stringify(decapCMSSrcUrl)};
|
|
174
|
+
export const decapCMSVersion = ${JSON.stringify(decapCMSVersion)};
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { APIRoute } from "astro";
|
|
2
|
+
import { OAUTH_GITHUB_CLIENT_ID, OAUTH_GITHUB_CLIENT_SECRET, OAUTH_GITHUB_REPO_ID } from "astro:env/server";
|
|
3
|
+
|
|
4
|
+
export const prerender = false;
|
|
5
|
+
|
|
6
|
+
export const GET: APIRoute = async ({ url, redirect }) => {
|
|
7
|
+
const data = {
|
|
8
|
+
code: url.searchParams.get("code"),
|
|
9
|
+
client_id: OAUTH_GITHUB_CLIENT_ID,
|
|
10
|
+
client_secret: OAUTH_GITHUB_CLIENT_SECRET,
|
|
11
|
+
...(OAUTH_GITHUB_REPO_ID ? { repository_id: OAUTH_GITHUB_REPO_ID } : {}),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch("https://github.com/login/oauth/access_token", {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: {
|
|
18
|
+
Accept: "application/json",
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify(data),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const body = await response.json();
|
|
29
|
+
|
|
30
|
+
const content = {
|
|
31
|
+
token: body.access_token,
|
|
32
|
+
provider: "github",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const script = `
|
|
36
|
+
<script>
|
|
37
|
+
const receiveMessage = (message) => {
|
|
38
|
+
window.opener.postMessage(
|
|
39
|
+
'authorization:${content.provider}:success:${JSON.stringify(content)}',
|
|
40
|
+
message.origin
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
window.removeEventListener("message", receiveMessage, false);
|
|
44
|
+
}
|
|
45
|
+
window.addEventListener("message", receiveMessage, false);
|
|
46
|
+
|
|
47
|
+
window.opener.postMessage("authorizing:${content.provider}", "*");
|
|
48
|
+
</script>
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
return new Response(script, {
|
|
52
|
+
headers: { "Content-Type": "text/html" },
|
|
53
|
+
});
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.log(err);
|
|
56
|
+
return redirect("/?error=😡");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { APIRoute } from "astro";
|
|
2
|
+
import { OAUTH_GITHUB_CLIENT_ID } from "astro:env/server";
|
|
3
|
+
|
|
4
|
+
export const prerender = false;
|
|
5
|
+
|
|
6
|
+
export const GET: APIRoute = ({ redirect }) => {
|
|
7
|
+
const params = new URLSearchParams({
|
|
8
|
+
client_id: OAUTH_GITHUB_CLIENT_ID,
|
|
9
|
+
scope: "repo,user",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return redirect(`https://github.com/login/oauth/authorize?${params.toString()}`);
|
|
13
|
+
};
|