@_mustachio/openauth 0.6.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/dist/esm/client.js +186 -0
- package/dist/esm/css.d.js +0 -0
- package/dist/esm/error.js +73 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/issuer.js +558 -0
- package/dist/esm/jwt.js +16 -0
- package/dist/esm/keys.js +113 -0
- package/dist/esm/pkce.js +35 -0
- package/dist/esm/provider/apple.js +28 -0
- package/dist/esm/provider/arctic.js +43 -0
- package/dist/esm/provider/code.js +58 -0
- package/dist/esm/provider/cognito.js +16 -0
- package/dist/esm/provider/discord.js +15 -0
- package/dist/esm/provider/facebook.js +24 -0
- package/dist/esm/provider/github.js +15 -0
- package/dist/esm/provider/google.js +25 -0
- package/dist/esm/provider/index.js +3 -0
- package/dist/esm/provider/jumpcloud.js +15 -0
- package/dist/esm/provider/keycloak.js +15 -0
- package/dist/esm/provider/linkedin.js +15 -0
- package/dist/esm/provider/m2m.js +17 -0
- package/dist/esm/provider/microsoft.js +24 -0
- package/dist/esm/provider/oauth2.js +119 -0
- package/dist/esm/provider/oidc.js +69 -0
- package/dist/esm/provider/passkey.js +315 -0
- package/dist/esm/provider/password.js +306 -0
- package/dist/esm/provider/provider.js +10 -0
- package/dist/esm/provider/slack.js +15 -0
- package/dist/esm/provider/spotify.js +15 -0
- package/dist/esm/provider/twitch.js +15 -0
- package/dist/esm/provider/x.js +16 -0
- package/dist/esm/provider/yahoo.js +15 -0
- package/dist/esm/random.js +27 -0
- package/dist/esm/storage/aws.js +39 -0
- package/dist/esm/storage/cloudflare.js +42 -0
- package/dist/esm/storage/dynamo.js +116 -0
- package/dist/esm/storage/memory.js +88 -0
- package/dist/esm/storage/storage.js +36 -0
- package/dist/esm/subject.js +7 -0
- package/dist/esm/ui/base.js +407 -0
- package/dist/esm/ui/code.js +151 -0
- package/dist/esm/ui/form.js +43 -0
- package/dist/esm/ui/icon.js +92 -0
- package/dist/esm/ui/passkey.js +329 -0
- package/dist/esm/ui/password.js +338 -0
- package/dist/esm/ui/select.js +187 -0
- package/dist/esm/ui/theme.js +115 -0
- package/dist/esm/util.js +54 -0
- package/dist/types/client.d.ts +466 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/error.d.ts +77 -0
- package/dist/types/error.d.ts.map +1 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/issuer.d.ts +465 -0
- package/dist/types/issuer.d.ts.map +1 -0
- package/dist/types/jwt.d.ts +6 -0
- package/dist/types/jwt.d.ts.map +1 -0
- package/dist/types/keys.d.ts +18 -0
- package/dist/types/keys.d.ts.map +1 -0
- package/dist/types/pkce.d.ts +7 -0
- package/dist/types/pkce.d.ts.map +1 -0
- package/dist/types/provider/apple.d.ts +108 -0
- package/dist/types/provider/apple.d.ts.map +1 -0
- package/dist/types/provider/arctic.d.ts +16 -0
- package/dist/types/provider/arctic.d.ts.map +1 -0
- package/dist/types/provider/code.d.ts +74 -0
- package/dist/types/provider/code.d.ts.map +1 -0
- package/dist/types/provider/cognito.d.ts +64 -0
- package/dist/types/provider/cognito.d.ts.map +1 -0
- package/dist/types/provider/discord.d.ts +38 -0
- package/dist/types/provider/discord.d.ts.map +1 -0
- package/dist/types/provider/facebook.d.ts +74 -0
- package/dist/types/provider/facebook.d.ts.map +1 -0
- package/dist/types/provider/github.d.ts +38 -0
- package/dist/types/provider/github.d.ts.map +1 -0
- package/dist/types/provider/google.d.ts +74 -0
- package/dist/types/provider/google.d.ts.map +1 -0
- package/dist/types/provider/index.d.ts +4 -0
- package/dist/types/provider/index.d.ts.map +1 -0
- package/dist/types/provider/jumpcloud.d.ts +38 -0
- package/dist/types/provider/jumpcloud.d.ts.map +1 -0
- package/dist/types/provider/keycloak.d.ts +67 -0
- package/dist/types/provider/keycloak.d.ts.map +1 -0
- package/dist/types/provider/linkedin.d.ts +6 -0
- package/dist/types/provider/linkedin.d.ts.map +1 -0
- package/dist/types/provider/m2m.d.ts +34 -0
- package/dist/types/provider/m2m.d.ts.map +1 -0
- package/dist/types/provider/microsoft.d.ts +89 -0
- package/dist/types/provider/microsoft.d.ts.map +1 -0
- package/dist/types/provider/oauth2.d.ts +133 -0
- package/dist/types/provider/oauth2.d.ts.map +1 -0
- package/dist/types/provider/oidc.d.ts +91 -0
- package/dist/types/provider/oidc.d.ts.map +1 -0
- package/dist/types/provider/passkey.d.ts +143 -0
- package/dist/types/provider/passkey.d.ts.map +1 -0
- package/dist/types/provider/password.d.ts +210 -0
- package/dist/types/provider/password.d.ts.map +1 -0
- package/dist/types/provider/provider.d.ts +29 -0
- package/dist/types/provider/provider.d.ts.map +1 -0
- package/dist/types/provider/slack.d.ts +59 -0
- package/dist/types/provider/slack.d.ts.map +1 -0
- package/dist/types/provider/spotify.d.ts +38 -0
- package/dist/types/provider/spotify.d.ts.map +1 -0
- package/dist/types/provider/twitch.d.ts +38 -0
- package/dist/types/provider/twitch.d.ts.map +1 -0
- package/dist/types/provider/x.d.ts +38 -0
- package/dist/types/provider/x.d.ts.map +1 -0
- package/dist/types/provider/yahoo.d.ts +38 -0
- package/dist/types/provider/yahoo.d.ts.map +1 -0
- package/dist/types/random.d.ts +3 -0
- package/dist/types/random.d.ts.map +1 -0
- package/dist/types/storage/aws.d.ts +4 -0
- package/dist/types/storage/aws.d.ts.map +1 -0
- package/dist/types/storage/cloudflare.d.ts +34 -0
- package/dist/types/storage/cloudflare.d.ts.map +1 -0
- package/dist/types/storage/dynamo.d.ts +65 -0
- package/dist/types/storage/dynamo.d.ts.map +1 -0
- package/dist/types/storage/memory.d.ts +49 -0
- package/dist/types/storage/memory.d.ts.map +1 -0
- package/dist/types/storage/storage.d.ts +15 -0
- package/dist/types/storage/storage.d.ts.map +1 -0
- package/dist/types/subject.d.ts +122 -0
- package/dist/types/subject.d.ts.map +1 -0
- package/dist/types/ui/base.d.ts +5 -0
- package/dist/types/ui/base.d.ts.map +1 -0
- package/dist/types/ui/code.d.ts +104 -0
- package/dist/types/ui/code.d.ts.map +1 -0
- package/dist/types/ui/form.d.ts +6 -0
- package/dist/types/ui/form.d.ts.map +1 -0
- package/dist/types/ui/icon.d.ts +6 -0
- package/dist/types/ui/icon.d.ts.map +1 -0
- package/dist/types/ui/passkey.d.ts +5 -0
- package/dist/types/ui/passkey.d.ts.map +1 -0
- package/dist/types/ui/password.d.ts +139 -0
- package/dist/types/ui/password.d.ts.map +1 -0
- package/dist/types/ui/select.d.ts +55 -0
- package/dist/types/ui/select.d.ts.map +1 -0
- package/dist/types/ui/theme.d.ts +207 -0
- package/dist/types/ui/theme.d.ts.map +1 -0
- package/dist/types/util.d.ts +8 -0
- package/dist/types/util.d.ts.map +1 -0
- package/package.json +51 -0
- package/src/client.ts +749 -0
- package/src/css.d.ts +4 -0
- package/src/error.ts +120 -0
- package/src/index.ts +26 -0
- package/src/issuer.ts +1302 -0
- package/src/jwt.ts +17 -0
- package/src/keys.ts +139 -0
- package/src/pkce.ts +40 -0
- package/src/provider/apple.ts +127 -0
- package/src/provider/arctic.ts +66 -0
- package/src/provider/code.ts +227 -0
- package/src/provider/cognito.ts +74 -0
- package/src/provider/discord.ts +45 -0
- package/src/provider/facebook.ts +84 -0
- package/src/provider/github.ts +45 -0
- package/src/provider/google.ts +85 -0
- package/src/provider/index.ts +3 -0
- package/src/provider/jumpcloud.ts +45 -0
- package/src/provider/keycloak.ts +75 -0
- package/src/provider/linkedin.ts +12 -0
- package/src/provider/m2m.ts +56 -0
- package/src/provider/microsoft.ts +100 -0
- package/src/provider/oauth2.ts +297 -0
- package/src/provider/oidc.ts +179 -0
- package/src/provider/passkey.ts +655 -0
- package/src/provider/password.ts +672 -0
- package/src/provider/provider.ts +33 -0
- package/src/provider/slack.ts +67 -0
- package/src/provider/spotify.ts +45 -0
- package/src/provider/twitch.ts +45 -0
- package/src/provider/x.ts +46 -0
- package/src/provider/yahoo.ts +45 -0
- package/src/random.ts +24 -0
- package/src/storage/aws.ts +59 -0
- package/src/storage/cloudflare.ts +77 -0
- package/src/storage/dynamo.ts +193 -0
- package/src/storage/memory.ts +135 -0
- package/src/storage/storage.ts +46 -0
- package/src/subject.ts +130 -0
- package/src/ui/base.tsx +118 -0
- package/src/ui/code.tsx +215 -0
- package/src/ui/form.tsx +40 -0
- package/src/ui/icon.tsx +95 -0
- package/src/ui/passkey.tsx +321 -0
- package/src/ui/password.tsx +405 -0
- package/src/ui/select.tsx +221 -0
- package/src/ui/theme.ts +319 -0
- package/src/ui/ui.css +252 -0
- package/src/util.ts +58 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configure the UI that's used by the Password provider.
|
|
3
|
+
*
|
|
4
|
+
* ```ts {1,7-12}
|
|
5
|
+
* import { PasswordUI } from "@openauthjs/openauth/ui/password"
|
|
6
|
+
* import { PasswordProvider } from "@openauthjs/openauth/provider/password"
|
|
7
|
+
*
|
|
8
|
+
* export default issuer({
|
|
9
|
+
* providers: {
|
|
10
|
+
* password: PasswordAdapter(
|
|
11
|
+
* PasswordUI({
|
|
12
|
+
* copy: {
|
|
13
|
+
* error_email_taken: "This email is already taken."
|
|
14
|
+
* },
|
|
15
|
+
* sendCode: (email, code) => console.log(email, code)
|
|
16
|
+
* })
|
|
17
|
+
* )
|
|
18
|
+
* },
|
|
19
|
+
* // ...
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @packageDocumentation
|
|
24
|
+
*/
|
|
25
|
+
/** @jsxImportSource hono/jsx */
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
PasswordChangeError,
|
|
29
|
+
PasswordConfig,
|
|
30
|
+
PasswordLoginError,
|
|
31
|
+
PasswordRegisterError,
|
|
32
|
+
} from "../provider/password.js"
|
|
33
|
+
import { Layout } from "./base.js"
|
|
34
|
+
import "./form.js"
|
|
35
|
+
import { FormAlert } from "./form.js"
|
|
36
|
+
|
|
37
|
+
const DEFAULT_COPY = {
|
|
38
|
+
/**
|
|
39
|
+
* Error message when email is already taken.
|
|
40
|
+
*/
|
|
41
|
+
error_email_taken: "There is already an account with this email.",
|
|
42
|
+
/**
|
|
43
|
+
* Error message when the confirmation code is incorrect.
|
|
44
|
+
*/
|
|
45
|
+
error_invalid_code: "Code is incorrect.",
|
|
46
|
+
/**
|
|
47
|
+
* Error message when the email is invalid.
|
|
48
|
+
*/
|
|
49
|
+
error_invalid_email: "Email is not valid.",
|
|
50
|
+
/**
|
|
51
|
+
* Error message when the password is incorrect.
|
|
52
|
+
*/
|
|
53
|
+
error_invalid_password: "Password is incorrect.",
|
|
54
|
+
/**
|
|
55
|
+
* Error message when the passwords do not match.
|
|
56
|
+
*/
|
|
57
|
+
error_password_mismatch: "Passwords do not match.",
|
|
58
|
+
/**
|
|
59
|
+
* Error message when the user enters a password that fails validation.
|
|
60
|
+
*/
|
|
61
|
+
error_validation_error: "Password does not meet requirements.",
|
|
62
|
+
/**
|
|
63
|
+
* Title of the register page.
|
|
64
|
+
*/
|
|
65
|
+
register_title: "Welcome to the app",
|
|
66
|
+
/**
|
|
67
|
+
* Description of the register page.
|
|
68
|
+
*/
|
|
69
|
+
register_description: "Sign in with your email",
|
|
70
|
+
/**
|
|
71
|
+
* Title of the login page.
|
|
72
|
+
*/
|
|
73
|
+
login_title: "Welcome to the app",
|
|
74
|
+
/**
|
|
75
|
+
* Description of the login page.
|
|
76
|
+
*/
|
|
77
|
+
login_description: "Sign in with your email",
|
|
78
|
+
/**
|
|
79
|
+
* Copy for the register button.
|
|
80
|
+
*/
|
|
81
|
+
register: "Register",
|
|
82
|
+
/**
|
|
83
|
+
* Copy for the register link.
|
|
84
|
+
*/
|
|
85
|
+
register_prompt: "Don't have an account?",
|
|
86
|
+
/**
|
|
87
|
+
* Copy for the login link.
|
|
88
|
+
*/
|
|
89
|
+
login_prompt: "Already have an account?",
|
|
90
|
+
/**
|
|
91
|
+
* Copy for the login button.
|
|
92
|
+
*/
|
|
93
|
+
login: "Login",
|
|
94
|
+
/**
|
|
95
|
+
* Copy for the forgot password link.
|
|
96
|
+
*/
|
|
97
|
+
change_prompt: "Forgot password?",
|
|
98
|
+
/**
|
|
99
|
+
* Copy for the resend code button.
|
|
100
|
+
*/
|
|
101
|
+
code_resend: "Resend code",
|
|
102
|
+
/**
|
|
103
|
+
* Copy for the "Back to" link.
|
|
104
|
+
*/
|
|
105
|
+
code_return: "Back to",
|
|
106
|
+
/**
|
|
107
|
+
* Copy for the logo.
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
logo: "A",
|
|
111
|
+
/**
|
|
112
|
+
* Copy for the email input.
|
|
113
|
+
*/
|
|
114
|
+
input_email: "Email",
|
|
115
|
+
/**
|
|
116
|
+
* Copy for the password input.
|
|
117
|
+
*/
|
|
118
|
+
input_password: "Password",
|
|
119
|
+
/**
|
|
120
|
+
* Copy for the code input.
|
|
121
|
+
*/
|
|
122
|
+
input_code: "Code",
|
|
123
|
+
/**
|
|
124
|
+
* Copy for the repeat password input.
|
|
125
|
+
*/
|
|
126
|
+
input_repeat: "Repeat password",
|
|
127
|
+
/**
|
|
128
|
+
* Copy for the continue button.
|
|
129
|
+
*/
|
|
130
|
+
button_continue: "Continue",
|
|
131
|
+
} satisfies {
|
|
132
|
+
[key in `error_${
|
|
133
|
+
| PasswordLoginError["type"]
|
|
134
|
+
| PasswordRegisterError["type"]
|
|
135
|
+
| PasswordChangeError["type"]}`]: string
|
|
136
|
+
} & Record<string, string>
|
|
137
|
+
|
|
138
|
+
type PasswordUICopy = typeof DEFAULT_COPY
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Configure the password UI.
|
|
142
|
+
*/
|
|
143
|
+
export interface PasswordUIOptions extends Pick<
|
|
144
|
+
PasswordConfig,
|
|
145
|
+
"sendCode" | "validatePassword"
|
|
146
|
+
> {
|
|
147
|
+
/**
|
|
148
|
+
* Custom copy for the UI.
|
|
149
|
+
*/
|
|
150
|
+
copy?: Partial<PasswordUICopy>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Creates a UI for the Password provider flow.
|
|
155
|
+
* @param input - Configure the UI.
|
|
156
|
+
*/
|
|
157
|
+
export function PasswordUI(input: PasswordUIOptions): PasswordConfig {
|
|
158
|
+
const copy = {
|
|
159
|
+
...DEFAULT_COPY,
|
|
160
|
+
...input.copy,
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
validatePassword: input.validatePassword,
|
|
164
|
+
sendCode: input.sendCode,
|
|
165
|
+
login: async (_req, form, error): Promise<Response> => {
|
|
166
|
+
const jsx = (
|
|
167
|
+
<Layout>
|
|
168
|
+
<form data-component="form" method="post">
|
|
169
|
+
<FormAlert message={error?.type && copy?.[`error_${error.type}`]} />
|
|
170
|
+
<input
|
|
171
|
+
data-component="input"
|
|
172
|
+
type="email"
|
|
173
|
+
name="email"
|
|
174
|
+
required
|
|
175
|
+
placeholder={copy.input_email}
|
|
176
|
+
autofocus={!error}
|
|
177
|
+
value={form?.get("email")?.toString()}
|
|
178
|
+
/>
|
|
179
|
+
<input
|
|
180
|
+
data-component="input"
|
|
181
|
+
autofocus={error?.type === "invalid_password"}
|
|
182
|
+
required
|
|
183
|
+
type="password"
|
|
184
|
+
name="password"
|
|
185
|
+
placeholder={copy.input_password}
|
|
186
|
+
autoComplete="current-password"
|
|
187
|
+
/>
|
|
188
|
+
<button data-component="button">{copy.button_continue}</button>
|
|
189
|
+
<div data-component="form-footer">
|
|
190
|
+
<span>
|
|
191
|
+
{copy.register_prompt}{" "}
|
|
192
|
+
<a data-component="link" href="register">
|
|
193
|
+
{copy.register}
|
|
194
|
+
</a>
|
|
195
|
+
</span>
|
|
196
|
+
<a data-component="link" href="change">
|
|
197
|
+
{copy.change_prompt}
|
|
198
|
+
</a>
|
|
199
|
+
</div>
|
|
200
|
+
</form>
|
|
201
|
+
</Layout>
|
|
202
|
+
)
|
|
203
|
+
return new Response(jsx.toString(), {
|
|
204
|
+
status: error ? 401 : 200,
|
|
205
|
+
headers: {
|
|
206
|
+
"Content-Type": "text/html",
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
},
|
|
210
|
+
register: async (_req, state, form, error): Promise<Response> => {
|
|
211
|
+
const emailError = ["invalid_email", "email_taken"].includes(
|
|
212
|
+
error?.type || "",
|
|
213
|
+
)
|
|
214
|
+
const passwordError = [
|
|
215
|
+
"invalid_password",
|
|
216
|
+
"password_mismatch",
|
|
217
|
+
"validation_error",
|
|
218
|
+
].includes(error?.type || "")
|
|
219
|
+
const jsx = (
|
|
220
|
+
<Layout>
|
|
221
|
+
<form data-component="form" method="post">
|
|
222
|
+
<FormAlert
|
|
223
|
+
message={
|
|
224
|
+
error?.type
|
|
225
|
+
? error.type === "validation_error"
|
|
226
|
+
? (error.message ?? copy?.[`error_${error.type}`])
|
|
227
|
+
: copy?.[`error_${error.type}`]
|
|
228
|
+
: undefined
|
|
229
|
+
}
|
|
230
|
+
/>
|
|
231
|
+
{state.type === "start" && (
|
|
232
|
+
<>
|
|
233
|
+
<input type="hidden" name="action" value="register" />
|
|
234
|
+
<input
|
|
235
|
+
data-component="input"
|
|
236
|
+
autofocus={!error || emailError}
|
|
237
|
+
type="email"
|
|
238
|
+
name="email"
|
|
239
|
+
value={!emailError ? form?.get("email")?.toString() : ""}
|
|
240
|
+
required
|
|
241
|
+
placeholder={copy.input_email}
|
|
242
|
+
/>
|
|
243
|
+
<input
|
|
244
|
+
data-component="input"
|
|
245
|
+
autofocus={passwordError}
|
|
246
|
+
type="password"
|
|
247
|
+
name="password"
|
|
248
|
+
placeholder={copy.input_password}
|
|
249
|
+
required
|
|
250
|
+
value={
|
|
251
|
+
!passwordError ? form?.get("password")?.toString() : ""
|
|
252
|
+
}
|
|
253
|
+
autoComplete="new-password"
|
|
254
|
+
/>
|
|
255
|
+
<input
|
|
256
|
+
data-component="input"
|
|
257
|
+
type="password"
|
|
258
|
+
name="repeat"
|
|
259
|
+
required
|
|
260
|
+
autofocus={passwordError}
|
|
261
|
+
placeholder={copy.input_repeat}
|
|
262
|
+
autoComplete="new-password"
|
|
263
|
+
/>
|
|
264
|
+
<button data-component="button">{copy.button_continue}</button>
|
|
265
|
+
<div data-component="form-footer">
|
|
266
|
+
<span>
|
|
267
|
+
{copy.login_prompt}{" "}
|
|
268
|
+
<a data-component="link" href="authorize">
|
|
269
|
+
{copy.login}
|
|
270
|
+
</a>
|
|
271
|
+
</span>
|
|
272
|
+
</div>
|
|
273
|
+
</>
|
|
274
|
+
)}
|
|
275
|
+
|
|
276
|
+
{state.type === "code" && (
|
|
277
|
+
<>
|
|
278
|
+
<input type="hidden" name="action" value="verify" />
|
|
279
|
+
<input
|
|
280
|
+
data-component="input"
|
|
281
|
+
autofocus
|
|
282
|
+
name="code"
|
|
283
|
+
minLength={6}
|
|
284
|
+
maxLength={6}
|
|
285
|
+
required
|
|
286
|
+
placeholder={copy.input_code}
|
|
287
|
+
autoComplete="one-time-code"
|
|
288
|
+
/>
|
|
289
|
+
<button data-component="button">{copy.button_continue}</button>
|
|
290
|
+
</>
|
|
291
|
+
)}
|
|
292
|
+
</form>
|
|
293
|
+
</Layout>
|
|
294
|
+
) as string
|
|
295
|
+
return new Response(jsx.toString(), {
|
|
296
|
+
headers: {
|
|
297
|
+
"Content-Type": "text/html",
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
},
|
|
301
|
+
change: async (_req, state, form, error): Promise<Response> => {
|
|
302
|
+
const passwordError = [
|
|
303
|
+
"invalid_password",
|
|
304
|
+
"password_mismatch",
|
|
305
|
+
"validation_error",
|
|
306
|
+
].includes(error?.type || "")
|
|
307
|
+
const jsx = (
|
|
308
|
+
<Layout>
|
|
309
|
+
<form data-component="form" method="post" replace>
|
|
310
|
+
<FormAlert
|
|
311
|
+
message={
|
|
312
|
+
error?.type
|
|
313
|
+
? error.type === "validation_error"
|
|
314
|
+
? (error.message ?? copy?.[`error_${error.type}`])
|
|
315
|
+
: copy?.[`error_${error.type}`]
|
|
316
|
+
: undefined
|
|
317
|
+
}
|
|
318
|
+
/>
|
|
319
|
+
{state.type === "start" && (
|
|
320
|
+
<>
|
|
321
|
+
<input type="hidden" name="action" value="code" />
|
|
322
|
+
<input
|
|
323
|
+
data-component="input"
|
|
324
|
+
autofocus
|
|
325
|
+
type="email"
|
|
326
|
+
name="email"
|
|
327
|
+
required
|
|
328
|
+
value={form?.get("email")?.toString()}
|
|
329
|
+
placeholder={copy.input_email}
|
|
330
|
+
/>
|
|
331
|
+
</>
|
|
332
|
+
)}
|
|
333
|
+
{state.type === "code" && (
|
|
334
|
+
<>
|
|
335
|
+
<input type="hidden" name="action" value="verify" />
|
|
336
|
+
<input
|
|
337
|
+
data-component="input"
|
|
338
|
+
autofocus
|
|
339
|
+
name="code"
|
|
340
|
+
minLength={6}
|
|
341
|
+
maxLength={6}
|
|
342
|
+
required
|
|
343
|
+
placeholder={copy.input_code}
|
|
344
|
+
autoComplete="one-time-code"
|
|
345
|
+
/>
|
|
346
|
+
</>
|
|
347
|
+
)}
|
|
348
|
+
{state.type === "update" && (
|
|
349
|
+
<>
|
|
350
|
+
<input type="hidden" name="action" value="update" />
|
|
351
|
+
<input
|
|
352
|
+
data-component="input"
|
|
353
|
+
autofocus
|
|
354
|
+
type="password"
|
|
355
|
+
name="password"
|
|
356
|
+
placeholder={copy.input_password}
|
|
357
|
+
required
|
|
358
|
+
value={
|
|
359
|
+
!passwordError ? form?.get("password")?.toString() : ""
|
|
360
|
+
}
|
|
361
|
+
autoComplete="new-password"
|
|
362
|
+
/>
|
|
363
|
+
<input
|
|
364
|
+
data-component="input"
|
|
365
|
+
type="password"
|
|
366
|
+
name="repeat"
|
|
367
|
+
required
|
|
368
|
+
value={
|
|
369
|
+
!passwordError ? form?.get("password")?.toString() : ""
|
|
370
|
+
}
|
|
371
|
+
placeholder={copy.input_repeat}
|
|
372
|
+
autoComplete="new-password"
|
|
373
|
+
/>
|
|
374
|
+
</>
|
|
375
|
+
)}
|
|
376
|
+
<button data-component="button">{copy.button_continue}</button>
|
|
377
|
+
</form>
|
|
378
|
+
{state.type === "code" && (
|
|
379
|
+
<form method="post">
|
|
380
|
+
<input type="hidden" name="action" value="code" />
|
|
381
|
+
<input type="hidden" name="email" value={state.email} />
|
|
382
|
+
{state.type === "code" && (
|
|
383
|
+
<div data-component="form-footer">
|
|
384
|
+
<span>
|
|
385
|
+
{copy.code_return}{" "}
|
|
386
|
+
<a data-component="link" href="authorize">
|
|
387
|
+
{copy.login.toLowerCase()}
|
|
388
|
+
</a>
|
|
389
|
+
</span>
|
|
390
|
+
<button data-component="link">{copy.code_resend}</button>
|
|
391
|
+
</div>
|
|
392
|
+
)}
|
|
393
|
+
</form>
|
|
394
|
+
)}
|
|
395
|
+
</Layout>
|
|
396
|
+
)
|
|
397
|
+
return new Response(jsx.toString(), {
|
|
398
|
+
status: error ? 400 : 200,
|
|
399
|
+
headers: {
|
|
400
|
+
"Content-Type": "text/html",
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
},
|
|
404
|
+
}
|
|
405
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The UI that's displayed when loading the root page of the OpenAuth server. You can configure
|
|
3
|
+
* which providers should be displayed in the select UI.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { Select } from "@openauthjs/openauth/ui/select"
|
|
7
|
+
*
|
|
8
|
+
* export default issuer({
|
|
9
|
+
* select: Select({
|
|
10
|
+
* providers: {
|
|
11
|
+
* github: {
|
|
12
|
+
* hide: true
|
|
13
|
+
* },
|
|
14
|
+
* google: {
|
|
15
|
+
* display: "Google"
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* })
|
|
19
|
+
* // ...
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @packageDocumentation
|
|
24
|
+
*/
|
|
25
|
+
/** @jsxImportSource hono/jsx */
|
|
26
|
+
|
|
27
|
+
import { Layout } from "./base.js"
|
|
28
|
+
import { ICON_GITHUB, ICON_GOOGLE } from "./icon.js"
|
|
29
|
+
|
|
30
|
+
export interface SelectProps {
|
|
31
|
+
/**
|
|
32
|
+
* An object with all the providers and their config; where the key is the provider name.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* {
|
|
37
|
+
* github: {
|
|
38
|
+
* hide: true
|
|
39
|
+
* },
|
|
40
|
+
* google: {
|
|
41
|
+
* display: "Google"
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
providers?: Record<
|
|
47
|
+
string,
|
|
48
|
+
{
|
|
49
|
+
/**
|
|
50
|
+
* Whether to hide the provider from the select UI.
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
hide?: boolean
|
|
54
|
+
/**
|
|
55
|
+
* The display name of the provider.
|
|
56
|
+
*/
|
|
57
|
+
display?: string
|
|
58
|
+
}
|
|
59
|
+
>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function Select(props?: SelectProps) {
|
|
63
|
+
return async (
|
|
64
|
+
providers: Record<string, string>,
|
|
65
|
+
_req: Request,
|
|
66
|
+
): Promise<Response> => {
|
|
67
|
+
const jsx = (
|
|
68
|
+
<Layout>
|
|
69
|
+
<div data-component="form">
|
|
70
|
+
{Object.entries(providers).map(([key, type]) => {
|
|
71
|
+
const match = props?.providers?.[key]
|
|
72
|
+
if (match?.hide) return
|
|
73
|
+
const icon = ICON[key]
|
|
74
|
+
return (
|
|
75
|
+
<a
|
|
76
|
+
href={`/${key}/authorize`}
|
|
77
|
+
data-component="button"
|
|
78
|
+
data-color="ghost"
|
|
79
|
+
>
|
|
80
|
+
{icon && <i data-slot="icon">{icon}</i>}
|
|
81
|
+
Continue with {match?.display || DISPLAY[type] || type}
|
|
82
|
+
</a>
|
|
83
|
+
)
|
|
84
|
+
})}
|
|
85
|
+
</div>
|
|
86
|
+
</Layout>
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return new Response(jsx.toString(), {
|
|
90
|
+
headers: {
|
|
91
|
+
"Content-Type": "text/html",
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const DISPLAY: Record<string, string> = {
|
|
98
|
+
twitch: "Twitch",
|
|
99
|
+
google: "Google",
|
|
100
|
+
github: "GitHub",
|
|
101
|
+
apple: "Apple",
|
|
102
|
+
x: "X",
|
|
103
|
+
facebook: "Facebook",
|
|
104
|
+
microsoft: "Microsoft",
|
|
105
|
+
slack: "Slack",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const ICON: Record<string, any> = {
|
|
109
|
+
code: (
|
|
110
|
+
<svg
|
|
111
|
+
fill="currentColor"
|
|
112
|
+
viewBox="0 0 52 52"
|
|
113
|
+
data-name="Layer 1"
|
|
114
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
115
|
+
>
|
|
116
|
+
<path
|
|
117
|
+
d="M8.55,36.91A6.55,6.55,0,1,1,2,43.45,6.54,6.54,0,0,1,8.55,36.91Zm17.45,0a6.55,6.55,0,1,1-6.55,6.54A6.55,6.55,0,0,1,26,36.91Zm17.45,0a6.55,6.55,0,1,1-6.54,6.54A6.54,6.54,0,0,1,43.45,36.91ZM8.55,19.45A6.55,6.55,0,1,1,2,26,6.55,6.55,0,0,1,8.55,19.45Zm17.45,0A6.55,6.55,0,1,1,19.45,26,6.56,6.56,0,0,1,26,19.45Zm17.45,0A6.55,6.55,0,1,1,36.91,26,6.55,6.55,0,0,1,43.45,19.45ZM8.55,2A6.55,6.55,0,1,1,2,8.55,6.54,6.54,0,0,1,8.55,2ZM26,2a6.55,6.55,0,1,1-6.55,6.55A6.55,6.55,0,0,1,26,2ZM43.45,2a6.55,6.55,0,1,1-6.54,6.55A6.55,6.55,0,0,1,43.45,2Z"
|
|
118
|
+
fill-rule="evenodd"
|
|
119
|
+
/>
|
|
120
|
+
</svg>
|
|
121
|
+
),
|
|
122
|
+
password: (
|
|
123
|
+
<svg
|
|
124
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
125
|
+
viewBox="0 0 24 24"
|
|
126
|
+
fill="currentColor"
|
|
127
|
+
>
|
|
128
|
+
<path
|
|
129
|
+
fill-rule="evenodd"
|
|
130
|
+
d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z"
|
|
131
|
+
clip-rule="evenodd"
|
|
132
|
+
/>
|
|
133
|
+
</svg>
|
|
134
|
+
),
|
|
135
|
+
twitch: (
|
|
136
|
+
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
|
137
|
+
<path
|
|
138
|
+
fill="currentColor"
|
|
139
|
+
d="M40.1 32L10 108.9v314.3h107V480h60.2l56.8-56.8h87l117-117V32H40.1zm357.8 254.1L331 353H224l-56.8 56.8V353H76.9V72.1h321v214zM331 149v116.9h-40.1V149H331zm-107 0v116.9h-40.1V149H224z"
|
|
140
|
+
></path>
|
|
141
|
+
</svg>
|
|
142
|
+
),
|
|
143
|
+
google: ICON_GOOGLE,
|
|
144
|
+
github: ICON_GITHUB,
|
|
145
|
+
apple: (
|
|
146
|
+
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 814 1000">
|
|
147
|
+
<path
|
|
148
|
+
fill="currentColor"
|
|
149
|
+
d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z "
|
|
150
|
+
/>
|
|
151
|
+
</svg>
|
|
152
|
+
),
|
|
153
|
+
x: (
|
|
154
|
+
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1227">
|
|
155
|
+
<path
|
|
156
|
+
fill="currentColor"
|
|
157
|
+
d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"
|
|
158
|
+
/>
|
|
159
|
+
</svg>
|
|
160
|
+
),
|
|
161
|
+
microsoft: (
|
|
162
|
+
<svg
|
|
163
|
+
role="img"
|
|
164
|
+
viewBox="0 0 256 256"
|
|
165
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
166
|
+
preserveAspectRatio="xMidYMid"
|
|
167
|
+
>
|
|
168
|
+
<path fill="#F1511B" d="M121.666 121.666H0V0h121.666z" />
|
|
169
|
+
<path fill="#80CC28" d="M256 121.666H134.335V0H256z" />
|
|
170
|
+
<path fill="#00ADEF" d="M121.663 256.002H0V134.336h121.663z" />
|
|
171
|
+
<path fill="#FBBC09" d="M256 256.002H134.335V134.336H256z" />
|
|
172
|
+
</svg>
|
|
173
|
+
),
|
|
174
|
+
facebook: (
|
|
175
|
+
<svg
|
|
176
|
+
role="img"
|
|
177
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
178
|
+
viewBox="0 0 36 36"
|
|
179
|
+
fill="url(#a)"
|
|
180
|
+
>
|
|
181
|
+
<defs>
|
|
182
|
+
<linearGradient x1="50%" x2="50%" y1="97.078%" y2="0%" id="a">
|
|
183
|
+
<stop offset="0%" stop-color="#0062E0" />
|
|
184
|
+
<stop offset="100%" stop-color="#19AFFF" />
|
|
185
|
+
</linearGradient>
|
|
186
|
+
</defs>
|
|
187
|
+
<path d="M15 35.8C6.5 34.3 0 26.9 0 18 0 8.1 8.1 0 18 0s18 8.1 18 18c0 8.9-6.5 16.3-15 17.8l-1-.8h-4l-1 .8z" />
|
|
188
|
+
<path
|
|
189
|
+
fill="#FFF"
|
|
190
|
+
d="m25 23 .8-5H21v-3.5c0-1.4.5-2.5 2.7-2.5H26V7.4c-1.3-.2-2.7-.4-4-.4-4.1 0-7 2.5-7 7v4h-4.5v5H15v12.7c1 .2 2 .3 3 .3s2-.1 3-.3V23h4z"
|
|
191
|
+
/>
|
|
192
|
+
</svg>
|
|
193
|
+
),
|
|
194
|
+
slack: (
|
|
195
|
+
<svg
|
|
196
|
+
role="img"
|
|
197
|
+
enable-background="new 0 0 2447.6 2452.5"
|
|
198
|
+
viewBox="0 0 2447.6 2452.5"
|
|
199
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
200
|
+
>
|
|
201
|
+
<g clip-rule="evenodd" fill-rule="evenodd">
|
|
202
|
+
<path
|
|
203
|
+
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
|
|
204
|
+
fill="#36c5f0"
|
|
205
|
+
/>
|
|
206
|
+
<path
|
|
207
|
+
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
|
|
208
|
+
fill="#2eb67d"
|
|
209
|
+
/>
|
|
210
|
+
<path
|
|
211
|
+
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
|
|
212
|
+
fill="#ecb22e"
|
|
213
|
+
/>
|
|
214
|
+
<path
|
|
215
|
+
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
|
|
216
|
+
fill="#e01e5a"
|
|
217
|
+
/>
|
|
218
|
+
</g>
|
|
219
|
+
</svg>
|
|
220
|
+
),
|
|
221
|
+
}
|