forked from Taegon21/devroom-client
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Integrate login with Cognito and set up API (Taegon21#31)
* ✨ Implement login with AWS Cognito * ✨ Manage idToken with cookie * ✨ Integrate API logic with React Query * ✨ Create API integration test page
- Loading branch information
Showing
19 changed files
with
1,510 additions
and
92 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import { | ||
CognitoUserPool, | ||
CognitoUserAttribute, | ||
ISignUpResult, | ||
AuthenticationDetails, | ||
CognitoUser, | ||
} from "amazon-cognito-identity-js"; | ||
|
||
interface SignUpParams { | ||
email: string; | ||
password: string; | ||
name: string; // custom:name 속성 | ||
role: string; // custom:role 속성 | ||
studentId: string; // custom:student_id 속성 | ||
onSuccess: (result: ISignUpResult) => void; | ||
onFailure: (error: Error) => void; | ||
} | ||
|
||
interface AuthResponse { | ||
idToken: string; | ||
name: string; // 사용자의 이름 | ||
role: string; // 사용자의 역할 | ||
studentId: string; // 학생 ID | ||
} | ||
|
||
interface VerifyEmailParams { | ||
username: string; // 이메일 주소 | ||
code: string; // 사용자로부터 받은 인증 코드 | ||
} | ||
|
||
const USER_POOL_ID = process.env.NEXT_PUBLIC_COGNITO_USER_POOL_ID; | ||
const CLIENT_ID = process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID; | ||
|
||
if (!USER_POOL_ID || !CLIENT_ID) { | ||
throw new Error( | ||
"Cognito credentials are not properly set in the environment variables." | ||
); | ||
} | ||
|
||
const poolData = { | ||
UserPoolId: USER_POOL_ID, | ||
ClientId: CLIENT_ID, | ||
}; | ||
|
||
const userPool = new CognitoUserPool(poolData); | ||
|
||
export function signUp({ | ||
email, | ||
password, | ||
name, | ||
role, | ||
studentId, | ||
onSuccess, | ||
onFailure, | ||
}: SignUpParams): void { | ||
const username = email; | ||
const attributeList = [ | ||
new CognitoUserAttribute({ | ||
Name: "email", | ||
Value: email, | ||
}), | ||
new CognitoUserAttribute({ | ||
Name: "custom:name", | ||
Value: name, | ||
}), | ||
new CognitoUserAttribute({ | ||
Name: "custom:role", | ||
Value: role, | ||
}), | ||
new CognitoUserAttribute({ | ||
Name: "custom:student_id", | ||
Value: studentId, | ||
}), | ||
]; | ||
|
||
userPool.signUp(username, password, attributeList, [], (err, result) => { | ||
if (err) { | ||
onFailure(err); | ||
return; | ||
} | ||
if (result) { | ||
onSuccess(result); | ||
} | ||
}); | ||
} | ||
|
||
export function authenticateCognitoUser( | ||
email: string, | ||
password: string | ||
): Promise<AuthResponse> { | ||
const authenticationDetails = new AuthenticationDetails({ | ||
Username: email, | ||
Password: password, | ||
}); | ||
|
||
const userData = { | ||
Username: email, | ||
Pool: userPool, | ||
}; | ||
|
||
const cognitoUser = new CognitoUser(userData); | ||
return new Promise((resolve, reject) => { | ||
cognitoUser.authenticateUser(authenticationDetails, { | ||
onSuccess: (result) => { | ||
const idToken = result.getIdToken().getJwtToken(); | ||
const claims = result.getIdToken().decodePayload(); | ||
|
||
// custom 속성 가져오기 | ||
const name = claims["custom:name"] || ""; | ||
const role = claims["custom:role"] || ""; | ||
const studentId = claims["custom:student_id"] || ""; | ||
|
||
resolve({ | ||
idToken, | ||
name, | ||
role, | ||
studentId, | ||
}); | ||
}, | ||
onFailure: (err) => { | ||
reject(err); | ||
}, | ||
}); | ||
}); | ||
} | ||
|
||
export function verifyEmail({ | ||
username, | ||
code, | ||
}: VerifyEmailParams): Promise<void> { | ||
const userData = { | ||
Username: username, | ||
Pool: userPool, | ||
}; | ||
|
||
const cognitoUser = new CognitoUser(userData); | ||
|
||
return new Promise((resolve, reject) => { | ||
cognitoUser.confirmRegistration(code, true, (err, result) => { | ||
if (err) { | ||
reject(err); | ||
return; | ||
} | ||
resolve(); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import axios from "axios"; | ||
import Cookies from "js-cookie"; | ||
|
||
const apiUrl = process.env.NEXT_PUBLIC_API_URL; | ||
|
||
const apiClient = axios.create({ | ||
baseURL: apiUrl, | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
|
||
apiClient.interceptors.request.use( | ||
async (config) => { | ||
const token = Cookies.get("idToken"); | ||
console.log("🚀 ~ file: client.ts:16 ~ token:", token); | ||
if (token) { | ||
config.headers.Authorization = `Bearer ${token}`; | ||
} | ||
return config; | ||
}, | ||
(error) => { | ||
console.log("error", error); | ||
return Promise.reject(error); | ||
} | ||
); | ||
|
||
export default apiClient; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const API_ENDPOINTS = { | ||
STUDENT: { | ||
SERVICE: (studentId: string) => `/service/${studentId}`, | ||
POD: (studentId: string) => `/pod/${studentId}`, | ||
DEPLOY: (studentId: string) => `/deploy/${studentId}`, | ||
}, | ||
PROFESSOR: { | ||
CHECK: (professorId: string) => `/class/${professorId}/pod`, | ||
CREATE: (professorId: string) => `/class/${professorId}/create`, | ||
DELETE: (professorId: string) => `/class/${professorId}/delete`, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import apiClient from "@/api/client"; | ||
import { API_ENDPOINTS } from "@/api/endpoints"; | ||
import { useQuery, useMutation } from "@tanstack/react-query"; | ||
|
||
const fetchCheck = async (professorId: string) => { | ||
const { data } = await apiClient.get( | ||
API_ENDPOINTS.PROFESSOR.CHECK(professorId) | ||
); | ||
return data; | ||
}; | ||
|
||
export const useFetchCheck = (professorId: string) => { | ||
return useQuery({ | ||
queryKey: ["fetchDeploy", professorId], | ||
queryFn: () => fetchCheck(professorId), | ||
}); | ||
}; | ||
|
||
interface ClassCreationData { | ||
className: string; | ||
studentIds: string[]; | ||
options: { [key: string]: string }; | ||
command: string[]; | ||
customScript: string; | ||
} | ||
|
||
interface CreateClassArgs { | ||
professorId: string; | ||
classData: ClassCreationData; | ||
} | ||
|
||
const createClass = async ({ professorId, classData }: CreateClassArgs) => { | ||
const { data } = await apiClient.post( | ||
API_ENDPOINTS.PROFESSOR.CREATE(professorId), | ||
classData | ||
); | ||
return data; | ||
}; | ||
|
||
export const useCreateClass = () => { | ||
return useMutation({ | ||
mutationFn: createClass, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import apiClient from "@/api/client"; | ||
import { API_ENDPOINTS } from "@/api/endpoints"; | ||
import { useQuery } from "@tanstack/react-query"; | ||
|
||
const fetchService = async (studentId: string) => { | ||
const { data } = await apiClient.get( | ||
API_ENDPOINTS.STUDENT.SERVICE(studentId) | ||
); | ||
return data; | ||
}; | ||
|
||
const fetchPod = async (studentId: string) => { | ||
const { data } = await apiClient.get(API_ENDPOINTS.STUDENT.POD(studentId)); | ||
return data; | ||
}; | ||
|
||
const fetchDeploy = async (studentId: string) => { | ||
const { data } = await apiClient.get(API_ENDPOINTS.STUDENT.DEPLOY(studentId)); | ||
return data; | ||
}; | ||
|
||
export const useFetchService = (studentId: string) => { | ||
return useQuery({ | ||
queryKey: ["fetchService", studentId], | ||
queryFn: () => fetchService(studentId), | ||
}); | ||
}; | ||
|
||
export const useFetchPod = (studentId: string) => { | ||
return useQuery({ | ||
queryKey: ["fetchPod", studentId], | ||
queryFn: () => fetchPod(studentId), | ||
}); | ||
}; | ||
|
||
export const useFetchDeploy = (studentId: string) => { | ||
return useQuery({ | ||
queryKey: ["fetchDeploy", studentId], | ||
queryFn: () => fetchDeploy(studentId), | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
"use client"; | ||
|
||
import React from "react"; | ||
import { | ||
useFetchService, | ||
useFetchPod, | ||
useFetchDeploy, | ||
} from "@/api/hooks/useStudent"; | ||
|
||
const StudentInfoComponent = () => { | ||
const studentId = "2019312430"; | ||
const { | ||
data: serviceData, | ||
isLoading: isLoadingService, | ||
error: errorService, | ||
} = useFetchService(studentId); | ||
const { | ||
data: podData, | ||
isLoading: isLoadingPod, | ||
error: errorPod, | ||
} = useFetchPod(studentId); | ||
const { | ||
data: deployData, | ||
isLoading: isLoadingDeploy, | ||
error: errorDeploy, | ||
} = useFetchDeploy(studentId); | ||
|
||
// 로딩 상태 처리 | ||
if (isLoadingService || isLoadingPod || isLoadingDeploy) { | ||
return <div>Loading...</div>; | ||
} | ||
|
||
// 에러 상태 처리 | ||
if (errorService || errorPod || errorDeploy) { | ||
return <div>Error loading data. Please try again later.</div>; | ||
} | ||
|
||
return ( | ||
<div> | ||
<h1>Student Information</h1> | ||
<div> | ||
<h2>Service Details</h2> | ||
<p>{JSON.stringify(serviceData, null, 2)}</p> | ||
</div> | ||
<div> | ||
<h2>Pod Details</h2> | ||
<p>{JSON.stringify(podData, null, 2)}</p> | ||
</div> | ||
<div> | ||
<h2>Deployment Details</h2> | ||
<p>{JSON.stringify(deployData, null, 2)}</p> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default StudentInfoComponent; |
Oops, something went wrong.