nuxt-studio 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -0
- package/dist/app/main.d.ts +342 -0
- package/dist/app/main.js +38597 -0
- package/dist/app/mdc-import-BDMct_Gn.js +4 -0
- package/dist/app/service-worker.d.ts +3 -0
- package/dist/app/service-worker.js +118 -0
- package/dist/app/shared-D04791PA.js +25754 -0
- package/dist/app/shared-DI6F-ifc.js +25774 -0
- package/dist/app/shared-R5zYJ3Dl.js +25803 -0
- package/dist/app/shared.d.ts +22 -0
- package/dist/app/shared.js +6 -0
- package/dist/app/utils-DI6F-ifc.js +25774 -0
- package/dist/app/utils.d.ts +22 -0
- package/dist/app/utils.js +6 -0
- package/dist/module/module.d.mts +68 -0
- package/dist/module/module.json +10 -0
- package/dist/module/module.mjs +185 -0
- package/dist/module/runtime/composables/useMeta.d.ts +6 -0
- package/dist/module/runtime/composables/useMeta.js +17 -0
- package/dist/module/runtime/host.d.ts +2 -0
- package/dist/module/runtime/host.dev.d.ts +2 -0
- package/dist/module/runtime/host.dev.js +46 -0
- package/dist/module/runtime/host.js +257 -0
- package/dist/module/runtime/plugins/studio.client.d.ts +2 -0
- package/dist/module/runtime/plugins/studio.client.dev.d.ts +2 -0
- package/dist/module/runtime/plugins/studio.client.dev.js +23 -0
- package/dist/module/runtime/plugins/studio.client.js +11 -0
- package/dist/module/runtime/server/routes/admin.d.ts +2 -0
- package/dist/module/runtime/server/routes/admin.js +192 -0
- package/dist/module/runtime/server/routes/auth/github.get.d.ts +53 -0
- package/dist/module/runtime/server/routes/auth/github.get.js +155 -0
- package/dist/module/runtime/server/routes/auth/google.get.d.ts +2 -0
- package/dist/module/runtime/server/routes/auth/google.get.js +13 -0
- package/dist/module/runtime/server/routes/auth/session.delete.d.ts +4 -0
- package/dist/module/runtime/server/routes/auth/session.delete.js +10 -0
- package/dist/module/runtime/server/routes/auth/session.get.d.ts +4 -0
- package/dist/module/runtime/server/routes/auth/session.get.js +12 -0
- package/dist/module/runtime/server/routes/dev/content/[...path].d.ts +2 -0
- package/dist/module/runtime/server/routes/dev/content/[...path].js +57 -0
- package/dist/module/runtime/server/routes/dev/public/[...path].d.ts +8 -0
- package/dist/module/runtime/server/routes/dev/public/[...path].js +61 -0
- package/dist/module/runtime/server/routes/meta.d.ts +17 -0
- package/dist/module/runtime/server/routes/meta.js +39 -0
- package/dist/module/runtime/server/routes/sw.d.ts +2 -0
- package/dist/module/runtime/server/routes/sw.js +6 -0
- package/dist/module/runtime/utils/activation.d.ts +2 -0
- package/dist/module/runtime/utils/activation.js +23 -0
- package/dist/module/runtime/utils/collection.d.ts +22 -0
- package/dist/module/runtime/utils/collection.js +154 -0
- package/dist/module/runtime/utils/ensure.d.ts +1 -0
- package/dist/module/runtime/utils/ensure.js +22 -0
- package/dist/module/runtime/utils/object.d.ts +3 -0
- package/dist/module/runtime/utils/object.js +3 -0
- package/dist/module/runtime/utils/path-meta.d.ts +50 -0
- package/dist/module/runtime/utils/path-meta.js +49 -0
- package/dist/module/types.d.mts +7 -0
- package/package.json +100 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void> | "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>Content Studio</title>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <script>\n (function () {\n var storageListenerKey = 'studio-auth-popup'\n function navigateToGitHub() {\n window.location.assign('/__nuxt_studio/auth/github')\n }\n\n function notifyOpenerAndClose() {\n try {\n if (window.opener && !window.opener.closed) {\n window.opener.localStorage.setItem('temp-' + storageListenerKey, String(Date.now()))\n }\n } catch (_) {}\n setTimeout(function () { window.close() }, 100)\n }\n\n // If this page was opened as a popup with ?done=1, signal the opener and close\n var params = new URLSearchParams(window.location.search)\n if (params.get('done') === '1') {\n notifyOpenerAndClose()\n }\n\n window.addEventListener('DOMContentLoaded', function () {\n var btn = document.querySelector('.github-btn')\n if (btn) {\n btn.addEventListener('click', function (e) {\n e.preventDefault()\n navigateToGitHub()\n })\n }\n })\n })()\n </script>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n :root {\n --background: #0d1117;\n --surface: #161b22;\n --surface-hover: #21262d;\n --border: #30363d;\n --text-primary: #f0f6fc;\n --text-secondary: #8b949e;\n --accent: #238636;\n --accent-hover: #2ea043;\n --github: #24292f;\n --github-hover: #32383f;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;\n background: var(--background);\n color: var(--text-primary);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1.5;\n }\n\n .login-container {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 48px;\n width: 100%;\n max-width: 400px;\n box-shadow: 0 16px 32px rgba(1, 4, 9, 0.85);\n }\n\n .logo {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .logo img {\n width: 48px;\n height: 48px;\n }\n\n .header {\n text-align: center;\n margin-bottom: 16px;\n }\n\n .header h1 {\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n color: var(--text-primary);\n }\n\n .header p {\n color: var(--text-secondary);\n font-size: 16px;\n }\n\n .github-btn {\n width: 100%;\n background: var(--github);\n color: var(--text-primary);\n border: 1px solid var(--border);\n padding: 16px 20px;\n border-radius: 8px;\n font-size: 16px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n transition: all 0.2s ease;\n text-decoration: none;\n }\n\n .github-btn:hover {\n background: var(--github-hover);\n border-color: var(--text-secondary);\n transform: translateY(-1px);\n }\n\n .github-btn svg {\n width: 20px;\n height: 20px;\n fill: currentColor;\n }\n\n .footer {\n text-align: center;\n margin-top: 32px;\n padding-top: 24px;\n border-top: 1px solid var(--border);\n }\n\n .footer p {\n color: var(--text-secondary);\n font-size: 14px;\n }\n\n .footer a {\n color: #0969da;\n text-decoration: none;\n }\n\n .footer a:hover {\n text-decoration: underline;\n }\n\n @media (max-width: 480px) {\n .login-container {\n padding: 32px 24px;\n margin: 16px;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"login-container\">\n <div class=\"logo\">\n <img src=\"https://nuxt.com/assets/design-kit/icon-white.svg\" alt=\"Nuxt Logo\" />\n </div>\n\n <div class=\"header\">\n <h1>Nuxt Studio</h1>\n <p>Sign in to start editing your website.</p>\n </div>\n\n <a href=\"#\" class=\"github-btn\">\n <svg viewBox=\"0 0 16 16\" version=\"1.1\" aria-hidden=\"true\">\n <path d=\"M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z\"></path>\n </svg>\n Continue with GitHub\n </a>\n\n </div>\n</body>\n</html>">;
|
|
2
|
+
export default _default;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { eventHandler, sendRedirect } from "h3";
|
|
2
|
+
export default eventHandler((event) => {
|
|
3
|
+
return sendRedirect(event, "/__nuxt_studio/auth/github");
|
|
4
|
+
return `<!DOCTYPE html>
|
|
5
|
+
<html lang="en">
|
|
6
|
+
<head>
|
|
7
|
+
<title>Content Studio</title>
|
|
8
|
+
<meta charset="UTF-8">
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
10
|
+
<script>
|
|
11
|
+
(function () {
|
|
12
|
+
var storageListenerKey = 'studio-auth-popup'
|
|
13
|
+
function navigateToGitHub() {
|
|
14
|
+
window.location.assign('/__nuxt_studio/auth/github')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function notifyOpenerAndClose() {
|
|
18
|
+
try {
|
|
19
|
+
if (window.opener && !window.opener.closed) {
|
|
20
|
+
window.opener.localStorage.setItem('temp-' + storageListenerKey, String(Date.now()))
|
|
21
|
+
}
|
|
22
|
+
} catch (_) {}
|
|
23
|
+
setTimeout(function () { window.close() }, 100)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If this page was opened as a popup with ?done=1, signal the opener and close
|
|
27
|
+
var params = new URLSearchParams(window.location.search)
|
|
28
|
+
if (params.get('done') === '1') {
|
|
29
|
+
notifyOpenerAndClose()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
window.addEventListener('DOMContentLoaded', function () {
|
|
33
|
+
var btn = document.querySelector('.github-btn')
|
|
34
|
+
if (btn) {
|
|
35
|
+
btn.addEventListener('click', function (e) {
|
|
36
|
+
e.preventDefault()
|
|
37
|
+
navigateToGitHub()
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
})()
|
|
42
|
+
<\/script>
|
|
43
|
+
<style>
|
|
44
|
+
* {
|
|
45
|
+
margin: 0;
|
|
46
|
+
padding: 0;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
:root {
|
|
51
|
+
--background: #0d1117;
|
|
52
|
+
--surface: #161b22;
|
|
53
|
+
--surface-hover: #21262d;
|
|
54
|
+
--border: #30363d;
|
|
55
|
+
--text-primary: #f0f6fc;
|
|
56
|
+
--text-secondary: #8b949e;
|
|
57
|
+
--accent: #238636;
|
|
58
|
+
--accent-hover: #2ea043;
|
|
59
|
+
--github: #24292f;
|
|
60
|
+
--github-hover: #32383f;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
body {
|
|
64
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
|
|
65
|
+
background: var(--background);
|
|
66
|
+
color: var(--text-primary);
|
|
67
|
+
min-height: 100vh;
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
line-height: 1.5;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.login-container {
|
|
75
|
+
background: var(--surface);
|
|
76
|
+
border: 1px solid var(--border);
|
|
77
|
+
border-radius: 12px;
|
|
78
|
+
padding: 48px;
|
|
79
|
+
width: 100%;
|
|
80
|
+
max-width: 400px;
|
|
81
|
+
box-shadow: 0 16px 32px rgba(1, 4, 9, 0.85);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.logo {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.logo img {
|
|
91
|
+
width: 48px;
|
|
92
|
+
height: 48px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.header {
|
|
96
|
+
text-align: center;
|
|
97
|
+
margin-bottom: 16px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.header h1 {
|
|
101
|
+
font-size: 24px;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
margin-bottom: 8px;
|
|
104
|
+
color: var(--text-primary);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.header p {
|
|
108
|
+
color: var(--text-secondary);
|
|
109
|
+
font-size: 16px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.github-btn {
|
|
113
|
+
width: 100%;
|
|
114
|
+
background: var(--github);
|
|
115
|
+
color: var(--text-primary);
|
|
116
|
+
border: 1px solid var(--border);
|
|
117
|
+
padding: 16px 20px;
|
|
118
|
+
border-radius: 8px;
|
|
119
|
+
font-size: 16px;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
justify-content: center;
|
|
125
|
+
gap: 12px;
|
|
126
|
+
transition: all 0.2s ease;
|
|
127
|
+
text-decoration: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.github-btn:hover {
|
|
131
|
+
background: var(--github-hover);
|
|
132
|
+
border-color: var(--text-secondary);
|
|
133
|
+
transform: translateY(-1px);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.github-btn svg {
|
|
137
|
+
width: 20px;
|
|
138
|
+
height: 20px;
|
|
139
|
+
fill: currentColor;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.footer {
|
|
143
|
+
text-align: center;
|
|
144
|
+
margin-top: 32px;
|
|
145
|
+
padding-top: 24px;
|
|
146
|
+
border-top: 1px solid var(--border);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.footer p {
|
|
150
|
+
color: var(--text-secondary);
|
|
151
|
+
font-size: 14px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.footer a {
|
|
155
|
+
color: #0969da;
|
|
156
|
+
text-decoration: none;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.footer a:hover {
|
|
160
|
+
text-decoration: underline;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@media (max-width: 480px) {
|
|
164
|
+
.login-container {
|
|
165
|
+
padding: 32px 24px;
|
|
166
|
+
margin: 16px;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
170
|
+
</head>
|
|
171
|
+
<body>
|
|
172
|
+
<div class="login-container">
|
|
173
|
+
<div class="logo">
|
|
174
|
+
<img src="https://nuxt.com/assets/design-kit/icon-white.svg" alt="Nuxt Logo" />
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div class="header">
|
|
178
|
+
<h1>Nuxt Studio</h1>
|
|
179
|
+
<p>Sign in to start editing your website.</p>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<a href="#" class="github-btn">
|
|
183
|
+
<svg viewBox="0 0 16 16" version="1.1" aria-hidden="true">
|
|
184
|
+
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
|
|
185
|
+
</svg>
|
|
186
|
+
Continue with GitHub
|
|
187
|
+
</a>
|
|
188
|
+
|
|
189
|
+
</div>
|
|
190
|
+
</body>
|
|
191
|
+
</html>`;
|
|
192
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface OAuthGitHubConfig {
|
|
2
|
+
/**
|
|
3
|
+
* GitHub OAuth Client ID
|
|
4
|
+
* @default process.env.NUXT_OAUTH_GITHUB_CLIENT_ID
|
|
5
|
+
*/
|
|
6
|
+
clientId?: string;
|
|
7
|
+
/**
|
|
8
|
+
* GitHub OAuth Client Secret
|
|
9
|
+
* @default process.env.NUXT_OAUTH_GITHUB_CLIENT_SECRET
|
|
10
|
+
*/
|
|
11
|
+
clientSecret?: string;
|
|
12
|
+
/**
|
|
13
|
+
* GitHub OAuth Scope
|
|
14
|
+
* @default []
|
|
15
|
+
* @see https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps
|
|
16
|
+
* @example ['user:email']
|
|
17
|
+
*/
|
|
18
|
+
scope?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Require email from user, adds the ['user:email'] scope if not present
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
emailRequired?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* GitHub OAuth Authorization URL
|
|
26
|
+
* @default 'https://github.com/login/oauth/authorize'
|
|
27
|
+
*/
|
|
28
|
+
authorizationURL?: string;
|
|
29
|
+
/**
|
|
30
|
+
* GitHub OAuth Token URL
|
|
31
|
+
* @default 'https://github.com/login/oauth/access_token'
|
|
32
|
+
*/
|
|
33
|
+
tokenURL?: string;
|
|
34
|
+
/**
|
|
35
|
+
* GitHub API URL
|
|
36
|
+
* @default 'https://api.github.com'
|
|
37
|
+
*/
|
|
38
|
+
apiURL?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
41
|
+
* @see https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#1-request-a-users-github-identity
|
|
42
|
+
* @example { allow_signup: 'true' }
|
|
43
|
+
*/
|
|
44
|
+
authorizationParams?: Record<string, string>;
|
|
45
|
+
/**
|
|
46
|
+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
|
|
47
|
+
* @default process.env.NUXT_OAUTH_GITHUB_REDIRECT_URL
|
|
48
|
+
* @see https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/differences-between-github-apps-and-oauth-apps
|
|
49
|
+
*/
|
|
50
|
+
redirectURL?: string;
|
|
51
|
+
}
|
|
52
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
53
|
+
export default _default;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { FetchError } from "ofetch";
|
|
2
|
+
import { getRandomValues } from "uncrypto";
|
|
3
|
+
import { eventHandler, getQuery, sendRedirect, createError, getRequestURL, setCookie, deleteCookie, getCookie, useSession } from "h3";
|
|
4
|
+
import { withQuery } from "ufo";
|
|
5
|
+
import { defu } from "defu";
|
|
6
|
+
import { useRuntimeConfig } from "#imports";
|
|
7
|
+
export default eventHandler(async (event) => {
|
|
8
|
+
const config = defu(useRuntimeConfig(event).studio?.auth?.github, {
|
|
9
|
+
clientId: process.env.STUDIO_GITHUB_CLIENT_ID,
|
|
10
|
+
clientSecret: process.env.STUDIO_GITHUB_CLIENT_SECRET,
|
|
11
|
+
authorizationURL: "https://github.com/login/oauth/authorize",
|
|
12
|
+
tokenURL: "https://github.com/login/oauth/access_token",
|
|
13
|
+
apiURL: "https://api.github.com",
|
|
14
|
+
authorizationParams: {},
|
|
15
|
+
emailRequired: true
|
|
16
|
+
});
|
|
17
|
+
const query = getQuery(event);
|
|
18
|
+
if (query.error) {
|
|
19
|
+
throw createError({
|
|
20
|
+
statusCode: 401,
|
|
21
|
+
message: `GitHub login failed: ${query.error || "Unknown error"}`,
|
|
22
|
+
data: query
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (!config.clientId || !config.clientSecret) {
|
|
26
|
+
throw createError({
|
|
27
|
+
statusCode: 500,
|
|
28
|
+
message: "Missing GitHub client ID or secret",
|
|
29
|
+
data: config
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const requestURL = getRequestURL(event);
|
|
33
|
+
const redirectURL = `${requestURL.protocol}//${requestURL.host}${requestURL.pathname}`;
|
|
34
|
+
const state = await handleState(event);
|
|
35
|
+
if (!query.code) {
|
|
36
|
+
config.scope = config.scope || [];
|
|
37
|
+
if (config.emailRequired && !config.scope.includes("user:email")) {
|
|
38
|
+
config.scope.push("user:email");
|
|
39
|
+
}
|
|
40
|
+
if (config.emailRequired && !config.scope.includes("repo")) {
|
|
41
|
+
config.scope.push("repo");
|
|
42
|
+
}
|
|
43
|
+
return sendRedirect(
|
|
44
|
+
event,
|
|
45
|
+
withQuery(config.authorizationURL, {
|
|
46
|
+
client_id: config.clientId,
|
|
47
|
+
redirect_uri: redirectURL,
|
|
48
|
+
scope: config.scope.join(" "),
|
|
49
|
+
state,
|
|
50
|
+
...config.authorizationParams
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (query.state !== state) {
|
|
55
|
+
throw createError({
|
|
56
|
+
statusCode: 500,
|
|
57
|
+
message: "Invalid state",
|
|
58
|
+
data: {
|
|
59
|
+
query,
|
|
60
|
+
state
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const token = await requestAccessToken(config.tokenURL, {
|
|
65
|
+
body: {
|
|
66
|
+
grant_type: "authorization_code",
|
|
67
|
+
client_id: config.clientId,
|
|
68
|
+
client_secret: config.clientSecret,
|
|
69
|
+
redirect_uri: redirectURL,
|
|
70
|
+
code: query.code
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
if (token.error || !token.access_token) {
|
|
74
|
+
throw createError({
|
|
75
|
+
statusCode: 500,
|
|
76
|
+
message: "Failed to get access token",
|
|
77
|
+
data: token
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const accessToken = token.access_token;
|
|
81
|
+
const user = await $fetch(`${config.apiURL}/user`, {
|
|
82
|
+
headers: {
|
|
83
|
+
"User-Agent": `Github-OAuth-${config.clientId}`,
|
|
84
|
+
"Authorization": `token ${accessToken}`
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (!user.email && config.emailRequired) {
|
|
88
|
+
const emails = await $fetch(`${config.apiURL}/user/emails`, {
|
|
89
|
+
headers: {
|
|
90
|
+
"User-Agent": `Github-OAuth-${config.clientId}`,
|
|
91
|
+
"Authorization": `token ${accessToken}`
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
const primaryEmail = emails.find((email) => email.primary);
|
|
95
|
+
if (!primaryEmail) {
|
|
96
|
+
throw createError({
|
|
97
|
+
statusCode: 500,
|
|
98
|
+
message: "Could not get GitHub user email",
|
|
99
|
+
data: token
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
user.email = primaryEmail.email;
|
|
103
|
+
}
|
|
104
|
+
const session = await useSession(event, {
|
|
105
|
+
name: "studio-session",
|
|
106
|
+
password: useRuntimeConfig(event).studio?.auth?.sessionSecret
|
|
107
|
+
});
|
|
108
|
+
await session.update(defu({
|
|
109
|
+
user: {
|
|
110
|
+
contentUser: true,
|
|
111
|
+
githubId: user.id,
|
|
112
|
+
githubToken: token.access_token,
|
|
113
|
+
name: user.name,
|
|
114
|
+
avatar: user.avatar_url,
|
|
115
|
+
email: user.email,
|
|
116
|
+
provider: "github"
|
|
117
|
+
}
|
|
118
|
+
}, session.data));
|
|
119
|
+
return sendRedirect(event, "/");
|
|
120
|
+
});
|
|
121
|
+
async function requestAccessToken(url, options) {
|
|
122
|
+
const headers = {
|
|
123
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
124
|
+
...options.headers
|
|
125
|
+
};
|
|
126
|
+
const body = headers["Content-Type"] === "application/x-www-form-urlencoded" ? new URLSearchParams(
|
|
127
|
+
options.body || options.params || {}
|
|
128
|
+
).toString() : options.body;
|
|
129
|
+
return $fetch(url, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers,
|
|
132
|
+
body
|
|
133
|
+
}).catch((error) => {
|
|
134
|
+
if (error instanceof FetchError && error.status === 401) {
|
|
135
|
+
return error.data;
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function handleState(event) {
|
|
141
|
+
let state = getCookie(event, "nuxt-auth-state");
|
|
142
|
+
if (state) {
|
|
143
|
+
deleteCookie(event, "nuxt-auth-state");
|
|
144
|
+
return state;
|
|
145
|
+
}
|
|
146
|
+
state = encodeBase64Url(getRandomBytes(8));
|
|
147
|
+
setCookie(event, "nuxt-auth-state", state);
|
|
148
|
+
return state;
|
|
149
|
+
}
|
|
150
|
+
function encodeBase64Url(input) {
|
|
151
|
+
return btoa(String.fromCharCode.apply(null, Array.from(input))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
152
|
+
}
|
|
153
|
+
function getRandomBytes(size = 32) {
|
|
154
|
+
return getRandomValues(new Uint8Array(size));
|
|
155
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { eventHandler, createError } from "h3";
|
|
2
|
+
export default eventHandler(async () => {
|
|
3
|
+
if (!process.env.STUDIO_GITHUB_TOKEN) {
|
|
4
|
+
throw createError({
|
|
5
|
+
statusCode: 500,
|
|
6
|
+
message: "STUDIO_GITHUB_TOKEN is not set. Google authenticated user cannot push changes to the repository without a valid Github token."
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
throw createError({
|
|
10
|
+
statusCode: 500,
|
|
11
|
+
message: "Google OAuth is not implemented"
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { eventHandler, useSession } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
export default eventHandler(async (event) => {
|
|
4
|
+
const session = await useSession(event, {
|
|
5
|
+
name: "studio-session",
|
|
6
|
+
password: useRuntimeConfig(event).studio?.auth?.sessionSecret
|
|
7
|
+
});
|
|
8
|
+
await session.clear();
|
|
9
|
+
return { loggedOut: true };
|
|
10
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { eventHandler, useSession } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
export default eventHandler(async (event) => {
|
|
4
|
+
const session = await useSession(event, {
|
|
5
|
+
name: "studio-session",
|
|
6
|
+
password: useRuntimeConfig(event).studio?.auth?.sessionSecret
|
|
7
|
+
});
|
|
8
|
+
return {
|
|
9
|
+
...session.data,
|
|
10
|
+
id: session.id
|
|
11
|
+
};
|
|
12
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createError, eventHandler, getRequestHeader, readRawBody, setResponseHeader } from "h3";
|
|
2
|
+
import { stringifyMarkdown } from "@nuxtjs/mdc/runtime";
|
|
3
|
+
import { decompressTree } from "@nuxt/content/runtime";
|
|
4
|
+
import { removeReservedKeysFromDocument } from "nuxt-studio/app/utils";
|
|
5
|
+
import { useStorage } from "#imports";
|
|
6
|
+
export default eventHandler(async (event) => {
|
|
7
|
+
const path = event.path.replace("/__nuxt_studio/dev/content/", "");
|
|
8
|
+
const key = path.replace(/\//g, ":").replace(/^content:/, "");
|
|
9
|
+
const storage = useStorage("nuxt_studio_content");
|
|
10
|
+
if (event.method === "GET") {
|
|
11
|
+
const isRaw = getRequestHeader(event, "accept") === "application/octet-stream";
|
|
12
|
+
const driverValue = await (isRaw ? storage.getItemRaw(key) : storage.getItem(key));
|
|
13
|
+
if (driverValue === null) {
|
|
14
|
+
throw createError({
|
|
15
|
+
statusCode: 404,
|
|
16
|
+
statusMessage: "KV value not found"
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
setMetaHeaders(event, await storage.getMeta(key));
|
|
20
|
+
return isRaw ? driverValue : String(driverValue);
|
|
21
|
+
}
|
|
22
|
+
if (event.method === "PUT") {
|
|
23
|
+
if (getRequestHeader(event, "content-type") === "application/octet-stream") {
|
|
24
|
+
const value = await readRawBody(event, false);
|
|
25
|
+
await storage.setItemRaw(key, value);
|
|
26
|
+
} else if (getRequestHeader(event, "content-type") === "text/plain") {
|
|
27
|
+
const value = await readRawBody(event, "utf8");
|
|
28
|
+
await storage.setItem(key, value);
|
|
29
|
+
} else {
|
|
30
|
+
const value = await readRawBody(event, "utf8");
|
|
31
|
+
const json = JSON.parse(value || "{}");
|
|
32
|
+
const content = await stringifyMarkdown(
|
|
33
|
+
json.body.type === "minimark" ? decompressTree(json.body) : json.body,
|
|
34
|
+
removeReservedKeysFromDocument(json)
|
|
35
|
+
);
|
|
36
|
+
await storage.setItem(key, content);
|
|
37
|
+
}
|
|
38
|
+
return "OK";
|
|
39
|
+
}
|
|
40
|
+
if (event.method === "DELETE") {
|
|
41
|
+
await storage.removeItem(key);
|
|
42
|
+
return "OK";
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
function setMetaHeaders(event, meta) {
|
|
46
|
+
if (meta.mtime) {
|
|
47
|
+
setResponseHeader(
|
|
48
|
+
event,
|
|
49
|
+
"last-modified",
|
|
50
|
+
new Date(meta.mtime).toUTCString()
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (meta.ttl) {
|
|
54
|
+
setResponseHeader(event, "x-ttl", `${meta.ttl}`);
|
|
55
|
+
setResponseHeader(event, "cache-control", `max-age=${meta.ttl}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createError, eventHandler, getRequestHeader, readRawBody, setResponseHeader } from "h3";
|
|
2
|
+
import { useStorage } from "#imports";
|
|
3
|
+
export default eventHandler(async (event) => {
|
|
4
|
+
const path = event.path.replace("/__nuxt_studio/dev/public/", "");
|
|
5
|
+
const key = path.replace(/\//g, ":").replace(/^public-assets:/, "");
|
|
6
|
+
const storage = useStorage("nuxt_studio_public_assets");
|
|
7
|
+
if (event.method === "GET") {
|
|
8
|
+
const lastChar = key[key.length - 1];
|
|
9
|
+
const isBaseKey = lastChar === "/" || lastChar === ":";
|
|
10
|
+
if (isBaseKey) {
|
|
11
|
+
const keys = await storage.getKeys(key);
|
|
12
|
+
return keys.map((key2) => key2.replace(/:/g, "/"));
|
|
13
|
+
}
|
|
14
|
+
const item = await storage.getMeta(key);
|
|
15
|
+
if (!item) {
|
|
16
|
+
throw createError({
|
|
17
|
+
statusCode: 404,
|
|
18
|
+
statusMessage: "KV value not found"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
id: `public-assets/${key.replace(/:/g, "/")}`,
|
|
23
|
+
extension: key.split(".").pop(),
|
|
24
|
+
stem: key.split(".").join("."),
|
|
25
|
+
path: "/" + key.replace(/:/g, "/"),
|
|
26
|
+
version: new Date(item.mtime || /* @__PURE__ */ new Date()).getTime()
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (event.method === "PUT") {
|
|
30
|
+
if (getRequestHeader(event, "content-type") === "application/octet-stream") {
|
|
31
|
+
const value = await readRawBody(event, false);
|
|
32
|
+
await storage.setItemRaw(key, value);
|
|
33
|
+
} else if (getRequestHeader(event, "content-type") === "text/plain") {
|
|
34
|
+
const value = await readRawBody(event, "utf8");
|
|
35
|
+
await storage.setItem(key, value);
|
|
36
|
+
} else {
|
|
37
|
+
const value = await readRawBody(event, "utf8");
|
|
38
|
+
const json = JSON.parse(value || "{}");
|
|
39
|
+
const data = json.raw.split(";base64,")[1];
|
|
40
|
+
await storage.setItemRaw(key, Buffer.from(data, "base64"));
|
|
41
|
+
}
|
|
42
|
+
return "OK";
|
|
43
|
+
}
|
|
44
|
+
if (event.method === "DELETE") {
|
|
45
|
+
await storage.removeItem(key);
|
|
46
|
+
return "OK";
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
function setMetaHeaders(event, meta) {
|
|
50
|
+
if (meta.mtime) {
|
|
51
|
+
setResponseHeader(
|
|
52
|
+
event,
|
|
53
|
+
"last-modified",
|
|
54
|
+
new Date(meta.mtime).toUTCString()
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (meta.ttl) {
|
|
58
|
+
setResponseHeader(event, "x-ttl", `${meta.ttl}`);
|
|
59
|
+
setResponseHeader(event, "cache-control", `max-age=${meta.ttl}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
2
|
+
version: string;
|
|
3
|
+
gitInfo: any;
|
|
4
|
+
collections: any;
|
|
5
|
+
appConfigSchema: any;
|
|
6
|
+
appConfig: import("@nuxt/schema").AppConfig;
|
|
7
|
+
components: {
|
|
8
|
+
name: string;
|
|
9
|
+
path: string;
|
|
10
|
+
meta: {
|
|
11
|
+
props: import("vue-component-meta").PropertyMeta[];
|
|
12
|
+
slots: import("vue-component-meta").SlotMeta[];
|
|
13
|
+
events: import("vue-component-meta").EventMeta[];
|
|
14
|
+
};
|
|
15
|
+
}[];
|
|
16
|
+
}>>;
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { eventHandler, useSession } from "h3";
|
|
2
|
+
import { useRuntimeConfig, useAppConfig, createError } from "#imports";
|
|
3
|
+
import components from "#nuxt-component-meta/nitro";
|
|
4
|
+
import { collections, gitInfo, appConfigSchema } from "#content/preview";
|
|
5
|
+
export default eventHandler(async (event) => {
|
|
6
|
+
const session = await useSession(event, {
|
|
7
|
+
name: "studio-session",
|
|
8
|
+
password: useRuntimeConfig(event).studio?.auth?.sessionSecret
|
|
9
|
+
});
|
|
10
|
+
if (!session?.data?.user) {
|
|
11
|
+
throw createError({
|
|
12
|
+
statusCode: 404,
|
|
13
|
+
message: "Not found"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const mappedComponents = Object.values(components).map(({ pascalName, filePath, meta }) => {
|
|
17
|
+
return {
|
|
18
|
+
name: pascalName,
|
|
19
|
+
path: filePath,
|
|
20
|
+
meta: {
|
|
21
|
+
props: meta.props,
|
|
22
|
+
slots: meta.slots,
|
|
23
|
+
events: meta.events
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
const appConfig = useAppConfig();
|
|
28
|
+
const runtimeConfig = useRuntimeConfig();
|
|
29
|
+
const { content } = runtimeConfig;
|
|
30
|
+
const { version } = content;
|
|
31
|
+
return {
|
|
32
|
+
version,
|
|
33
|
+
gitInfo,
|
|
34
|
+
collections,
|
|
35
|
+
appConfigSchema,
|
|
36
|
+
appConfig,
|
|
37
|
+
components: mappedComponents
|
|
38
|
+
};
|
|
39
|
+
});
|