Skip to content

Commit ee79dcb

Browse files
authored
Merge pull request #828 from Sim-sat/no-explicit-any-auth
fix(lint): no-explicit-any in auth and removal of magiclink method
2 parents 70d770d + 61a2a15 commit ee79dcb

3 files changed

Lines changed: 62 additions & 195 deletions

File tree

SparkyFitnessFrontend/src/api/Auth/auth.ts

Lines changed: 61 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,36 @@ import { authClient } from '@/lib/auth-client';
22
import type { AccessibleUser, AuthResponse, LoginSettings } from '@/types/auth';
33
import { apiCall } from '../api';
44

5+
interface AuthError extends Error {
6+
code?: string;
7+
status?: number;
8+
}
9+
10+
interface BetterAuthUser {
11+
id: string;
12+
email: string;
13+
name: string;
14+
role?: string;
15+
twoFactorEnabled?: boolean;
16+
mfaEmailEnabled?: boolean;
17+
}
18+
19+
interface BetterAuthResponse {
20+
user: BetterAuthUser;
21+
twoFactorRedirect?: boolean;
22+
}
23+
24+
export interface IdentityUserResponse {
25+
activeUserId: string;
26+
fullName: string | null;
27+
activeUserFullName?: string;
28+
activeUserEmail: string;
29+
}
30+
31+
export interface SwitchContextResponse {
32+
activeUserId?: string;
33+
}
34+
535
export const requestMagicLink = async (email: string): Promise<void> => {
636
const { error } = await authClient.signIn.magicLink({
737
email,
@@ -23,22 +53,28 @@ export const registerUser = async (
2353

2454
if (error) {
2555
if (error.status === 409) {
26-
const err = new Error('User with this email already exists.');
27-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
28-
(err as any).code = '23505';
56+
const err = new Error(
57+
'User with this email already exists.'
58+
) as AuthError;
59+
err.code = '23505';
2960
throw err;
3061
}
3162
throw error;
3263
}
3364

65+
const authData = data as BetterAuthResponse | null;
66+
67+
if (!authData?.user) {
68+
throw new Error(
69+
'Registration succeeded but no user data was received from the server.'
70+
);
71+
}
72+
3473
return {
3574
message: 'User registered successfully',
36-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
37-
userId: (data as any)?.user?.id,
38-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
39-
role: ((data as any)?.user as any)?.role || 'user',
40-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
41-
fullName: (data as any)?.user?.name || '',
75+
userId: authData?.user?.id,
76+
role: authData?.user?.role || 'user',
77+
fullName: authData?.user?.name || '',
4278
} as AuthResponse;
4379
};
4480

@@ -58,31 +94,31 @@ export const loginUser = async (
5894
throw error;
5995
}
6096

97+
const authData = data as BetterAuthResponse | null;
98+
99+
if (!authData?.user) {
100+
throw new Error(
101+
'Login succeeded but no user data was received from the server.'
102+
);
103+
}
104+
61105
// Better Auth native 2FA handling
62-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
63-
if ((data as any)?.twoFactorRedirect) {
106+
if (authData?.twoFactorRedirect) {
64107
return {
65-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
66-
userId: (data as any)?.user?.id || '',
67-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
68-
email: (data as any)?.user?.email || email,
108+
userId: authData?.user?.id || '',
109+
email: authData?.user?.email || email,
69110
status: 'MFA_REQUIRED',
70111
twoFactorRedirect: true,
71-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
72-
mfa_totp_enabled: (data as any)?.user?.twoFactorEnabled,
73-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
74-
mfa_email_enabled: (data as any)?.user?.mfaEmailEnabled,
112+
mfa_totp_enabled: authData?.user?.twoFactorEnabled,
113+
mfa_email_enabled: authData?.user?.mfaEmailEnabled,
75114
} as AuthResponse;
76115
}
77116

78117
return {
79118
message: 'Login successful',
80-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
81-
userId: (data as any)?.user?.id,
82-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
83-
role: ((data as any)?.user as any)?.role || 'user',
84-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
85-
fullName: (data as any)?.user?.name || '',
119+
userId: authData?.user?.id,
120+
role: authData?.user?.role || 'user',
121+
fullName: authData?.user?.name || '',
86122
} as AuthResponse;
87123
};
88124

@@ -141,61 +177,12 @@ export const getLoginSettings = async (): Promise<LoginSettings> => {
141177
}
142178
};
143179

144-
export const verifyMagicLink = async (token: string): Promise<AuthResponse> => {
145-
// In Better Auth 1.0, verification can also be done via signIn.magicLink token property
146-
// if the plugin is configured to support manual verification.
147-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
148-
const { data, error } = await (authClient as any).signIn.magicLink({
149-
token,
150-
});
151-
152-
if (error) throw error;
153-
154-
// Better Auth native 2FA handling after Magic Link
155-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
156-
if ((data as any)?.twoFactorRedirect) {
157-
return {
158-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
159-
userId: (data as any)?.user?.id || '',
160-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
161-
email: (data as any)?.user?.email || '',
162-
status: 'MFA_REQUIRED',
163-
twoFactorRedirect: true,
164-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
165-
mfa_totp_enabled: (data as any)?.user?.twoFactorEnabled,
166-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
167-
mfa_email_enabled: (data as any)?.user?.mfaEmailEnabled,
168-
} as AuthResponse;
169-
}
170-
171-
return {
172-
message: 'Magic link login successful',
173-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
174-
userId: (data as any)?.user?.id,
175-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
176-
role: ((data as any)?.user as any)?.role || 'user',
177-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
178-
fullName: (data as any)?.user?.name || '',
179-
} as AuthResponse;
180-
};
181-
182180
export const getMfaFactors = async (email: string) => {
183181
return await apiCall(`/auth/mfa-factors?email=${encodeURIComponent(email)}`, {
184182
method: 'GET',
185183
});
186184
};
187185

188-
export interface IdentityUserResponse {
189-
activeUserId: string;
190-
fullName: string | null;
191-
activeUserFullName?: string;
192-
activeUserEmail: string;
193-
}
194-
195-
export interface SwitchContextResponse {
196-
activeUserId?: string;
197-
}
198-
199186
export const fetchIdentityUser = async (): Promise<IdentityUserResponse> => {
200187
return apiCall('/identity/user', {
201188
method: 'GET',

SparkyFitnessFrontend/src/hooks/Auth/useAuth.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
requestMagicLink,
55
registerUser,
66
loginUser,
7-
verifyMagicLink,
87
getLoginSettings,
98
initiateOidcLogin,
109
resetPassword,
@@ -72,23 +71,6 @@ export const useRequestMagicLinkMutation = () => {
7271
});
7372
};
7473

75-
export const useVerifyMagicLinkMutation = () => {
76-
const { t } = useTranslation();
77-
return useMutation({
78-
mutationFn: verifyMagicLink,
79-
meta: {
80-
successMessage: t(
81-
'auth.magicLinkVerifySuccess',
82-
'Logged in via magic link!'
83-
),
84-
errorMessage: t(
85-
'auth.magicLinkVerifyError',
86-
'Magic link is invalid or expired.'
87-
),
88-
},
89-
});
90-
};
91-
9274
export const useAuthSettings = () => {
9375
return useQuery({
9476
queryKey: authKeys.settings,

SparkyFitnessFrontend/src/pages/Auth/Auth.tsx

Lines changed: 1 addition & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useRef, useCallback } from 'react';
1+
import { useState, useEffect, useCallback } from 'react';
22
import { useNavigate } from 'react-router-dom';
33
import { Button } from '@/components/ui/button';
44
import { Input } from '@/components/ui/input';
@@ -28,7 +28,6 @@ import {
2828
useLoginUserMutation,
2929
useRegisterUserMutation,
3030
useRequestMagicLinkMutation,
31-
useVerifyMagicLinkMutation,
3231
} from '@/hooks/Auth/useAuth';
3332
import { MagicLinkRequestDialog } from './MagicLinkRequestDialog';
3433
import { useQueryClient } from '@tanstack/react-query';
@@ -61,7 +60,6 @@ const Auth = () => {
6160
const { mutateAsync: loginUser } = useLoginUserMutation();
6261
const { mutateAsync: registerUser } = useRegisterUserMutation();
6362
const { mutateAsync: requestMagicLink } = useRequestMagicLinkMutation();
64-
const { mutateAsync: verifyMagicLink } = useVerifyMagicLinkMutation();
6563
const { mutateAsync: initiateOidcLogin } = useInitiateOidcLoginMutation();
6664

6765
useEffect(() => {
@@ -241,106 +239,6 @@ const Auth = () => {
241239
[loggingLevel, queryClient]
242240
);
243241

244-
const hasAttemptedMagicLinkLogin = useRef(false);
245-
246-
useEffect(() => {
247-
const params = new URLSearchParams(window.location.search);
248-
const magicLinkToken = params.get('token');
249-
const authError = params.get('error');
250-
const path = window.location.pathname;
251-
252-
// Handle authentication errors from redirects
253-
if (authError) {
254-
const errorMsg =
255-
authError === 'signup disabled'
256-
? 'New registrations are currently disabled for this provider.'
257-
: `Authentication failed: ${authError}`;
258-
259-
toast({
260-
title: 'Login Error',
261-
description: errorMsg,
262-
variant: 'destructive',
263-
});
264-
// Clear the error from URL by redirecting to base path
265-
navigate('/');
266-
}
267-
268-
if (
269-
path === '/login/magic-link' &&
270-
magicLinkToken &&
271-
!hasAttemptedMagicLinkLogin.current
272-
) {
273-
hasAttemptedMagicLinkLogin.current = true;
274-
info(loggingLevel, 'Auth: Attempting magic link login.');
275-
setLoading(true);
276-
const handleMagicLinkLogin = async () => {
277-
try {
278-
const data: AuthResponse = await verifyMagicLink(magicLinkToken);
279-
280-
if (data.status === 'MFA_REQUIRED') {
281-
const mfaShown = await triggerMfaChallenge(data, email, {
282-
onMfaSuccess: () => {
283-
setShowMfaChallenge(false);
284-
navigate('/');
285-
},
286-
onMfaCancel: () => {
287-
setShowMfaChallenge(false);
288-
setLoading(false);
289-
navigate('/'); // Redirect back to home page on cancel
290-
},
291-
});
292-
293-
if (!mfaShown) {
294-
signIn(
295-
data.userId,
296-
data.userId,
297-
data.email || email,
298-
data.role || 'user',
299-
'magic_link',
300-
true,
301-
data.fullName
302-
);
303-
}
304-
} else {
305-
info(loggingLevel, 'Auth: Magic link login successful.');
306-
toast({
307-
title: 'Success',
308-
description: 'Logged in successfully via magic link!',
309-
});
310-
signIn(
311-
data.userId,
312-
data.userId,
313-
data.email || email,
314-
data.role || 'user',
315-
'magic_link',
316-
true,
317-
data.fullName
318-
);
319-
}
320-
} catch (err: unknown) {
321-
const message = getErrorMessage(err);
322-
error(loggingLevel, 'Auth: Magic link login failed:', err);
323-
toast({
324-
title: 'Error',
325-
description: message || 'Magic link is invalid or has expired.',
326-
variant: 'destructive',
327-
});
328-
window.location.replace('/'); // Force a full page reload to clear state
329-
} finally {
330-
setLoading(false);
331-
}
332-
};
333-
handleMagicLinkLogin();
334-
}
335-
}, [
336-
loggingLevel,
337-
navigate,
338-
signIn,
339-
email,
340-
triggerMfaChallenge,
341-
verifyMagicLink,
342-
]);
343-
344242
const validatePassword = (pwd: string) => {
345243
if (pwd.length < 6) {
346244
return 'Password must be at least 6 characters long.';

0 commit comments

Comments
 (0)