Skip to content

Commit f0b13fa

Browse files
Step 2 - Implement Comprehensive Authentication Hooks and Cognito Integration
- Refactor authentication flow to use AWS Cognito - Add granular authentication hooks for various auth operations - Implement user token and current user retrieval from Cognito - Update AuthContext and AuthProvider to support new authentication methods - Add support for email/password and social sign-in flows
1 parent f02356a commit f0b13fa

File tree

6 files changed

+423
-52
lines changed

6 files changed

+423
-52
lines changed

frontend/src/common/api/useGetCurrentUser.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,42 @@
11
import { useQuery } from '@tanstack/react-query';
22

3-
import { User } from 'common/models/user';
4-
import { QueryKey, StorageKey } from 'common/utils/constants';
5-
import storage from 'common/utils/storage';
3+
import { CognitoUser } from 'common/models/user';
4+
import { QueryKey } from 'common/utils/constants';
5+
import CognitoAuthService from 'common/services/auth/cognito-auth-service';
6+
import { mapCognitoUserToAppUser } from 'common/utils/user-mapper';
67

78
/**
8-
* An API hook which fetches the currently authenticated `User`.
9-
* @returns Returns a `UseQueryResult` with `User` data.
9+
* An API hook which fetches the currently authenticated user from Cognito.
10+
* @returns Returns a `UseQueryResult` with `CognitoUser` data.
1011
*/
1112
export const useGetCurrentUser = () => {
1213
/**
13-
* Fetch the details about the currently authenticated `User`.
14-
* @returns A Promise which resolves to a `User`.
14+
* Fetch the details about the currently authenticated user.
15+
* @returns A Promise which resolves to a `CognitoUser`.
1516
*/
16-
const getCurentUser = (): Promise<User> => {
17-
// TODO: This is contrived, example logic. Replace with an actual
18-
// integration with your Identity Provider, IdP.
19-
return new Promise((resolve, reject) => {
20-
try {
21-
const storedUser = storage.getItem(StorageKey.User);
22-
if (storedUser) {
23-
const user = JSON.parse(storedUser) as unknown as User;
24-
return resolve(user);
25-
}
26-
return reject(new Error('Not found.'));
27-
} catch (err) {
28-
return reject(err);
17+
const getCurentUser = async (): Promise<CognitoUser> => {
18+
try {
19+
// Get current user from Cognito
20+
const cognitoUser = await CognitoAuthService.getCurrentUser();
21+
22+
if (!cognitoUser) {
23+
throw new Error('User not found');
2924
}
30-
});
25+
26+
// Map Cognito user data to our application's user model
27+
const userData = {
28+
username: cognitoUser.username || '',
29+
attributes: {
30+
// Extract whatever attributes are available from the user object
31+
email: cognitoUser.signInDetails?.loginId || '',
32+
}
33+
};
34+
35+
return mapCognitoUserToAppUser(userData);
36+
} catch (error) {
37+
console.error('Error getting current user:', error);
38+
throw error;
39+
}
3140
};
3241

3342
return useQuery({
Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,34 @@
11
import { useQuery } from '@tanstack/react-query';
2-
import dayjs from 'dayjs';
32

43
import { UserTokens } from 'common/models/auth';
5-
import { QueryKey, StorageKey } from 'common/utils/constants';
6-
import storage from 'common/utils/storage';
4+
import { QueryKey } from 'common/utils/constants';
5+
import CognitoAuthService from 'common/services/auth/cognito-auth-service';
76

87
/**
9-
* An API hook which fetches tokens from the application Identity Provider, IdP.
10-
* This hook contains example logic which should be replaced with an actual
11-
* IdP integration.
8+
* An API hook which fetches tokens from AWS Cognito.
129
* @returns Returns a `UseQueryResult` with `UserTokens` data.
1310
*/
1411
export const useGetUserTokens = () => {
1512
const getUserTokens = async (): Promise<UserTokens> => {
16-
// TODO: replace with logic to fetch tokens from your IdP
17-
return new Promise((resolve, reject) => {
18-
const storedTokens = storage.getItem(StorageKey.UserTokens);
19-
20-
if (storedTokens) {
21-
// tokens found
22-
const tokens = JSON.parse(storedTokens) as unknown as UserTokens;
23-
const now = dayjs();
24-
if (now.isBefore(tokens.expires_at)) {
25-
// tokens not expired
26-
return resolve(tokens);
27-
} else {
28-
// tokens expired
29-
return reject(new Error('Tokens expired.'));
30-
}
13+
try {
14+
// Get tokens from Cognito
15+
const tokens = await CognitoAuthService.getUserTokens();
16+
17+
if (!tokens) {
18+
throw new Error('Tokens not found.');
3119
}
32-
33-
// tokens not found
34-
return reject(new Error('Tokens not found.'));
35-
});
20+
21+
return tokens;
22+
} catch (error) {
23+
console.error('Error getting user tokens:', error);
24+
throw error;
25+
}
3626
};
3727

3828
return useQuery({
3929
queryKey: [QueryKey.UserTokens],
4030
queryFn: () => getUserTokens(),
4131
retry: 0,
42-
refetchInterval: 60000,
32+
refetchInterval: 60000, // Refresh tokens every minute
4333
});
4434
};

frontend/src/common/hooks/useAuth.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,75 @@ import { AuthContext, AuthContextValue } from 'common/providers/AuthContext';
99
export const useAuth = (): AuthContextValue => {
1010
return useContext(AuthContext);
1111
};
12+
13+
/**
14+
* Check if a user is authenticated
15+
* @returns {boolean} True if authenticated, false otherwise
16+
*/
17+
export const useIsAuthenticated = (): boolean => {
18+
const { isAuthenticated } = useAuth();
19+
return isAuthenticated;
20+
};
21+
22+
/**
23+
* Get the current user
24+
* @returns The current user or undefined if not authenticated
25+
*/
26+
export const useCurrentUser = () => {
27+
const { user } = useAuth();
28+
return user;
29+
};
30+
31+
/**
32+
* Get authentication loading state
33+
* @returns True if authentication is in progress, false otherwise
34+
*/
35+
export const useAuthLoading = (): boolean => {
36+
const { isLoading } = useAuth();
37+
return isLoading;
38+
};
39+
40+
/**
41+
* Get authentication error
42+
* @returns The current authentication error or undefined
43+
*/
44+
export const useAuthError = () => {
45+
const { error, clearError } = useAuth();
46+
return { error, clearError };
47+
};
48+
49+
/**
50+
* Hook for using sign in functionality
51+
* @returns The sign in function and loading state
52+
*/
53+
export const useSignIn = () => {
54+
const { signIn, isLoading } = useAuth();
55+
return { signIn, isLoading };
56+
};
57+
58+
/**
59+
* Hook for using sign up functionality
60+
* @returns The sign up function and loading state
61+
*/
62+
export const useSignUp = () => {
63+
const { signUp, isLoading } = useAuth();
64+
return { signUp, isLoading };
65+
};
66+
67+
/**
68+
* Hook for using sign out functionality
69+
* @returns The sign out function and loading state
70+
*/
71+
export const useSignOut = () => {
72+
const { signOut, isLoading } = useAuth();
73+
return { signOut, isLoading };
74+
};
75+
76+
/**
77+
* Hook for using social sign in functionality
78+
* @returns Functions for Google and Apple sign in
79+
*/
80+
export const useSocialSignIn = () => {
81+
const { signInWithGoogle, signInWithApple, isLoading } = useAuth();
82+
return { signInWithGoogle, signInWithApple, isLoading };
83+
};
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { useState } from 'react';
2+
import { AuthError } from 'common/models/auth';
3+
import { CognitoUser } from 'common/models/user';
4+
import CognitoAuthService from 'common/services/auth/cognito-auth-service';
5+
import { formatAuthError } from 'common/utils/auth-errors';
6+
import { mapCognitoUserToAppUser } from 'common/utils/user-mapper';
7+
8+
/**
9+
* Hook for authentication operations
10+
* @returns Authentication methods and state
11+
*/
12+
export const useAuthOperations = () => {
13+
const [isLoading, setIsLoading] = useState(false);
14+
const [error, setError] = useState<AuthError | undefined>(undefined);
15+
const [user, setUser] = useState<CognitoUser | undefined>(undefined);
16+
17+
/**
18+
* Clear any authentication errors
19+
*/
20+
const clearError = () => setError(undefined);
21+
22+
/**
23+
* Sign in with email and password
24+
* @param email User's email
25+
* @param password User's password
26+
*/
27+
const signIn = async (email: string, password: string): Promise<void> => {
28+
setIsLoading(true);
29+
clearError();
30+
31+
try {
32+
await CognitoAuthService.signIn(email, password);
33+
// Convert the result to the structure expected by mapCognitoUserToAppUser
34+
const userData = {
35+
username: email,
36+
attributes: {
37+
email,
38+
// Other attributes would be filled from the session if needed
39+
}
40+
};
41+
const cognitoUser = mapCognitoUserToAppUser(userData);
42+
setUser(cognitoUser);
43+
} catch (err) {
44+
setError(formatAuthError(err));
45+
throw err;
46+
} finally {
47+
setIsLoading(false);
48+
}
49+
};
50+
51+
/**
52+
* Sign up a new user
53+
* @param email User's email
54+
* @param password User's password
55+
* @param firstName User's first name
56+
* @param lastName User's last name
57+
*/
58+
const signUp = async (
59+
email: string,
60+
password: string,
61+
firstName: string,
62+
lastName: string
63+
): Promise<void> => {
64+
setIsLoading(true);
65+
clearError();
66+
67+
try {
68+
await CognitoAuthService.signUp(email, password, { firstName, lastName });
69+
// Don't set the user yet - confirmation is required first
70+
} catch (err) {
71+
setError(formatAuthError(err));
72+
throw err;
73+
} finally {
74+
setIsLoading(false);
75+
}
76+
};
77+
78+
/**
79+
* Confirm a user's sign up with verification code
80+
* @param email User's email
81+
* @param code Verification code
82+
*/
83+
const confirmSignUp = async (email: string, code: string): Promise<void> => {
84+
setIsLoading(true);
85+
clearError();
86+
87+
try {
88+
await CognitoAuthService.confirmSignUp(email, code);
89+
// User confirmed, but not yet signed in
90+
} catch (err) {
91+
setError(formatAuthError(err));
92+
throw err;
93+
} finally {
94+
setIsLoading(false);
95+
}
96+
};
97+
98+
/**
99+
* Resend confirmation code to user's email
100+
* @param email User's email
101+
*/
102+
const resendConfirmationCode = async (email: string): Promise<void> => {
103+
setIsLoading(true);
104+
clearError();
105+
console.log('resendConfirmationCode', email);
106+
107+
try {
108+
// This would need to be implemented in the Cognito service
109+
// For now, we'll throw an error
110+
throw new Error('Not implemented');
111+
} catch (err) {
112+
setError(formatAuthError(err));
113+
throw err;
114+
} finally {
115+
setIsLoading(false);
116+
}
117+
};
118+
119+
/**
120+
* Sign out the current user
121+
*/
122+
const signOut = async (): Promise<void> => {
123+
setIsLoading(true);
124+
clearError();
125+
126+
try {
127+
await CognitoAuthService.signOut();
128+
setUser(undefined);
129+
} catch (err) {
130+
setError(formatAuthError(err));
131+
throw err;
132+
} finally {
133+
setIsLoading(false);
134+
}
135+
};
136+
137+
/**
138+
* Sign in with Google
139+
*/
140+
const signInWithGoogle = async (): Promise<void> => {
141+
setIsLoading(true);
142+
clearError();
143+
144+
try {
145+
await CognitoAuthService.federatedSignIn('Google');
146+
// Note: This doesn't complete the sign-in flow.
147+
// The user will be redirected to Google and then back to your app
148+
// Your app needs to handle the redirect URI
149+
} catch (err) {
150+
setError(formatAuthError(err));
151+
throw err;
152+
} finally {
153+
setIsLoading(false);
154+
}
155+
};
156+
157+
/**
158+
* Sign in with Apple
159+
*/
160+
const signInWithApple = async (): Promise<void> => {
161+
setIsLoading(true);
162+
clearError();
163+
164+
try {
165+
await CognitoAuthService.federatedSignIn('SignInWithApple');
166+
// Note: This doesn't complete the sign-in flow.
167+
// The user will be redirected to Apple and then back to your app
168+
// Your app needs to handle the redirect URI
169+
} catch (err) {
170+
setError(formatAuthError(err));
171+
throw err;
172+
} finally {
173+
setIsLoading(false);
174+
}
175+
};
176+
177+
return {
178+
isLoading,
179+
error,
180+
user,
181+
clearError,
182+
signIn,
183+
signUp,
184+
confirmSignUp,
185+
resendConfirmationCode,
186+
signOut,
187+
signInWithGoogle,
188+
signInWithApple,
189+
};
190+
};

0 commit comments

Comments
 (0)