@checkmate-monitor/auth-frontend 0.1.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/src/index.tsx ADDED
@@ -0,0 +1,241 @@
1
+ import {
2
+ ApiRef,
3
+ permissionApiRef,
4
+ PermissionApi,
5
+ createFrontendPlugin,
6
+ NavbarSlot,
7
+ UserMenuItemsSlot,
8
+ UserMenuItemsBottomSlot,
9
+ } from "@checkmate-monitor/frontend-api";
10
+ import {
11
+ LoginPage,
12
+ LoginNavbarAction,
13
+ LogoutMenuItem,
14
+ } from "./components/LoginPage";
15
+ import { RegisterPage } from "./components/RegisterPage";
16
+ import { AuthErrorPage } from "./components/AuthErrorPage";
17
+ import { ForgotPasswordPage } from "./components/ForgotPasswordPage";
18
+ import { ResetPasswordPage } from "./components/ResetPasswordPage";
19
+ import { authApiRef, AuthApi, AuthSession } from "./api";
20
+ import { authClient } from "./lib/auth-client";
21
+
22
+ import { usePermissions } from "./hooks/usePermissions";
23
+
24
+ import { PermissionAction } from "@checkmate-monitor/common";
25
+ import { useNavigate } from "react-router-dom";
26
+ import { Settings2 } from "lucide-react";
27
+ import { DropdownMenuItem } from "@checkmate-monitor/ui";
28
+ import { useApi } from "@checkmate-monitor/frontend-api";
29
+ import { AuthSettingsPage } from "./components/AuthSettingsPage";
30
+ import {
31
+ permissions as authPermissions,
32
+ authRoutes,
33
+ pluginMetadata,
34
+ } from "@checkmate-monitor/auth-common";
35
+ import { resolveRoute } from "@checkmate-monitor/common";
36
+
37
+ class AuthPermissionApi implements PermissionApi {
38
+ usePermission(permission: string): { loading: boolean; allowed: boolean } {
39
+ const { permissions, loading } = usePermissions();
40
+
41
+ if (loading) {
42
+ return { loading: true, allowed: false };
43
+ }
44
+
45
+ // If no user, or user has no permissions, return false
46
+ if (!permissions || permissions.length === 0) {
47
+ return { loading: false, allowed: false };
48
+ }
49
+ const allowed =
50
+ permissions.includes("*") || permissions.includes(permission);
51
+ return { loading: false, allowed };
52
+ }
53
+
54
+ useResourcePermission(
55
+ resource: string,
56
+ action: PermissionAction
57
+ ): { loading: boolean; allowed: boolean } {
58
+ const { permissions, loading } = usePermissions();
59
+
60
+ if (loading) {
61
+ return { loading: true, allowed: false };
62
+ }
63
+
64
+ if (!permissions || permissions.length === 0) {
65
+ return { loading: false, allowed: false };
66
+ }
67
+
68
+ const isWildcard = permissions.includes("*");
69
+ const hasResourceManage = permissions.includes(`${resource}.manage`);
70
+ const hasSpecificPermission = permissions.includes(`${resource}.${action}`);
71
+
72
+ // manage implies read
73
+ const isAllowed =
74
+ isWildcard ||
75
+ hasResourceManage ||
76
+ (action === "read" && hasResourceManage) ||
77
+ hasSpecificPermission;
78
+
79
+ return { loading: false, allowed: isAllowed };
80
+ }
81
+
82
+ useManagePermission(resource: string): {
83
+ loading: boolean;
84
+ allowed: boolean;
85
+ } {
86
+ return this.useResourcePermission(resource, "manage");
87
+ }
88
+ }
89
+
90
+ /**
91
+ * BetterAuthApi wraps only better-auth client methods.
92
+ * For RPC calls, use rpcApiRef.forPlugin<AuthClient>("auth") directly.
93
+ */
94
+ class BetterAuthApi implements AuthApi {
95
+ async signIn(email: string, password: string) {
96
+ const res = await authClient.signIn.email({ email, password });
97
+ if (res.error) {
98
+ const error = new Error(res.error.message || res.error.statusText);
99
+ error.name = res.error.code || "AuthError";
100
+ return { data: undefined, error };
101
+ }
102
+
103
+ const data = res.data as typeof res.data & {
104
+ session?: AuthSession["session"];
105
+ };
106
+ return {
107
+ data: {
108
+ session: data.session || {
109
+ token: data.token,
110
+ id: "session-id",
111
+ userId: data.user.id,
112
+ expiresAt: new Date(),
113
+ },
114
+ user: data.user,
115
+ } as AuthSession,
116
+ error: undefined,
117
+ };
118
+ }
119
+
120
+ async signInWithSocial(provider: string) {
121
+ const frontendUrl =
122
+ import.meta.env.VITE_FRONTEND_URL || "http://localhost:5173";
123
+ await authClient.signIn.social({
124
+ provider,
125
+ callbackURL: frontendUrl,
126
+ errorCallbackURL: `${frontendUrl}${resolveRoute(
127
+ authRoutes.routes.error
128
+ )}`,
129
+ });
130
+ }
131
+
132
+ async signOut() {
133
+ await authClient.signOut({
134
+ fetchOptions: {
135
+ onSuccess: () => {
136
+ // Redirect to frontend root after successful logout
137
+ globalThis.location.href = "/";
138
+ },
139
+ },
140
+ });
141
+ }
142
+
143
+ async getSession() {
144
+ const res = await authClient.getSession();
145
+ if (res.error) {
146
+ const error = new Error(res.error.message || res.error.statusText);
147
+ error.name = res.error.code || "AuthError";
148
+ return { data: undefined, error };
149
+ }
150
+ if (!res.data) return { data: undefined, error: undefined };
151
+
152
+ return {
153
+ data: res.data as AuthSession,
154
+ error: undefined,
155
+ };
156
+ }
157
+
158
+ useSession() {
159
+ const { data, isPending, error } = authClient.useSession();
160
+ return {
161
+ data: data as AuthSession | undefined,
162
+ isPending,
163
+ error: error as Error | undefined,
164
+ };
165
+ }
166
+ }
167
+
168
+ export const authPlugin = createFrontendPlugin({
169
+ metadata: pluginMetadata,
170
+ apis: [
171
+ {
172
+ ref: authApiRef as ApiRef<unknown>,
173
+ factory: () => new BetterAuthApi(),
174
+ },
175
+ {
176
+ ref: permissionApiRef as ApiRef<unknown>,
177
+ factory: () => new AuthPermissionApi(),
178
+ },
179
+ ],
180
+ routes: [
181
+ {
182
+ route: authRoutes.routes.login,
183
+ element: <LoginPage />,
184
+ },
185
+ {
186
+ route: authRoutes.routes.register,
187
+ element: <RegisterPage />,
188
+ },
189
+ {
190
+ route: authRoutes.routes.error,
191
+ element: <AuthErrorPage />,
192
+ },
193
+ {
194
+ route: authRoutes.routes.settings,
195
+ element: <AuthSettingsPage />,
196
+ },
197
+ {
198
+ route: authRoutes.routes.forgotPassword,
199
+ element: <ForgotPasswordPage />,
200
+ },
201
+ {
202
+ route: authRoutes.routes.resetPassword,
203
+ element: <ResetPasswordPage />,
204
+ },
205
+ ],
206
+ extensions: [
207
+ {
208
+ id: "auth.navbar.action",
209
+ slot: NavbarSlot,
210
+ component: LoginNavbarAction,
211
+ },
212
+ {
213
+ id: "auth.user-menu.settings",
214
+ slot: UserMenuItemsSlot,
215
+ component: () => {
216
+ // Use a wrapper component to use hooks
217
+ const navigate = useNavigate();
218
+ const permissionApi = useApi(permissionApiRef);
219
+ const canManage = permissionApi.usePermission(
220
+ authPermissions.strategiesManage.id
221
+ );
222
+
223
+ if (!canManage.allowed) return;
224
+
225
+ return (
226
+ <DropdownMenuItem
227
+ onClick={() => navigate(resolveRoute(authRoutes.routes.settings))}
228
+ icon={<Settings2 className="h-4 w-4" />}
229
+ >
230
+ Auth Settings
231
+ </DropdownMenuItem>
232
+ );
233
+ },
234
+ },
235
+ {
236
+ id: "auth.user-menu.logout",
237
+ slot: UserMenuItemsBottomSlot,
238
+ component: LogoutMenuItem,
239
+ },
240
+ ],
241
+ });
@@ -0,0 +1,6 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+
3
+ export const authClient = createAuthClient({
4
+ baseURL: import.meta.env.VITE_API_BASE_URL || "http://localhost:3000",
5
+ basePath: "/api/auth",
6
+ });
@@ -0,0 +1,3 @@
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:27abb4affaed83ae85739b62b71e5e860d2e6ee7b4fa91c915f5a3c26227b944
3
+ size 4253
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "@checkmate-monitor/tsconfig/frontend.json",
3
+ "include": [
4
+ "src"
5
+ ]
6
+ }