@rebasepro/auth 0.0.1-canary.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/LICENSE +6 -0
- package/dist/api.d.ts +119 -0
- package/dist/components/AdminViews.d.ts +20 -0
- package/dist/components/RebaseLoginView.d.ts +52 -0
- package/dist/hooks/useBackendUserManagement.d.ts +41 -0
- package/dist/hooks/useRebaseAuthController.d.ts +9 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.es.js +1883 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +1883 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/package.json +48 -0
- package/src/api.ts +328 -0
- package/src/components/AdminViews.tsx +795 -0
- package/src/components/RebaseLoginView.tsx +570 -0
- package/src/hooks/useBackendUserManagement.ts +407 -0
- package/src/hooks/useRebaseAuthController.ts +692 -0
- package/src/index.ts +28 -0
- package/src/types.ts +102 -0
package/src/api.ts
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { AuthResponse, RefreshResponse, Session, UserInfo } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default API URL - can be overridden in hook props
|
|
5
|
+
*/
|
|
6
|
+
let baseApiUrl = "";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configure the API base URL
|
|
10
|
+
*/
|
|
11
|
+
export function setApiUrl(url: string): void {
|
|
12
|
+
baseApiUrl = url;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the current API URL
|
|
17
|
+
*/
|
|
18
|
+
export function getApiUrl(): string {
|
|
19
|
+
return baseApiUrl;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class AuthApiError extends Error {
|
|
23
|
+
code: string;
|
|
24
|
+
|
|
25
|
+
constructor(message: string, code: string) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.code = code;
|
|
28
|
+
this.name = "AuthApiError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function handleResponse<T>(response: Response): Promise<T> {
|
|
33
|
+
let data: Record<string, unknown>;
|
|
34
|
+
try {
|
|
35
|
+
data = await response.json();
|
|
36
|
+
} catch (parseError) {
|
|
37
|
+
// Response wasn't JSON - could be network error or server issue
|
|
38
|
+
throw new AuthApiError(
|
|
39
|
+
`Server returned non-JSON response (status: ${response.status})`,
|
|
40
|
+
"PARSE_ERROR"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new AuthApiError(
|
|
46
|
+
(data as Record<string, Record<string, string>>).error?.message || "Request failed",
|
|
47
|
+
(data as Record<string, Record<string, string>>).error?.code || "UNKNOWN_ERROR"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return data as T;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Wrapper for fetch that catches generic network failures (like server down)
|
|
56
|
+
* and translates them to an AuthApiError.
|
|
57
|
+
*/
|
|
58
|
+
async function fetchWithHandling(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
|
59
|
+
try {
|
|
60
|
+
return await fetch(input, init);
|
|
61
|
+
} catch (error: unknown) {
|
|
62
|
+
if (error instanceof TypeError && error.message.includes("Failed to fetch")) {
|
|
63
|
+
throw new AuthApiError(
|
|
64
|
+
"Failed to connect to the backend server. The backend might be down or failed to initialize (e.g., database connection timeout).",
|
|
65
|
+
"NETWORK_ERROR"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
throw new AuthApiError("Network error: " + (error instanceof Error ? error.message : String(error)), "NETWORK_ERROR");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Register a new user with email/password
|
|
74
|
+
*/
|
|
75
|
+
export async function register(
|
|
76
|
+
email: string,
|
|
77
|
+
password: string,
|
|
78
|
+
displayName?: string
|
|
79
|
+
): Promise<AuthResponse> {
|
|
80
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/register`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify({ email, password, displayName })
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return handleResponse<AuthResponse>(response);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Login with email/password
|
|
91
|
+
*/
|
|
92
|
+
export async function login(email: string, password: string): Promise<AuthResponse> {
|
|
93
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/login`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ email, password })
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return handleResponse<AuthResponse>(response);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Login with Google ID token
|
|
104
|
+
*/
|
|
105
|
+
export async function googleLogin(idToken: string): Promise<AuthResponse> {
|
|
106
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/google`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "Content-Type": "application/json" },
|
|
109
|
+
body: JSON.stringify({ idToken })
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return handleResponse<AuthResponse>(response);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Refresh access token using refresh token
|
|
117
|
+
*/
|
|
118
|
+
export async function refreshAccessToken(refreshToken: string): Promise<RefreshResponse> {
|
|
119
|
+
console.log("[AUTH-API] Calling refresh endpoint...");
|
|
120
|
+
|
|
121
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/refresh`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { "Content-Type": "application/json" },
|
|
124
|
+
body: JSON.stringify({ refreshToken })
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
console.log("[AUTH-API] Refresh response status:", response.status);
|
|
128
|
+
return handleResponse<RefreshResponse>(response);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Logout and invalidate refresh token
|
|
133
|
+
*/
|
|
134
|
+
export async function logout(refreshToken?: string): Promise<void> {
|
|
135
|
+
await fetchWithHandling(`${baseApiUrl}/api/auth/logout`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Content-Type": "application/json" },
|
|
138
|
+
body: JSON.stringify({ refreshToken })
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get current user info
|
|
144
|
+
*/
|
|
145
|
+
export async function getCurrentUser(accessToken: string): Promise<{ user: UserInfo }> {
|
|
146
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/me`, {
|
|
147
|
+
method: "GET",
|
|
148
|
+
headers: {
|
|
149
|
+
"Content-Type": "application/json",
|
|
150
|
+
"Authorization": `Bearer ${accessToken}`
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return handleResponse<{ user: UserInfo }>(response);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Request password reset email
|
|
159
|
+
*/
|
|
160
|
+
export async function forgotPassword(email: string): Promise<{ success: boolean; message: string }> {
|
|
161
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/forgot-password`, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: { "Content-Type": "application/json" },
|
|
164
|
+
body: JSON.stringify({ email })
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return handleResponse<{ success: boolean; message: string }>(response);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Reset password using token from email
|
|
172
|
+
*/
|
|
173
|
+
export async function resetPassword(token: string, password: string): Promise<{ success: boolean; message: string }> {
|
|
174
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/reset-password`, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: { "Content-Type": "application/json" },
|
|
177
|
+
body: JSON.stringify({ token, password })
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return handleResponse<{ success: boolean; message: string }>(response);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Change password for authenticated user
|
|
185
|
+
*/
|
|
186
|
+
export async function changePassword(
|
|
187
|
+
accessToken: string,
|
|
188
|
+
oldPassword: string,
|
|
189
|
+
newPassword: string
|
|
190
|
+
): Promise<{ success: boolean; message: string }> {
|
|
191
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/change-password`, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
"Authorization": `Bearer ${accessToken}`
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify({ oldPassword, newPassword })
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return handleResponse<{ success: boolean; message: string }>(response);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Send email verification link
|
|
205
|
+
*/
|
|
206
|
+
export async function sendVerificationEmail(accessToken: string): Promise<{ success: boolean; message: string }> {
|
|
207
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/send-verification`, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: {
|
|
210
|
+
"Content-Type": "application/json",
|
|
211
|
+
"Authorization": `Bearer ${accessToken}`
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return handleResponse<{ success: boolean; message: string }>(response);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Verify email address using token
|
|
220
|
+
*/
|
|
221
|
+
export async function verifyEmail(token: string): Promise<{ success: boolean; message: string }> {
|
|
222
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`, {
|
|
223
|
+
method: "GET",
|
|
224
|
+
headers: { "Content-Type": "application/json" }
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return handleResponse<{ success: boolean; message: string }>(response);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Update current user profile
|
|
232
|
+
*/
|
|
233
|
+
export async function updateProfile(
|
|
234
|
+
accessToken: string,
|
|
235
|
+
displayName?: string,
|
|
236
|
+
photoURL?: string
|
|
237
|
+
): Promise<{ user: UserInfo }> {
|
|
238
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/me`, {
|
|
239
|
+
method: "PATCH",
|
|
240
|
+
headers: {
|
|
241
|
+
"Content-Type": "application/json",
|
|
242
|
+
"Authorization": `Bearer ${accessToken}`
|
|
243
|
+
},
|
|
244
|
+
body: JSON.stringify({ displayName, photoURL })
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return handleResponse<{ user: UserInfo }>(response);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Fetch active sessions for current user
|
|
252
|
+
*/
|
|
253
|
+
export async function fetchSessions(accessToken: string, currentRefreshToken?: string): Promise<{ sessions: Session[] }> {
|
|
254
|
+
const headers: Record<string, string> = {
|
|
255
|
+
"Content-Type": "application/json",
|
|
256
|
+
"Authorization": `Bearer ${accessToken}`
|
|
257
|
+
};
|
|
258
|
+
if (currentRefreshToken) {
|
|
259
|
+
headers["X-Refresh-Token"] = currentRefreshToken;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions`, {
|
|
263
|
+
method: "GET",
|
|
264
|
+
headers
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return handleResponse<{ sessions: Session[] }>(response);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Revoke a specific session
|
|
272
|
+
*/
|
|
273
|
+
export async function revokeSession(accessToken: string, sessionId: string): Promise<{ success: boolean; message: string }> {
|
|
274
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions/${sessionId}`, {
|
|
275
|
+
method: "DELETE",
|
|
276
|
+
headers: {
|
|
277
|
+
"Content-Type": "application/json",
|
|
278
|
+
"Authorization": `Bearer ${accessToken}`
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
return handleResponse<{ success: boolean; message: string }>(response);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Revoke all sessions for current user
|
|
287
|
+
*/
|
|
288
|
+
export async function revokeAllSessions(accessToken: string): Promise<{ success: boolean; message: string }> {
|
|
289
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions`, {
|
|
290
|
+
method: "DELETE",
|
|
291
|
+
headers: {
|
|
292
|
+
"Content-Type": "application/json",
|
|
293
|
+
"Authorization": `Bearer ${accessToken}`
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
return handleResponse<{ success: boolean; message: string }>(response);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Auth config response from the backend
|
|
302
|
+
*/
|
|
303
|
+
export interface AuthConfigResponse {
|
|
304
|
+
/** True when there are no users in the system and first user setup is needed */
|
|
305
|
+
needsSetup: boolean;
|
|
306
|
+
/** Whether new user registration is enabled */
|
|
307
|
+
registrationEnabled: boolean;
|
|
308
|
+
/** Whether Google OAuth is configured */
|
|
309
|
+
googleEnabled: boolean;
|
|
310
|
+
/** Whether email service is configured */
|
|
311
|
+
emailServiceEnabled: boolean;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Fetch auth configuration / status from the backend
|
|
316
|
+
* This is an unauthenticated endpoint used to detect bootstrap mode
|
|
317
|
+
*/
|
|
318
|
+
export async function fetchAuthConfig(): Promise<AuthConfigResponse> {
|
|
319
|
+
const response = await fetchWithHandling(`${baseApiUrl}/api/auth/config`, {
|
|
320
|
+
method: "GET",
|
|
321
|
+
headers: { "Content-Type": "application/json" }
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return handleResponse<AuthConfigResponse>(response);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export { AuthApiError };
|
|
328
|
+
|