@cedarjs/cli 7.0.0-canary.3073 → 7.0.0-canary.3075
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.
|
@@ -75,12 +75,14 @@ const files = async ({
|
|
|
75
75
|
skipReset,
|
|
76
76
|
skipSignup,
|
|
77
77
|
webauthn,
|
|
78
|
+
oauth,
|
|
78
79
|
usernameLabel,
|
|
79
80
|
passwordLabel
|
|
80
81
|
}) => {
|
|
81
82
|
const filesList = [];
|
|
82
83
|
usernameLabel = usernameLabel || "username";
|
|
83
84
|
passwordLabel = passwordLabel || "password";
|
|
85
|
+
const oauthEnabled = oauth ?? isOAuthEnabled();
|
|
84
86
|
const templateVars = {
|
|
85
87
|
usernameLowerCase: usernameLabel.toLowerCase(),
|
|
86
88
|
usernameCamelCase: camelCase(usernameLabel),
|
|
@@ -110,7 +112,7 @@ const files = async ({
|
|
|
110
112
|
extension: typescript ? ".tsx" : ".jsx",
|
|
111
113
|
webPathSection: "pages",
|
|
112
114
|
generator: "dbAuth",
|
|
113
|
-
templatePath: webauthn
|
|
115
|
+
templatePath: loginTemplatePath(webauthn, oauthEnabled),
|
|
114
116
|
templateVars
|
|
115
117
|
})
|
|
116
118
|
);
|
|
@@ -136,7 +138,7 @@ const files = async ({
|
|
|
136
138
|
extension: typescript ? ".tsx" : ".jsx",
|
|
137
139
|
webPathSection: "pages",
|
|
138
140
|
generator: "dbAuth",
|
|
139
|
-
templatePath: "signup.tsx.template",
|
|
141
|
+
templatePath: oauthEnabled ? "signup.oauth.tsx.template" : "signup.tsx.template",
|
|
140
142
|
templateVars
|
|
141
143
|
})
|
|
142
144
|
);
|
|
@@ -378,6 +380,23 @@ function isWebAuthnEnabled() {
|
|
|
378
380
|
);
|
|
379
381
|
return webPackageJson.includes('"@simplewebauthn/browser": ');
|
|
380
382
|
}
|
|
383
|
+
function isOAuthEnabled() {
|
|
384
|
+
const apiPackageJsonPath = path.join(getPaths().base, "api", "package.json");
|
|
385
|
+
if (!fs.existsSync(apiPackageJsonPath)) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
const apiPackageJson = fs.readFileSync(apiPackageJsonPath, "utf-8");
|
|
389
|
+
return apiPackageJson.includes('"@cedarjs/auth-dbauth-oauth": ');
|
|
390
|
+
}
|
|
391
|
+
function loginTemplatePath(webauthn, oauth) {
|
|
392
|
+
if (webauthn) {
|
|
393
|
+
return "login.webAuthn.tsx.template";
|
|
394
|
+
}
|
|
395
|
+
if (oauth) {
|
|
396
|
+
return "login.oauth.tsx.template";
|
|
397
|
+
}
|
|
398
|
+
return "login.tsx.template";
|
|
399
|
+
}
|
|
381
400
|
export {
|
|
382
401
|
files,
|
|
383
402
|
handler
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Form,
|
|
5
|
+
Label,
|
|
6
|
+
TextField,
|
|
7
|
+
PasswordField,
|
|
8
|
+
Submit,
|
|
9
|
+
FieldError,
|
|
10
|
+
} from '@cedarjs/forms'
|
|
11
|
+
import { Link, navigate, routes } from '@cedarjs/router'
|
|
12
|
+
import { Metadata } from '@cedarjs/web'
|
|
13
|
+
import { toast, Toaster } from '@cedarjs/web/toast'
|
|
14
|
+
|
|
15
|
+
import { getOAuthUrl, getOAuthError } from '@cedarjs/auth-dbauth-web/oauth'
|
|
16
|
+
|
|
17
|
+
import { useAuth } from 'src/auth'
|
|
18
|
+
|
|
19
|
+
const LoginPage = () => {
|
|
20
|
+
const { isAuthenticated, logIn } = useAuth()
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (isAuthenticated) {
|
|
24
|
+
navigate(routes.home())
|
|
25
|
+
}
|
|
26
|
+
}, [isAuthenticated])
|
|
27
|
+
|
|
28
|
+
// Surface an error that the OAuth provider redirect sent back, e.g.
|
|
29
|
+
// after the user cancels the provider's consent screen.
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const oauthError = getOAuthError(window.location.search)
|
|
32
|
+
|
|
33
|
+
if (oauthError) {
|
|
34
|
+
toast.error('Could not log in: ' + oauthError)
|
|
35
|
+
}
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
38
|
+
const ${usernameCamelCase}Ref = useRef<HTMLInputElement>(null)
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
${usernameCamelCase}Ref.current?.focus()
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
const onSubmit = async (data: Record<string, string>) => {
|
|
44
|
+
const response = await logIn({ username: data.${usernameCamelCase}, password: data.${passwordCamelCase} })
|
|
45
|
+
|
|
46
|
+
if (response.message) {
|
|
47
|
+
toast(response.message)
|
|
48
|
+
} else if (response.error) {
|
|
49
|
+
toast.error(response.error)
|
|
50
|
+
} else {
|
|
51
|
+
toast.success('Welcome back!')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<Metadata title="Login" />
|
|
58
|
+
|
|
59
|
+
<main className="rw-main">
|
|
60
|
+
<Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
|
|
61
|
+
<div className="rw-scaffold rw-login-container">
|
|
62
|
+
<div className="rw-segment">
|
|
63
|
+
<header className="rw-segment-header">
|
|
64
|
+
<h2 className="rw-heading rw-heading-secondary">Login</h2>
|
|
65
|
+
</header>
|
|
66
|
+
|
|
67
|
+
<div className="rw-segment-main">
|
|
68
|
+
<div className="rw-form-wrapper">
|
|
69
|
+
<Form onSubmit={onSubmit} className="rw-form-wrapper">
|
|
70
|
+
<Label
|
|
71
|
+
name="${usernameCamelCase}"
|
|
72
|
+
className="rw-label"
|
|
73
|
+
errorClassName="rw-label rw-label-error"
|
|
74
|
+
>
|
|
75
|
+
${usernameTitleCase}
|
|
76
|
+
</Label>
|
|
77
|
+
<TextField
|
|
78
|
+
name="${usernameCamelCase}"
|
|
79
|
+
className="rw-input"
|
|
80
|
+
errorClassName="rw-input rw-input-error"
|
|
81
|
+
ref={${usernameCamelCase}Ref}
|
|
82
|
+
validation={{
|
|
83
|
+
required: {
|
|
84
|
+
value: true,
|
|
85
|
+
message: '${usernameTitleCase} is required',
|
|
86
|
+
},
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
<FieldError name="${usernameCamelCase}" className="rw-field-error" />
|
|
91
|
+
|
|
92
|
+
<Label
|
|
93
|
+
name="${passwordCamelCase}"
|
|
94
|
+
className="rw-label"
|
|
95
|
+
errorClassName="rw-label rw-label-error"
|
|
96
|
+
>
|
|
97
|
+
${passwordTitleCase}
|
|
98
|
+
</Label>
|
|
99
|
+
<PasswordField
|
|
100
|
+
name="${passwordCamelCase}"
|
|
101
|
+
className="rw-input"
|
|
102
|
+
errorClassName="rw-input rw-input-error"
|
|
103
|
+
autoComplete="current-password"
|
|
104
|
+
validation={{
|
|
105
|
+
required: {
|
|
106
|
+
value: true,
|
|
107
|
+
message: '${passwordTitleCase} is required',
|
|
108
|
+
},
|
|
109
|
+
}}
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
<div className="rw-forgot-link">
|
|
113
|
+
<Link
|
|
114
|
+
to={routes.forgotPassword()}
|
|
115
|
+
className="rw-forgot-link"
|
|
116
|
+
>
|
|
117
|
+
Forgot ${passwordTitleCase}?
|
|
118
|
+
</Link>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<FieldError name="${passwordCamelCase}" className="rw-field-error" />
|
|
122
|
+
|
|
123
|
+
<div className="rw-button-group">
|
|
124
|
+
<Submit className="rw-button rw-button-blue">Login</Submit>
|
|
125
|
+
</div>
|
|
126
|
+
</Form>
|
|
127
|
+
|
|
128
|
+
{/*
|
|
129
|
+
* OAuth login buttons. Delete the ones for providers you
|
|
130
|
+
* haven't configured in `cedar setup auth dbAuth --oauth`.
|
|
131
|
+
* Each link is a full-page navigation to the provider's
|
|
132
|
+
* authorize route — it must not be turned into a fetch/XHR
|
|
133
|
+
* call.
|
|
134
|
+
*/}
|
|
135
|
+
<div className="rw-button-group">
|
|
136
|
+
<a
|
|
137
|
+
href={getOAuthUrl('google', { flow: 'login' })}
|
|
138
|
+
className="rw-button rw-button-blue"
|
|
139
|
+
>
|
|
140
|
+
Continue with Google
|
|
141
|
+
</a>
|
|
142
|
+
<a
|
|
143
|
+
href={getOAuthUrl('github', { flow: 'login' })}
|
|
144
|
+
className="rw-button rw-button-blue"
|
|
145
|
+
>
|
|
146
|
+
Continue with GitHub
|
|
147
|
+
</a>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<div className="rw-login-link">
|
|
153
|
+
<span>Don't have an account?</span>{' '}
|
|
154
|
+
<Link to={routes.signup()} className="rw-link">
|
|
155
|
+
Sign up!
|
|
156
|
+
</Link>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</main>
|
|
160
|
+
</>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default LoginPage
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Form,
|
|
5
|
+
Label,
|
|
6
|
+
TextField,
|
|
7
|
+
PasswordField,
|
|
8
|
+
FieldError,
|
|
9
|
+
Submit,
|
|
10
|
+
} from '@cedarjs/forms'
|
|
11
|
+
import { Link, navigate, routes } from '@cedarjs/router'
|
|
12
|
+
import { Metadata } from '@cedarjs/web'
|
|
13
|
+
import { toast, Toaster } from '@cedarjs/web/toast'
|
|
14
|
+
|
|
15
|
+
import { getOAuthUrl } from '@cedarjs/auth-dbauth-web/oauth'
|
|
16
|
+
|
|
17
|
+
import { useAuth } from 'src/auth'
|
|
18
|
+
|
|
19
|
+
const SignupPage = () => {
|
|
20
|
+
const { isAuthenticated, signUp } = useAuth()
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (isAuthenticated) {
|
|
24
|
+
navigate(routes.home())
|
|
25
|
+
}
|
|
26
|
+
}, [isAuthenticated])
|
|
27
|
+
|
|
28
|
+
// focus on ${usernameLowerCase} box on page load
|
|
29
|
+
const ${usernameCamelCase}Ref = useRef<HTMLInputElement>(null)
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
${usernameCamelCase}Ref.current?.focus()
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
const onSubmit = async (data: Record<string, string>) => {
|
|
35
|
+
const response = await signUp({ username: data.${usernameCamelCase}, password: data.${passwordCamelCase} })
|
|
36
|
+
|
|
37
|
+
if (response.message) {
|
|
38
|
+
toast(response.message)
|
|
39
|
+
} else if (response.error) {
|
|
40
|
+
toast.error(response.error)
|
|
41
|
+
} else {
|
|
42
|
+
// user is signed in automatically
|
|
43
|
+
toast.success('Welcome!')
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<Metadata title="Signup" />
|
|
50
|
+
|
|
51
|
+
<main className="rw-main">
|
|
52
|
+
<Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
|
|
53
|
+
<div className="rw-scaffold rw-login-container">
|
|
54
|
+
<div className="rw-segment">
|
|
55
|
+
<header className="rw-segment-header">
|
|
56
|
+
<h2 className="rw-heading rw-heading-secondary">Signup</h2>
|
|
57
|
+
</header>
|
|
58
|
+
|
|
59
|
+
<div className="rw-segment-main">
|
|
60
|
+
<div className="rw-form-wrapper">
|
|
61
|
+
<Form onSubmit={onSubmit} className="rw-form-wrapper">
|
|
62
|
+
<Label
|
|
63
|
+
name="${usernameCamelCase}"
|
|
64
|
+
className="rw-label"
|
|
65
|
+
errorClassName="rw-label rw-label-error"
|
|
66
|
+
>
|
|
67
|
+
${usernameTitleCase}
|
|
68
|
+
</Label>
|
|
69
|
+
<TextField
|
|
70
|
+
name="${usernameCamelCase}"
|
|
71
|
+
className="rw-input"
|
|
72
|
+
errorClassName="rw-input rw-input-error"
|
|
73
|
+
ref={${usernameCamelCase}Ref}
|
|
74
|
+
validation={{
|
|
75
|
+
required: {
|
|
76
|
+
value: true,
|
|
77
|
+
message: '${usernameTitleCase} is required',
|
|
78
|
+
},
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
<FieldError name="${usernameCamelCase}" className="rw-field-error" />
|
|
82
|
+
|
|
83
|
+
<Label
|
|
84
|
+
name="${passwordCamelCase}"
|
|
85
|
+
className="rw-label"
|
|
86
|
+
errorClassName="rw-label rw-label-error"
|
|
87
|
+
>
|
|
88
|
+
${passwordTitleCase}
|
|
89
|
+
</Label>
|
|
90
|
+
<PasswordField
|
|
91
|
+
name="${passwordCamelCase}"
|
|
92
|
+
className="rw-input"
|
|
93
|
+
errorClassName="rw-input rw-input-error"
|
|
94
|
+
autoComplete="current-password"
|
|
95
|
+
validation={{
|
|
96
|
+
required: {
|
|
97
|
+
value: true,
|
|
98
|
+
message: '${passwordTitleCase} is required',
|
|
99
|
+
},
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
<FieldError name="${passwordCamelCase}" className="rw-field-error" />
|
|
103
|
+
|
|
104
|
+
<div className="rw-button-group">
|
|
105
|
+
<Submit className="rw-button rw-button-blue">
|
|
106
|
+
Sign Up
|
|
107
|
+
</Submit>
|
|
108
|
+
</div>
|
|
109
|
+
</Form>
|
|
110
|
+
|
|
111
|
+
{/*
|
|
112
|
+
* OAuth signup buttons. Delete the ones for providers you
|
|
113
|
+
* haven't configured in `cedar setup auth dbAuth --oauth`.
|
|
114
|
+
* Each link is a full-page navigation to the provider's
|
|
115
|
+
* authorize route — it must not be turned into a fetch/XHR
|
|
116
|
+
* call.
|
|
117
|
+
*/}
|
|
118
|
+
<div className="rw-button-group">
|
|
119
|
+
<a
|
|
120
|
+
href={getOAuthUrl('google', { flow: 'signup' })}
|
|
121
|
+
className="rw-button rw-button-blue"
|
|
122
|
+
>
|
|
123
|
+
Continue with Google
|
|
124
|
+
</a>
|
|
125
|
+
<a
|
|
126
|
+
href={getOAuthUrl('github', { flow: 'signup' })}
|
|
127
|
+
className="rw-button rw-button-blue"
|
|
128
|
+
>
|
|
129
|
+
Continue with GitHub
|
|
130
|
+
</a>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<div className="rw-login-link">
|
|
136
|
+
<span>Already have an account?</span>{' '}
|
|
137
|
+
<Link to={routes.login()} className="rw-link">
|
|
138
|
+
Log in!
|
|
139
|
+
</Link>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</main>
|
|
143
|
+
</>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default SignupPage
|
package/dist/commands/serve.js
CHANGED
|
@@ -126,9 +126,19 @@ const builder = async (yargs) => {
|
|
|
126
126
|
const targetPath = url.pathname.slice(apiUrl.length) || "/";
|
|
127
127
|
const root = apiRootPath === "/" ? "" : apiRootPath;
|
|
128
128
|
const targetUrl = `${apiTarget}${root}${targetPath}${url.search}`;
|
|
129
|
+
const forwardedHeaders = new Headers(req.headers);
|
|
130
|
+
if (!forwardedHeaders.has("x-forwarded-host")) {
|
|
131
|
+
forwardedHeaders.set(
|
|
132
|
+
"x-forwarded-host",
|
|
133
|
+
req.headers.get("host") || url.host
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (!forwardedHeaders.has("x-forwarded-proto")) {
|
|
137
|
+
forwardedHeaders.set("x-forwarded-proto", "http");
|
|
138
|
+
}
|
|
129
139
|
return fetch(targetUrl, {
|
|
130
140
|
method: req.method,
|
|
131
|
-
headers:
|
|
141
|
+
headers: forwardedHeaders,
|
|
132
142
|
body: req.body,
|
|
133
143
|
// @ts-expect-error - `duplex` is required when forwarding a
|
|
134
144
|
// request body via fetch (Node 18+).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/cli",
|
|
3
|
-
"version": "7.0.0-canary.
|
|
3
|
+
"version": "7.0.0-canary.3075",
|
|
4
4
|
"description": "The CedarJS Command Line",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,17 +36,17 @@
|
|
|
36
36
|
"@babel/preset-typescript": "7.29.7",
|
|
37
37
|
"@babel/traverse": "7.29.8",
|
|
38
38
|
"@babel/types": "7.29.8",
|
|
39
|
-
"@cedarjs/api-server": "7.0.0-canary.
|
|
40
|
-
"@cedarjs/babel-config": "7.0.0-canary.
|
|
41
|
-
"@cedarjs/cli-helpers": "7.0.0-canary.
|
|
42
|
-
"@cedarjs/internal": "7.0.0-canary.
|
|
43
|
-
"@cedarjs/prerender": "7.0.0-canary.
|
|
44
|
-
"@cedarjs/project-config": "7.0.0-canary.
|
|
45
|
-
"@cedarjs/structure": "7.0.0-canary.
|
|
46
|
-
"@cedarjs/telemetry": "7.0.0-canary.
|
|
47
|
-
"@cedarjs/utils": "7.0.0-canary.
|
|
48
|
-
"@cedarjs/vite": "7.0.0-canary.
|
|
49
|
-
"@cedarjs/web-server": "7.0.0-canary.
|
|
39
|
+
"@cedarjs/api-server": "7.0.0-canary.3075",
|
|
40
|
+
"@cedarjs/babel-config": "7.0.0-canary.3075",
|
|
41
|
+
"@cedarjs/cli-helpers": "7.0.0-canary.3075",
|
|
42
|
+
"@cedarjs/internal": "7.0.0-canary.3075",
|
|
43
|
+
"@cedarjs/prerender": "7.0.0-canary.3075",
|
|
44
|
+
"@cedarjs/project-config": "7.0.0-canary.3075",
|
|
45
|
+
"@cedarjs/structure": "7.0.0-canary.3075",
|
|
46
|
+
"@cedarjs/telemetry": "7.0.0-canary.3075",
|
|
47
|
+
"@cedarjs/utils": "7.0.0-canary.3075",
|
|
48
|
+
"@cedarjs/vite": "7.0.0-canary.3075",
|
|
49
|
+
"@cedarjs/web-server": "7.0.0-canary.3075",
|
|
50
50
|
"@listr2/prompt-adapter-enquirer": "4.3.1",
|
|
51
51
|
"@opentelemetry/api": "1.9.1",
|
|
52
52
|
"@opentelemetry/core": "1.30.1",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"yargs": "17.7.3"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@cedarjs/framework-tools": "7.0.0-canary.
|
|
97
|
+
"@cedarjs/framework-tools": "7.0.0-canary.3075",
|
|
98
98
|
"@prisma/dmmf": "7.10.0",
|
|
99
99
|
"@types/archiver": "^7.0.0",
|
|
100
100
|
"memfs": "4.68.2",
|