zudoku 0.64.1 → 0.64.2

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 (223) hide show
  1. package/dist/config/config.d.ts +15 -46
  2. package/dist/config/validators/InputNavigationSchema.d.ts +53 -53
  3. package/dist/config/validators/ProtectedRoutesSchema.d.ts +1 -1
  4. package/dist/config/validators/validate.d.ts +105 -1
  5. package/dist/config/validators/validate.js +30 -0
  6. package/dist/config/validators/validate.js.map +1 -1
  7. package/dist/flat-config.d.ts +16 -0
  8. package/dist/lib/auth/issuer.js +3 -0
  9. package/dist/lib/auth/issuer.js.map +1 -1
  10. package/dist/lib/authentication/authentication.d.ts +1 -1
  11. package/dist/lib/authentication/providers/firebase.d.ts +4 -0
  12. package/dist/lib/authentication/providers/firebase.js +215 -0
  13. package/dist/lib/authentication/providers/firebase.js.map +1 -0
  14. package/dist/lib/authentication/providers/supabase.js +1 -6
  15. package/dist/lib/authentication/providers/supabase.js.map +1 -1
  16. package/dist/lib/authentication/ui/ZudokuAuthUi.d.ts +24 -0
  17. package/dist/lib/authentication/ui/ZudokuAuthUi.js +124 -0
  18. package/dist/lib/authentication/ui/ZudokuAuthUi.js.map +1 -0
  19. package/dist/lib/authentication/ui/icons/Apple.d.ts +3 -0
  20. package/dist/lib/authentication/ui/icons/Apple.js +4 -0
  21. package/dist/lib/authentication/ui/icons/Apple.js.map +1 -0
  22. package/dist/lib/authentication/ui/icons/Facebook.d.ts +3 -0
  23. package/dist/lib/authentication/ui/icons/Facebook.js +4 -0
  24. package/dist/lib/authentication/ui/icons/Facebook.js.map +1 -0
  25. package/dist/lib/authentication/ui/icons/Github.d.ts +3 -0
  26. package/dist/lib/authentication/ui/icons/Github.js +4 -0
  27. package/dist/lib/authentication/ui/icons/Github.js.map +1 -0
  28. package/dist/lib/authentication/ui/icons/Google.d.ts +3 -0
  29. package/dist/lib/authentication/ui/icons/Google.js +4 -0
  30. package/dist/lib/authentication/ui/icons/Google.js.map +1 -0
  31. package/dist/lib/authentication/ui/icons/Microsoft.d.ts +3 -0
  32. package/dist/lib/authentication/ui/icons/Microsoft.js +4 -0
  33. package/dist/lib/authentication/ui/icons/Microsoft.js.map +1 -0
  34. package/dist/lib/authentication/ui/icons/X.d.ts +3 -0
  35. package/dist/lib/authentication/ui/icons/X.js +4 -0
  36. package/dist/lib/authentication/ui/icons/X.js.map +1 -0
  37. package/dist/lib/components/Heading.d.ts +1 -1
  38. package/dist/lib/core/RouteGuard.js +6 -6
  39. package/dist/lib/core/RouteGuard.js.map +1 -1
  40. package/dist/lib/oas/parser/index.js +7 -3
  41. package/dist/lib/oas/parser/index.js.map +1 -1
  42. package/dist/lib/plugins/api-keys/ProtectedRoute.js +4 -1
  43. package/dist/lib/plugins/api-keys/ProtectedRoute.js.map +1 -1
  44. package/dist/lib/plugins/openapi/CollapsibleCode.d.ts +1 -0
  45. package/dist/lib/plugins/openapi/CollapsibleCode.js +2 -1
  46. package/dist/lib/plugins/openapi/CollapsibleCode.js.map +1 -1
  47. package/dist/lib/plugins/openapi/GeneratedExampleSidecarBox.d.ts +5 -0
  48. package/dist/lib/plugins/openapi/GeneratedExampleSidecarBox.js +10 -0
  49. package/dist/lib/plugins/openapi/GeneratedExampleSidecarBox.js.map +1 -0
  50. package/dist/lib/plugins/openapi/OperationList.js +4 -1
  51. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  52. package/dist/lib/plugins/openapi/OperationListItem.d.ts +2 -1
  53. package/dist/lib/plugins/openapi/OperationListItem.js +2 -2
  54. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  55. package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +9 -2
  56. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +2 -2
  57. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  58. package/dist/lib/plugins/openapi/ResponsesSidecarBox.d.ts +3 -1
  59. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +14 -2
  60. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
  61. package/dist/lib/plugins/openapi/Sidecar.d.ts +2 -1
  62. package/dist/lib/plugins/openapi/Sidecar.js +33 -30
  63. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  64. package/dist/lib/plugins/openapi/SidecarExamples.d.ts +9 -2
  65. package/dist/lib/plugins/openapi/SidecarExamples.js +15 -33
  66. package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -1
  67. package/dist/lib/plugins/openapi/components/NonHighlightedCode.d.ts +4 -0
  68. package/dist/lib/plugins/openapi/components/NonHighlightedCode.js +5 -0
  69. package/dist/lib/plugins/openapi/components/NonHighlightedCode.js.map +1 -0
  70. package/dist/lib/plugins/openapi/components/ResponseContent.js +1 -1
  71. package/dist/lib/plugins/openapi/components/ResponseContent.js.map +1 -1
  72. package/dist/lib/plugins/openapi/playground/InlineInput.d.ts +1 -1
  73. package/dist/lib/plugins/openapi/playground/ParamsGrid.d.ts +2 -2
  74. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js +1 -2
  75. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js.map +1 -1
  76. package/dist/lib/plugins/openapi/schema/SchemaView.js +0 -4
  77. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  78. package/dist/lib/plugins/openapi/schema/union-helpers.js +0 -1
  79. package/dist/lib/plugins/openapi/schema/union-helpers.js.map +1 -1
  80. package/dist/lib/plugins/openapi/util/generateSchemaExample.js +5 -14
  81. package/dist/lib/plugins/openapi/util/generateSchemaExample.js.map +1 -1
  82. package/dist/lib/ui/CodeBlock.d.ts +0 -1
  83. package/dist/lib/ui/CodeBlock.js.map +1 -1
  84. package/dist/lib/ui/Command.d.ts +3 -3
  85. package/dist/lib/ui/EmbeddedCodeBlock.d.ts +0 -1
  86. package/dist/lib/ui/EmbeddedCodeBlock.js +1 -1
  87. package/dist/lib/ui/EmbeddedCodeBlock.js.map +1 -1
  88. package/dist/lib/ui/Separator.d.ts +4 -0
  89. package/dist/lib/ui/Separator.js +8 -0
  90. package/dist/lib/ui/Separator.js.map +1 -0
  91. package/dist/lib/ui/Tooltip.d.ts +7 -7
  92. package/dist/lib/ui/Tooltip.js +16 -10
  93. package/dist/lib/ui/Tooltip.js.map +1 -1
  94. package/dist/lib/util/createVariantComponent.d.ts +5 -2
  95. package/dist/lib/util/createVariantComponent.js +5 -2
  96. package/dist/lib/util/createVariantComponent.js.map +1 -1
  97. package/dist/lib/util/flattenAllOf.d.ts +4 -0
  98. package/dist/lib/util/flattenAllOf.js +65 -0
  99. package/dist/lib/util/flattenAllOf.js.map +1 -0
  100. package/dist/lib/util/flattenAllOf.test.d.ts +1 -0
  101. package/dist/lib/util/flattenAllOf.test.js +532 -0
  102. package/dist/lib/util/flattenAllOf.test.js.map +1 -0
  103. package/dist/vite/api/SchemaManager.js +6 -18
  104. package/dist/vite/api/SchemaManager.js.map +1 -1
  105. package/dist/vite/plugin-theme.js +10 -1
  106. package/dist/vite/plugin-theme.js.map +1 -1
  107. package/lib/{ErrorAlert-DE3Sf66a.js → ErrorAlert--3alJ_-b.js} +1340 -1311
  108. package/lib/{ErrorAlert-DE3Sf66a.js.map → ErrorAlert--3alJ_-b.js.map} +1 -1
  109. package/lib/{MdxPage-DZfeC0QY.js → MdxPage-Bpa9tL63.js} +5 -5
  110. package/lib/{MdxPage-DZfeC0QY.js.map → MdxPage-Bpa9tL63.js.map} +1 -1
  111. package/lib/{OAuthErrorPage-BycMozgn.js → OAuthErrorPage-B79J86Fo.js} +4 -4
  112. package/lib/{OAuthErrorPage-BycMozgn.js.map → OAuthErrorPage-B79J86Fo.js.map} +1 -1
  113. package/lib/{OasProvider-1XEOsIiW.js → OasProvider-jr0oDSFy.js} +2 -2
  114. package/lib/{OasProvider-1XEOsIiW.js.map → OasProvider-jr0oDSFy.js.map} +1 -1
  115. package/lib/OperationList-DLEAg4qw.js +5465 -0
  116. package/lib/OperationList-DLEAg4qw.js.map +1 -0
  117. package/lib/{Pagination-CJszmeSA.js → Pagination-H2HW9-Er.js} +2 -2
  118. package/lib/{Pagination-CJszmeSA.js.map → Pagination-H2HW9-Er.js.map} +1 -1
  119. package/lib/RouteGuard-CjzxosTf.js +77 -0
  120. package/lib/RouteGuard-CjzxosTf.js.map +1 -0
  121. package/lib/{RouterError-VDLnrFqF.js → RouterError-DZS2d6Sc.js} +2 -2
  122. package/lib/{RouterError-VDLnrFqF.js.map → RouterError-DZS2d6Sc.js.map} +1 -1
  123. package/lib/{SchemaList-qOHkDzSz.js → SchemaList-CSDSazqV.js} +6 -6
  124. package/lib/{SchemaList-qOHkDzSz.js.map → SchemaList-CSDSazqV.js.map} +1 -1
  125. package/lib/SchemaView-DJiBd0_5.js +397 -0
  126. package/lib/SchemaView-DJiBd0_5.js.map +1 -0
  127. package/lib/{SignUp-6SGx9Yyq.js → SignUp-Fycafbyg.js} +2 -2
  128. package/lib/{SignUp-6SGx9Yyq.js.map → SignUp-Fycafbyg.js.map} +1 -1
  129. package/lib/{SyntaxHighlight-zvlnSnHB.js → SyntaxHighlight-C19vH0V_.js} +525 -509
  130. package/lib/SyntaxHighlight-C19vH0V_.js.map +1 -0
  131. package/lib/{Toc-Da9yp7lo.js → Toc-ChkOg2UU.js} +2 -2
  132. package/lib/{Toc-Da9yp7lo.js.map → Toc-ChkOg2UU.js.map} +1 -1
  133. package/lib/{circular-CSSuz-LS.js → circular-DGfd8SGc.js} +2 -2
  134. package/lib/{circular-CSSuz-LS.js.map → circular-DGfd8SGc.js.map} +1 -1
  135. package/lib/{createServer-CLbcVLbK.js → createServer-DGD8hEzT.js} +4662 -4238
  136. package/lib/createServer-DGD8hEzT.js.map +1 -0
  137. package/lib/{errors-CuGgh3hf.js → errors-BTpjwHS6.js} +2 -2
  138. package/lib/{errors-CuGgh3hf.js.map → errors-BTpjwHS6.js.map} +1 -1
  139. package/lib/{index-rYHsvtTo.js → index-Bvas0H4x.js} +2 -2
  140. package/lib/{index-rYHsvtTo.js.map → index-Bvas0H4x.js.map} +1 -1
  141. package/lib/{index-RNAxx6IF.js → index-DP1xZgfJ.js} +9 -9
  142. package/lib/index-DP1xZgfJ.js.map +1 -0
  143. package/lib/{index-B1rmok4X.js → index-FNRZUtwo.js} +2 -2
  144. package/lib/{index-B1rmok4X.js.map → index-FNRZUtwo.js.map} +1 -1
  145. package/lib/ui/CodeBlock.js.map +1 -1
  146. package/lib/ui/EmbeddedCodeBlock.js +9 -9
  147. package/lib/ui/EmbeddedCodeBlock.js.map +1 -1
  148. package/lib/ui/Separator.js +27 -0
  149. package/lib/ui/Separator.js.map +1 -0
  150. package/lib/ui/SyntaxHighlight.js +1 -1
  151. package/lib/ui/Tooltip.js +55 -28
  152. package/lib/ui/Tooltip.js.map +1 -1
  153. package/lib/zudoku.__internal.js +4 -4
  154. package/lib/zudoku.auth-azureb2c.js +3 -3
  155. package/lib/zudoku.auth-clerk.js +1 -1
  156. package/lib/zudoku.auth-openid.js +3 -3
  157. package/lib/zudoku.auth-supabase.js +30 -33
  158. package/lib/zudoku.auth-supabase.js.map +1 -1
  159. package/lib/zudoku.components.js +2 -2
  160. package/lib/zudoku.plugin-api-catalog.js +3 -3
  161. package/lib/zudoku.plugin-api-keys.js +3 -3
  162. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  163. package/lib/zudoku.plugin-markdown.js +1 -1
  164. package/lib/zudoku.plugin-openapi.js +1 -1
  165. package/lib/zudoku.plugin-search-pagefind.js +1 -1
  166. package/package.json +15 -10
  167. package/src/app/main.css +1 -1
  168. package/src/lib/auth/issuer.ts +3 -0
  169. package/src/lib/authentication/authentication.ts +1 -1
  170. package/src/lib/authentication/providers/firebase.tsx +284 -0
  171. package/src/lib/authentication/providers/supabase.tsx +2 -7
  172. package/src/lib/authentication/ui/ZudokuAuthUi.tsx +335 -0
  173. package/src/lib/authentication/ui/icons/Apple.tsx +10 -0
  174. package/src/lib/authentication/ui/icons/Facebook.tsx +15 -0
  175. package/src/lib/authentication/ui/icons/Github.tsx +16 -0
  176. package/src/lib/authentication/ui/icons/Google.tsx +16 -0
  177. package/src/lib/authentication/ui/icons/Microsoft.tsx +12 -0
  178. package/src/lib/authentication/ui/icons/X.tsx +10 -0
  179. package/src/lib/core/RouteGuard.tsx +8 -8
  180. package/src/lib/oas/parser/index.ts +8 -3
  181. package/src/lib/plugins/api-keys/ProtectedRoute.tsx +11 -7
  182. package/src/lib/plugins/openapi/CollapsibleCode.tsx +5 -3
  183. package/src/lib/plugins/openapi/GeneratedExampleSidecarBox.tsx +52 -0
  184. package/src/lib/plugins/openapi/OperationList.tsx +5 -0
  185. package/src/lib/plugins/openapi/OperationListItem.tsx +3 -0
  186. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +20 -2
  187. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +26 -1
  188. package/src/lib/plugins/openapi/Sidecar.tsx +84 -63
  189. package/src/lib/plugins/openapi/SidecarExamples.tsx +38 -48
  190. package/src/lib/plugins/openapi/components/NonHighlightedCode.tsx +22 -0
  191. package/src/lib/plugins/openapi/components/ResponseContent.tsx +1 -1
  192. package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +1 -4
  193. package/src/lib/plugins/openapi/schema/SchemaView.tsx +0 -5
  194. package/src/lib/plugins/openapi/schema/union-helpers.ts +0 -1
  195. package/src/lib/plugins/openapi/util/generateSchemaExample.ts +5 -15
  196. package/src/lib/ui/CodeBlock.tsx +0 -1
  197. package/src/lib/ui/EmbeddedCodeBlock.tsx +1 -2
  198. package/src/lib/ui/Separator.tsx +25 -0
  199. package/src/lib/ui/Tooltip.tsx +54 -32
  200. package/src/lib/util/createVariantComponent.tsx +31 -5
  201. package/src/lib/util/flattenAllOf.test.ts +637 -0
  202. package/src/lib/util/flattenAllOf.ts +101 -0
  203. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupConnector.d.ts +0 -5
  204. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupConnector.js +0 -7
  205. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupConnector.js.map +0 -1
  206. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupItem.d.ts +0 -4
  207. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupItem.js +0 -10
  208. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupItem.js.map +0 -1
  209. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupView.d.ts +0 -5
  210. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupView.js +0 -16
  211. package/dist/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupView.js.map +0 -1
  212. package/lib/OperationList-DCJw6wXL.js +0 -5450
  213. package/lib/OperationList-DCJw6wXL.js.map +0 -1
  214. package/lib/RouteGuard-DhU3LRr1.js +0 -81
  215. package/lib/RouteGuard-DhU3LRr1.js.map +0 -1
  216. package/lib/SchemaView-D3hm65cc.js +0 -458
  217. package/lib/SchemaView-D3hm65cc.js.map +0 -1
  218. package/lib/SyntaxHighlight-zvlnSnHB.js.map +0 -1
  219. package/lib/createServer-CLbcVLbK.js.map +0 -1
  220. package/lib/index-RNAxx6IF.js.map +0 -1
  221. package/src/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupConnector.tsx +0 -36
  222. package/src/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupItem.tsx +0 -25
  223. package/src/lib/plugins/openapi/schema/AllOfGroup/AllOfGroupView.tsx +0 -42
@@ -0,0 +1,284 @@
1
+ import { type FirebaseApp, initializeApp } from "firebase/app";
2
+ import {
3
+ type Auth,
4
+ createUserWithEmailAndPassword,
5
+ getAuth,
6
+ signInWithEmailAndPassword,
7
+ signInWithPopup,
8
+ signOut,
9
+ type User,
10
+ } from "firebase/auth";
11
+ import type { FirebaseAuthenticationConfig } from "../../../config/config.js";
12
+ import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
13
+ import type {
14
+ AuthActionContext,
15
+ AuthActionOptions,
16
+ AuthenticationPlugin,
17
+ AuthenticationProviderInitializer,
18
+ } from "../authentication.js";
19
+ import { SignOut } from "../components/SignOut.js";
20
+ import { AuthorizationError } from "../errors.js";
21
+ import { useAuthState } from "../state.js";
22
+ import { ZudokuSignInUi, ZudokuSignUpUi } from "../ui/ZudokuAuthUi.js";
23
+
24
+ class FirebaseAuthenticationProvider
25
+ extends CoreAuthenticationPlugin
26
+ implements AuthenticationPlugin
27
+ {
28
+ private readonly app: FirebaseApp;
29
+ private readonly auth: Auth;
30
+ private readonly providers: string[];
31
+
32
+ constructor(config: FirebaseAuthenticationConfig) {
33
+ super();
34
+
35
+ this.app = initializeApp({
36
+ apiKey: config.apiKey,
37
+ authDomain: config.authDomain,
38
+ projectId: config.projectId,
39
+ storageBucket: config.storageBucket,
40
+ messagingSenderId: config.messagingSenderId,
41
+ appId: config.appId,
42
+ measurementId: config.measurementId,
43
+ });
44
+ this.auth = getAuth(this.app);
45
+ this.providers = config.providers ?? [];
46
+ }
47
+
48
+ async signRequest(request: Request): Promise<Request> {
49
+ const accessToken = await this.auth.currentUser?.getIdToken();
50
+ if (!accessToken) {
51
+ throw new AuthorizationError("User is not authenticated");
52
+ }
53
+ request.headers.set("Authorization", `Bearer ${accessToken}`);
54
+ return request;
55
+ }
56
+
57
+ signUp = async (
58
+ { navigate }: AuthActionContext,
59
+ { redirectTo }: AuthActionOptions,
60
+ ) => {
61
+ void navigate(
62
+ redirectTo
63
+ ? `/signup?redirectTo=${encodeURIComponent(redirectTo)}`
64
+ : `/signup`,
65
+ );
66
+ };
67
+
68
+ signIn = async (
69
+ { navigate }: AuthActionContext,
70
+ { redirectTo }: AuthActionOptions,
71
+ ) => {
72
+ void navigate(
73
+ redirectTo
74
+ ? `/signin?redirectTo=${encodeURIComponent(redirectTo)}`
75
+ : `/signin`,
76
+ );
77
+ };
78
+
79
+ getRoutes = () => {
80
+ return [
81
+ {
82
+ path: "/signin",
83
+ element: (
84
+ <ZudokuSignInUi
85
+ providers={this.providers}
86
+ onOAuthSignIn={async (providerId: string) => {
87
+ useAuthState.setState({ isPending: true });
88
+ const provider = await getProviderForId(providerId);
89
+ if (!provider) {
90
+ throw new AuthorizationError(
91
+ `Provider ${providerId} not found`,
92
+ );
93
+ }
94
+ const result = await signInWithPopup(this.auth, provider);
95
+ useAuthState.setState({ isPending: false });
96
+ useAuthState.getState().setLoggedIn({
97
+ providerData: { user: result.user },
98
+ profile: {
99
+ sub: result.user.uid,
100
+ email: result.user.email ?? undefined,
101
+ name: result.user.displayName ?? undefined,
102
+ emailVerified: result.user.emailVerified,
103
+ pictureUrl: result.user.photoURL ?? undefined,
104
+ },
105
+ });
106
+ }}
107
+ onUsernamePasswordSignIn={async (
108
+ email: string,
109
+ password: string,
110
+ ) => {
111
+ try {
112
+ await signInWithEmailAndPassword(this.auth, email, password);
113
+ } catch (error) {
114
+ throw Error(getFirebaseErrorMessage(error), { cause: error });
115
+ }
116
+ }}
117
+ />
118
+ ),
119
+ },
120
+ {
121
+ path: "/signup",
122
+ element: (
123
+ <ZudokuSignUpUi
124
+ providers={this.providers}
125
+ onOAuthSignUp={async (providerId: string) => {
126
+ const provider = await getProviderForId(providerId);
127
+ if (!provider) {
128
+ throw new AuthorizationError(
129
+ `Provider ${providerId} not found`,
130
+ );
131
+ }
132
+ await signInWithPopup(this.auth, provider);
133
+ }}
134
+ onUsernamePasswordSignUp={async (
135
+ email: string,
136
+ password: string,
137
+ ) => {
138
+ await createUserWithEmailAndPassword(this.auth, email, password);
139
+ }}
140
+ />
141
+ ),
142
+ },
143
+ {
144
+ path: "/signout",
145
+ element: <SignOut />,
146
+ },
147
+ ];
148
+ };
149
+
150
+ signOut = async () => {
151
+ await signOut(this.auth);
152
+
153
+ useAuthState.setState({
154
+ isAuthenticated: false,
155
+ isPending: false,
156
+ profile: undefined,
157
+ providerData: undefined,
158
+ });
159
+ };
160
+
161
+ onPageLoad = async () => {
162
+ const user = this.auth.currentUser;
163
+
164
+ if (user) {
165
+ await this.updateUserState(user);
166
+ } else {
167
+ useAuthState.setState({ isPending: false });
168
+ }
169
+ };
170
+
171
+ private async updateUserState(user: User) {
172
+ useAuthState.getState().setLoggedIn({
173
+ profile: {
174
+ sub: user.uid,
175
+ email: user.email ?? undefined,
176
+ name: user.displayName ?? undefined,
177
+ emailVerified: user.emailVerified,
178
+ pictureUrl: user.photoURL ?? undefined,
179
+ },
180
+ providerData: { user },
181
+ });
182
+ }
183
+ }
184
+
185
+ const supabaseAuth: AuthenticationProviderInitializer<
186
+ FirebaseAuthenticationConfig
187
+ > = (options) => new FirebaseAuthenticationProvider(options);
188
+
189
+ export default supabaseAuth;
190
+
191
+ const getProviderForId = async (providerId: string) => {
192
+ switch (providerId) {
193
+ case "google": {
194
+ const { GoogleAuthProvider } = await import("firebase/auth");
195
+ return new GoogleAuthProvider();
196
+ }
197
+ case "github": {
198
+ const { GithubAuthProvider } = await import("firebase/auth");
199
+ return new GithubAuthProvider();
200
+ }
201
+ case "facebook": {
202
+ const { FacebookAuthProvider } = await import("firebase/auth");
203
+ return new FacebookAuthProvider();
204
+ }
205
+ case "twitter": {
206
+ const { TwitterAuthProvider } = await import("firebase/auth");
207
+ return new TwitterAuthProvider();
208
+ }
209
+ case "microsoft": {
210
+ const { OAuthProvider } = await import("firebase/auth");
211
+ return new OAuthProvider("microsoft.com");
212
+ }
213
+ case "apple": {
214
+ const { OAuthProvider } = await import("firebase/auth");
215
+ return new OAuthProvider("apple.com");
216
+ }
217
+ case "yahoo": {
218
+ const { OAuthProvider } = await import("firebase/auth");
219
+ return new OAuthProvider("yahoo.com");
220
+ }
221
+ }
222
+
223
+ throw new AuthorizationError(`Provider ${providerId} not found`);
224
+ };
225
+
226
+ const getFirebaseErrorMessage = (error: unknown): string => {
227
+ if (!(error instanceof Error)) {
228
+ return "An unexpected error occurred. Please try again.";
229
+ }
230
+
231
+ const errorCode = (error as { code?: string }).code;
232
+
233
+ switch (errorCode) {
234
+ case "auth/email-already-in-use":
235
+ return "The email address is already used by another account.";
236
+ case "auth/invalid-email":
237
+ return "That email address isn't correct.";
238
+ case "auth/operation-not-allowed":
239
+ return "This sign-in method is not enabled. Please contact support.";
240
+ case "auth/weak-password":
241
+ return "The password must be at least 6 characters long.";
242
+ case "auth/user-disabled":
243
+ return "This account has been disabled. Please contact support.";
244
+ case "auth/user-not-found":
245
+ return "That email address doesn't match an existing account.";
246
+ case "auth/wrong-password":
247
+ return "The email and password you entered don't match.";
248
+ case "auth/too-many-requests":
249
+ return "You have entered an incorrect password too many times. Please try again in a few minutes.";
250
+ case "auth/popup-blocked":
251
+ return "The sign-in popup was blocked by your browser. Please allow popups and try again.";
252
+ case "auth/popup-closed-by-user":
253
+ return "The sign-in popup was closed before completing. Please try again.";
254
+ case "auth/network-request-failed":
255
+ return "A network error has occurred. Please check your connection and try again.";
256
+ case "auth/requires-recent-login":
257
+ return "Please login again to perform this operation.";
258
+ case "auth/invalid-credential":
259
+ return "The credential is invalid or has expired. Please try again.";
260
+ case "auth/account-exists-with-different-credential":
261
+ return "An account already exists with the same email address but different sign-in credentials.";
262
+ case "auth/credential-already-in-use":
263
+ return "This credential is already associated with a different user account.";
264
+ case "auth/invalid-verification-code":
265
+ return "Wrong code. Try again.";
266
+ case "auth/invalid-verification-id":
267
+ return "The verification ID is invalid.";
268
+ case "auth/missing-verification-code":
269
+ return "Please enter the verification code.";
270
+ case "auth/user-cancelled":
271
+ return "Please authorize the required permissions to sign in.";
272
+ case "auth/expired-action-code":
273
+ return "This code has expired.";
274
+ case "auth/invalid-action-code":
275
+ return "The action code is invalid. This can happen if the code is malformed or has already been used.";
276
+ case "auth/unauthorized-domain":
277
+ return "This domain is not authorized for OAuth operations.";
278
+ default:
279
+ return (
280
+ error.message ||
281
+ "An error occurred during authentication. Please try again."
282
+ );
283
+ }
284
+ };
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  createClient,
3
- type Provider,
4
3
  type Session,
5
4
  type SupabaseClient,
6
5
  } from "@supabase/supabase-js";
@@ -22,16 +21,12 @@ class SupabaseAuthenticationProvider
22
21
  implements AuthenticationPlugin
23
22
  {
24
23
  private readonly client: SupabaseClient;
25
- private readonly providers: Provider[];
26
24
  private readonly config: SupabaseAuthenticationConfig;
27
25
 
28
26
  constructor(config: SupabaseAuthenticationConfig) {
29
- const { provider, providers, supabaseUrl, supabaseKey } = config;
27
+ const { supabaseUrl, supabaseKey } = config;
30
28
  super();
31
- this.providers = providers ?? (provider ? [provider] : []);
32
- if (this.providers.length === 0) {
33
- throw new Error("At least one provider must be provided");
34
- }
29
+
35
30
  this.client = createClient(supabaseUrl, supabaseKey, {
36
31
  auth: {
37
32
  autoRefreshToken: true,
@@ -0,0 +1,335 @@
1
+ import { useMutation } from "@tanstack/react-query";
2
+ import React from "react";
3
+ import { useForm } from "react-hook-form";
4
+ import { Link, useNavigate, useSearchParams } from "react-router";
5
+ import { ActionButton } from "zudoku/ui/ActionButton.js";
6
+ import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
7
+ import { Button, type ButtonProps } from "zudoku/ui/Button.js";
8
+ import {
9
+ Card,
10
+ CardContent,
11
+ CardDescription,
12
+ CardHeader,
13
+ CardTitle,
14
+ } from "zudoku/ui/Card.js";
15
+ import { Input } from "zudoku/ui/Input.js";
16
+ import { Separator } from "zudoku/ui/Separator.js";
17
+ import {
18
+ Form,
19
+ FormControl,
20
+ FormItem,
21
+ FormLabel,
22
+ FormMessage,
23
+ } from "../../ui/Form.js";
24
+ import { cn } from "../../util/cn.js";
25
+ import createVariantComponent from "../../util/createVariantComponent.js";
26
+ import AppleIcon from "./icons/Apple.js";
27
+ import FacebookIcon from "./icons/Facebook.js";
28
+ import GithubIcon from "./icons/Github.js";
29
+ import GoogleIcon from "./icons/Google.js";
30
+ import MicrosoftIcon from "./icons/Microsoft.js";
31
+ import XIcon from "./icons/X.js";
32
+
33
+ export const AUTH_PROVIDER_NAMES: Record<AuthProviderId, string> = {
34
+ google: "Google",
35
+ github: "GitHub",
36
+ facebook: "Facebook",
37
+ twitter: "X",
38
+ x: "X",
39
+ microsoft: "Microsoft",
40
+ apple: "Apple",
41
+ yahoo: "Yahoo",
42
+ };
43
+
44
+ export type AuthProviderId = keyof typeof ProviderIcons;
45
+
46
+ const ProviderIcons = {
47
+ google: GoogleIcon,
48
+ github: GithubIcon,
49
+ facebook: FacebookIcon,
50
+ twitter: XIcon,
51
+ x: XIcon,
52
+ apple: AppleIcon,
53
+ microsoft: MicrosoftIcon,
54
+ yahoo: React.Fragment,
55
+ } as const;
56
+
57
+ const isValidAuthProviderId = (
58
+ provider: string,
59
+ ): provider is AuthProviderId => {
60
+ return provider in ProviderIcons;
61
+ };
62
+
63
+ const isAuthProviderIdArray = (
64
+ providers: string[],
65
+ ): providers is AuthProviderId[] => {
66
+ return providers.every(isValidAuthProviderId);
67
+ };
68
+
69
+ const AuthProviderButton = ({
70
+ providerId,
71
+ onClick,
72
+ ...buttonProps
73
+ }: { providerId: AuthProviderId; onClick?: () => void } & ButtonProps) => {
74
+ const IconRenderer = ProviderIcons[providerId];
75
+ return (
76
+ <Button
77
+ variant="outline"
78
+ className="gap-2"
79
+ onClick={onClick}
80
+ {...buttonProps}
81
+ >
82
+ <IconRenderer className="w-4 h-4 fill-foreground" />
83
+ {AUTH_PROVIDER_NAMES[providerId]}
84
+ </Button>
85
+ );
86
+ };
87
+
88
+ type FormFields = {
89
+ email: string;
90
+ password: string;
91
+ };
92
+
93
+ const EmailPasswordForm = ({
94
+ form,
95
+ onSubmit,
96
+ submitLabel,
97
+ isPending,
98
+ }: {
99
+ form: ReturnType<typeof useForm<FormFields>>;
100
+ onSubmit: (data: FormFields) => void;
101
+ submitLabel: string;
102
+ isPending: boolean;
103
+ }) => {
104
+ return (
105
+ <Form {...form}>
106
+ <form
107
+ onSubmit={form.handleSubmit(onSubmit)}
108
+ className="flex flex-col gap-2"
109
+ >
110
+ <FormItem>
111
+ <FormLabel>E-Mail</FormLabel>
112
+ <FormControl>
113
+ <Input placeholder="Email" {...form.register("email")} />
114
+ </FormControl>
115
+ <FormMessage />
116
+ </FormItem>
117
+ <FormItem>
118
+ <FormLabel>Password</FormLabel>
119
+ <FormControl>
120
+ <Input
121
+ placeholder="Password"
122
+ {...form.register("password")}
123
+ type="password"
124
+ />
125
+ </FormControl>
126
+ <FormMessage />
127
+ </FormItem>
128
+ <ActionButton type="submit" isPending={isPending}>
129
+ {submitLabel}
130
+ </ActionButton>
131
+ </form>
132
+ </Form>
133
+ );
134
+ };
135
+
136
+ export const ZudokuSignInUi = ({
137
+ providers,
138
+ onOAuthSignIn,
139
+ onUsernamePasswordSignIn,
140
+ }: {
141
+ providers: string[];
142
+ onOAuthSignIn: (providerId: string) => Promise<void>;
143
+ onUsernamePasswordSignIn: (email: string, password: string) => Promise<void>;
144
+ }) => {
145
+ const navigate = useNavigate();
146
+ const [searchParams] = useSearchParams();
147
+ const redirectTo = searchParams.get("redirectTo");
148
+
149
+ if (!isAuthProviderIdArray(providers)) {
150
+ throw new Error("Invalid auth provider IDs");
151
+ }
152
+
153
+ const signInUsernameMutation = useMutation({
154
+ mutationFn: async ({ email, password }: FormFields) => {
155
+ await onUsernamePasswordSignIn(email, password);
156
+ },
157
+ onSuccess: () => {
158
+ void navigate(redirectTo ?? "/");
159
+ },
160
+ });
161
+ const signInByProviderMutation = useMutation({
162
+ mutationFn: async ({ providerId }: { providerId: string }) => {
163
+ await onOAuthSignIn(providerId);
164
+ },
165
+ onSuccess: () => {
166
+ void navigate(redirectTo ?? "/");
167
+ },
168
+ });
169
+
170
+ const form = useForm<FormFields>({
171
+ defaultValues: {
172
+ email: "",
173
+ password: "",
174
+ },
175
+ });
176
+
177
+ const pending =
178
+ signInUsernameMutation.isPending || signInByProviderMutation.isPending;
179
+
180
+ const error = signInUsernameMutation.error ?? signInByProviderMutation.error;
181
+
182
+ return (
183
+ <AuthCard>
184
+ <CardHeader>
185
+ <CardTitle>Sign in</CardTitle>
186
+ <CardDescription>Sign in to your account to continue.</CardDescription>
187
+ </CardHeader>
188
+ <CardContent className="flex flex-col gap-4">
189
+ {error && (
190
+ <Alert variant="destructive">
191
+ <AlertTitle>Error</AlertTitle>
192
+ <AlertDescription>{error?.message}</AlertDescription>
193
+ </Alert>
194
+ )}
195
+ <EmailPasswordForm
196
+ form={form}
197
+ onSubmit={(data) =>
198
+ void signInUsernameMutation.mutate({
199
+ email: data.email,
200
+ password: data.password,
201
+ })
202
+ }
203
+ submitLabel="Sign in"
204
+ isPending={pending}
205
+ />
206
+ <ProviderSeparator providers={providers} />
207
+ <ProviderButtons
208
+ providers={providers}
209
+ onClick={(providerId) =>
210
+ signInByProviderMutation.mutate({ providerId })
211
+ }
212
+ />
213
+ <Link to="/signup" className="text-sm text-muted-foreground">
214
+ Don't have an account? Sign up.
215
+ </Link>
216
+ </CardContent>
217
+ </AuthCard>
218
+ );
219
+ };
220
+
221
+ export const ZudokuSignUpUi = ({
222
+ providers,
223
+ onOAuthSignUp,
224
+ onUsernamePasswordSignUp,
225
+ }: {
226
+ providers: string[];
227
+ onOAuthSignUp: (providerId: string) => Promise<void>;
228
+ onUsernamePasswordSignUp: (email: string, password: string) => Promise<void>;
229
+ }) => {
230
+ if (!isAuthProviderIdArray(providers)) {
231
+ throw new Error("Invalid auth provider IDs");
232
+ }
233
+
234
+ const signUpUsernameMutation = useMutation({
235
+ mutationFn: async ({ email, password }: FormFields) => {
236
+ await onUsernamePasswordSignUp(email, password);
237
+ },
238
+ });
239
+
240
+ const signUpByProviderMutation = useMutation({
241
+ mutationFn: async ({ providerId }: { providerId: string }) => {
242
+ await onOAuthSignUp(providerId);
243
+ },
244
+ });
245
+
246
+ const form = useForm<FormFields>({
247
+ defaultValues: {
248
+ email: "",
249
+ password: "",
250
+ },
251
+ });
252
+
253
+ const pending =
254
+ signUpUsernameMutation.isPending || signUpByProviderMutation.isPending;
255
+
256
+ const error = signUpUsernameMutation.error ?? signUpByProviderMutation.error;
257
+
258
+ return (
259
+ <AuthCard>
260
+ <CardHeader>
261
+ <CardTitle>Sign up</CardTitle>
262
+ <CardDescription>Sign up to your account to continue.</CardDescription>
263
+ </CardHeader>
264
+ <CardContent className="flex flex-col gap-4">
265
+ {error && (
266
+ <Alert variant="destructive">
267
+ <AlertTitle>Error</AlertTitle>
268
+ <AlertDescription>{error?.message}</AlertDescription>
269
+ </Alert>
270
+ )}
271
+
272
+ <EmailPasswordForm
273
+ form={form}
274
+ onSubmit={(data) =>
275
+ void signUpUsernameMutation.mutate({
276
+ email: data.email,
277
+ password: data.password,
278
+ })
279
+ }
280
+ submitLabel="Sign up"
281
+ isPending={pending}
282
+ />
283
+ <ProviderSeparator providers={providers} />
284
+ <ProviderButtons
285
+ providers={providers}
286
+ onClick={(providerId) =>
287
+ signUpByProviderMutation.mutate({ providerId })
288
+ }
289
+ />
290
+ <Link to="/signin" className="text-sm text-muted-foreground">
291
+ Already have an account? Sign in.
292
+ </Link>
293
+ </CardContent>
294
+ </AuthCard>
295
+ );
296
+ };
297
+
298
+ const AuthCard = createVariantComponent(Card, "max-w-md w-full mt-10 mx-auto");
299
+
300
+ const ProviderButtons = ({
301
+ providers,
302
+ onClick,
303
+ }: {
304
+ providers: AuthProviderId[];
305
+ onClick: (providerId: string) => void;
306
+ }) => {
307
+ return (
308
+ <div
309
+ className={cn(
310
+ "grid grid-cols-2 gap-2",
311
+ providers.length % 2 === 0 ? "grid-cols-2" : "grid-cols-1",
312
+ )}
313
+ >
314
+ {providers.map((provider) => (
315
+ <AuthProviderButton
316
+ key={provider}
317
+ providerId={provider}
318
+ onClick={() => onClick(provider)}
319
+ />
320
+ ))}
321
+ </div>
322
+ );
323
+ };
324
+
325
+ const ProviderSeparator = ({ providers }: { providers: AuthProviderId[] }) => {
326
+ return (
327
+ providers.length > 0 && (
328
+ <Separator className="my-3 relative">
329
+ <span className="bg-card text-muted-foreground text-sm px-2 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
330
+ or continue with
331
+ </span>
332
+ </Separator>
333
+ )
334
+ );
335
+ };
@@ -0,0 +1,10 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ const AppleIcon = (props: SVGProps<SVGSVGElement>) => (
4
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
5
+ <title>{"Apple"}</title>
6
+ <path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" />
7
+ </svg>
8
+ );
9
+
10
+ export default AppleIcon;
@@ -0,0 +1,15 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ const FacebookIcon = (props: SVGProps<SVGSVGElement>) => (
4
+ <svg
5
+ fill="#0866FF"
6
+ viewBox="0 0 24 24"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ {...props}
9
+ >
10
+ <title>{"Facebook"}</title>
11
+ <path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z" />
12
+ </svg>
13
+ );
14
+
15
+ export default FacebookIcon;
@@ -0,0 +1,16 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ const GithubIcon = (props: SVGProps<SVGSVGElement>) => (
4
+ <svg
5
+ viewBox="0 0 24 24"
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ width="1em"
8
+ height="1em"
9
+ {...props}
10
+ >
11
+ <title>{"GitHub"}</title>
12
+ <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
13
+ </svg>
14
+ );
15
+
16
+ export default GithubIcon;