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 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
+ }
@@ -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
@@ -0,0 +1,7 @@
1
+ /// <reference path="../.astro/types.d.ts" />
2
+
3
+ declare module "virtual:decap-cms-config" {
4
+ export const configYaml: string;
5
+ export const decapCMSSrcUrl: string | undefined;
6
+ export const decapCMSVersion: string;
7
+ }
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
+ };