@_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.
Files changed (192) hide show
  1. package/dist/esm/client.js +186 -0
  2. package/dist/esm/css.d.js +0 -0
  3. package/dist/esm/error.js +73 -0
  4. package/dist/esm/index.js +14 -0
  5. package/dist/esm/issuer.js +558 -0
  6. package/dist/esm/jwt.js +16 -0
  7. package/dist/esm/keys.js +113 -0
  8. package/dist/esm/pkce.js +35 -0
  9. package/dist/esm/provider/apple.js +28 -0
  10. package/dist/esm/provider/arctic.js +43 -0
  11. package/dist/esm/provider/code.js +58 -0
  12. package/dist/esm/provider/cognito.js +16 -0
  13. package/dist/esm/provider/discord.js +15 -0
  14. package/dist/esm/provider/facebook.js +24 -0
  15. package/dist/esm/provider/github.js +15 -0
  16. package/dist/esm/provider/google.js +25 -0
  17. package/dist/esm/provider/index.js +3 -0
  18. package/dist/esm/provider/jumpcloud.js +15 -0
  19. package/dist/esm/provider/keycloak.js +15 -0
  20. package/dist/esm/provider/linkedin.js +15 -0
  21. package/dist/esm/provider/m2m.js +17 -0
  22. package/dist/esm/provider/microsoft.js +24 -0
  23. package/dist/esm/provider/oauth2.js +119 -0
  24. package/dist/esm/provider/oidc.js +69 -0
  25. package/dist/esm/provider/passkey.js +315 -0
  26. package/dist/esm/provider/password.js +306 -0
  27. package/dist/esm/provider/provider.js +10 -0
  28. package/dist/esm/provider/slack.js +15 -0
  29. package/dist/esm/provider/spotify.js +15 -0
  30. package/dist/esm/provider/twitch.js +15 -0
  31. package/dist/esm/provider/x.js +16 -0
  32. package/dist/esm/provider/yahoo.js +15 -0
  33. package/dist/esm/random.js +27 -0
  34. package/dist/esm/storage/aws.js +39 -0
  35. package/dist/esm/storage/cloudflare.js +42 -0
  36. package/dist/esm/storage/dynamo.js +116 -0
  37. package/dist/esm/storage/memory.js +88 -0
  38. package/dist/esm/storage/storage.js +36 -0
  39. package/dist/esm/subject.js +7 -0
  40. package/dist/esm/ui/base.js +407 -0
  41. package/dist/esm/ui/code.js +151 -0
  42. package/dist/esm/ui/form.js +43 -0
  43. package/dist/esm/ui/icon.js +92 -0
  44. package/dist/esm/ui/passkey.js +329 -0
  45. package/dist/esm/ui/password.js +338 -0
  46. package/dist/esm/ui/select.js +187 -0
  47. package/dist/esm/ui/theme.js +115 -0
  48. package/dist/esm/util.js +54 -0
  49. package/dist/types/client.d.ts +466 -0
  50. package/dist/types/client.d.ts.map +1 -0
  51. package/dist/types/error.d.ts +77 -0
  52. package/dist/types/error.d.ts.map +1 -0
  53. package/dist/types/index.d.ts +20 -0
  54. package/dist/types/index.d.ts.map +1 -0
  55. package/dist/types/issuer.d.ts +465 -0
  56. package/dist/types/issuer.d.ts.map +1 -0
  57. package/dist/types/jwt.d.ts +6 -0
  58. package/dist/types/jwt.d.ts.map +1 -0
  59. package/dist/types/keys.d.ts +18 -0
  60. package/dist/types/keys.d.ts.map +1 -0
  61. package/dist/types/pkce.d.ts +7 -0
  62. package/dist/types/pkce.d.ts.map +1 -0
  63. package/dist/types/provider/apple.d.ts +108 -0
  64. package/dist/types/provider/apple.d.ts.map +1 -0
  65. package/dist/types/provider/arctic.d.ts +16 -0
  66. package/dist/types/provider/arctic.d.ts.map +1 -0
  67. package/dist/types/provider/code.d.ts +74 -0
  68. package/dist/types/provider/code.d.ts.map +1 -0
  69. package/dist/types/provider/cognito.d.ts +64 -0
  70. package/dist/types/provider/cognito.d.ts.map +1 -0
  71. package/dist/types/provider/discord.d.ts +38 -0
  72. package/dist/types/provider/discord.d.ts.map +1 -0
  73. package/dist/types/provider/facebook.d.ts +74 -0
  74. package/dist/types/provider/facebook.d.ts.map +1 -0
  75. package/dist/types/provider/github.d.ts +38 -0
  76. package/dist/types/provider/github.d.ts.map +1 -0
  77. package/dist/types/provider/google.d.ts +74 -0
  78. package/dist/types/provider/google.d.ts.map +1 -0
  79. package/dist/types/provider/index.d.ts +4 -0
  80. package/dist/types/provider/index.d.ts.map +1 -0
  81. package/dist/types/provider/jumpcloud.d.ts +38 -0
  82. package/dist/types/provider/jumpcloud.d.ts.map +1 -0
  83. package/dist/types/provider/keycloak.d.ts +67 -0
  84. package/dist/types/provider/keycloak.d.ts.map +1 -0
  85. package/dist/types/provider/linkedin.d.ts +6 -0
  86. package/dist/types/provider/linkedin.d.ts.map +1 -0
  87. package/dist/types/provider/m2m.d.ts +34 -0
  88. package/dist/types/provider/m2m.d.ts.map +1 -0
  89. package/dist/types/provider/microsoft.d.ts +89 -0
  90. package/dist/types/provider/microsoft.d.ts.map +1 -0
  91. package/dist/types/provider/oauth2.d.ts +133 -0
  92. package/dist/types/provider/oauth2.d.ts.map +1 -0
  93. package/dist/types/provider/oidc.d.ts +91 -0
  94. package/dist/types/provider/oidc.d.ts.map +1 -0
  95. package/dist/types/provider/passkey.d.ts +143 -0
  96. package/dist/types/provider/passkey.d.ts.map +1 -0
  97. package/dist/types/provider/password.d.ts +210 -0
  98. package/dist/types/provider/password.d.ts.map +1 -0
  99. package/dist/types/provider/provider.d.ts +29 -0
  100. package/dist/types/provider/provider.d.ts.map +1 -0
  101. package/dist/types/provider/slack.d.ts +59 -0
  102. package/dist/types/provider/slack.d.ts.map +1 -0
  103. package/dist/types/provider/spotify.d.ts +38 -0
  104. package/dist/types/provider/spotify.d.ts.map +1 -0
  105. package/dist/types/provider/twitch.d.ts +38 -0
  106. package/dist/types/provider/twitch.d.ts.map +1 -0
  107. package/dist/types/provider/x.d.ts +38 -0
  108. package/dist/types/provider/x.d.ts.map +1 -0
  109. package/dist/types/provider/yahoo.d.ts +38 -0
  110. package/dist/types/provider/yahoo.d.ts.map +1 -0
  111. package/dist/types/random.d.ts +3 -0
  112. package/dist/types/random.d.ts.map +1 -0
  113. package/dist/types/storage/aws.d.ts +4 -0
  114. package/dist/types/storage/aws.d.ts.map +1 -0
  115. package/dist/types/storage/cloudflare.d.ts +34 -0
  116. package/dist/types/storage/cloudflare.d.ts.map +1 -0
  117. package/dist/types/storage/dynamo.d.ts +65 -0
  118. package/dist/types/storage/dynamo.d.ts.map +1 -0
  119. package/dist/types/storage/memory.d.ts +49 -0
  120. package/dist/types/storage/memory.d.ts.map +1 -0
  121. package/dist/types/storage/storage.d.ts +15 -0
  122. package/dist/types/storage/storage.d.ts.map +1 -0
  123. package/dist/types/subject.d.ts +122 -0
  124. package/dist/types/subject.d.ts.map +1 -0
  125. package/dist/types/ui/base.d.ts +5 -0
  126. package/dist/types/ui/base.d.ts.map +1 -0
  127. package/dist/types/ui/code.d.ts +104 -0
  128. package/dist/types/ui/code.d.ts.map +1 -0
  129. package/dist/types/ui/form.d.ts +6 -0
  130. package/dist/types/ui/form.d.ts.map +1 -0
  131. package/dist/types/ui/icon.d.ts +6 -0
  132. package/dist/types/ui/icon.d.ts.map +1 -0
  133. package/dist/types/ui/passkey.d.ts +5 -0
  134. package/dist/types/ui/passkey.d.ts.map +1 -0
  135. package/dist/types/ui/password.d.ts +139 -0
  136. package/dist/types/ui/password.d.ts.map +1 -0
  137. package/dist/types/ui/select.d.ts +55 -0
  138. package/dist/types/ui/select.d.ts.map +1 -0
  139. package/dist/types/ui/theme.d.ts +207 -0
  140. package/dist/types/ui/theme.d.ts.map +1 -0
  141. package/dist/types/util.d.ts +8 -0
  142. package/dist/types/util.d.ts.map +1 -0
  143. package/package.json +51 -0
  144. package/src/client.ts +749 -0
  145. package/src/css.d.ts +4 -0
  146. package/src/error.ts +120 -0
  147. package/src/index.ts +26 -0
  148. package/src/issuer.ts +1302 -0
  149. package/src/jwt.ts +17 -0
  150. package/src/keys.ts +139 -0
  151. package/src/pkce.ts +40 -0
  152. package/src/provider/apple.ts +127 -0
  153. package/src/provider/arctic.ts +66 -0
  154. package/src/provider/code.ts +227 -0
  155. package/src/provider/cognito.ts +74 -0
  156. package/src/provider/discord.ts +45 -0
  157. package/src/provider/facebook.ts +84 -0
  158. package/src/provider/github.ts +45 -0
  159. package/src/provider/google.ts +85 -0
  160. package/src/provider/index.ts +3 -0
  161. package/src/provider/jumpcloud.ts +45 -0
  162. package/src/provider/keycloak.ts +75 -0
  163. package/src/provider/linkedin.ts +12 -0
  164. package/src/provider/m2m.ts +56 -0
  165. package/src/provider/microsoft.ts +100 -0
  166. package/src/provider/oauth2.ts +297 -0
  167. package/src/provider/oidc.ts +179 -0
  168. package/src/provider/passkey.ts +655 -0
  169. package/src/provider/password.ts +672 -0
  170. package/src/provider/provider.ts +33 -0
  171. package/src/provider/slack.ts +67 -0
  172. package/src/provider/spotify.ts +45 -0
  173. package/src/provider/twitch.ts +45 -0
  174. package/src/provider/x.ts +46 -0
  175. package/src/provider/yahoo.ts +45 -0
  176. package/src/random.ts +24 -0
  177. package/src/storage/aws.ts +59 -0
  178. package/src/storage/cloudflare.ts +77 -0
  179. package/src/storage/dynamo.ts +193 -0
  180. package/src/storage/memory.ts +135 -0
  181. package/src/storage/storage.ts +46 -0
  182. package/src/subject.ts +130 -0
  183. package/src/ui/base.tsx +118 -0
  184. package/src/ui/code.tsx +215 -0
  185. package/src/ui/form.tsx +40 -0
  186. package/src/ui/icon.tsx +95 -0
  187. package/src/ui/passkey.tsx +321 -0
  188. package/src/ui/password.tsx +405 -0
  189. package/src/ui/select.tsx +221 -0
  190. package/src/ui/theme.ts +319 -0
  191. package/src/ui/ui.css +252 -0
  192. 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
+ }