Skip to content

Commit

Permalink
Merge pull request #24 from KernelSquare/dev
Browse files Browse the repository at this point in the history
✨ FEAT: 로그인 기능 구현 및 관리자 페이지 배포
  • Loading branch information
JeongwooHam authored Mar 28, 2024
2 parents 60f30f9 + a3097e0 commit d12fd67
Show file tree
Hide file tree
Showing 24 changed files with 445 additions and 25 deletions.
53 changes: 53 additions & 0 deletions .github/workflows/admin-cicd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Admin-CICD

on:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout source code
uses: actions/checkout@v3

- name: Install pnpm
run: npm install pnpm -g

- name: Install dependencies
run: pnpm install

- name: build
env:
VITE_API_MOCKING: ${{secrets.VITE_API_MOCKING}}
VITE_SERVER: ${{secrets.VITE_SERVER}}
VITE_CRYPTO_KEY_NAME: ${{secrets.VITE_CRYPTO_KEY_NAME}}
VITE_CRYPTO_KEY_LENGTH: ${{secrets.VITE_CRYPTO_KEY_LENGTH}}
run: pnpm run build

deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_S3_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_S3_SECRET_ACCESS_KEY_ID }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Upload to S3
env:
BUCKET_NAME: ${{ secrets.AWS_S3_BUCKET_NAME}}
run: |
aws s3 sync \
./build s3://$BUCKET_NAME
- name: Invalidate CloudFront Cache
uses: chetan/invalidate-cloudfront-action@master
env:
AWS_DISTRIBUTION: ${{ secrets.AWS_DISTRIBUTION_ID }}
PATHS: '/index.html'
continue-on-error: true
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
"axios": "^1.6.8",
"chart.js": "^4.4.2",
"d3": "^7.9.0",
"dayjs": "^1.11.10",
"framer-motion": "^11.0.14",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-cookie": "^7.1.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.1",
"react-icons": "^5.0.1",
Expand Down
41 changes: 40 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions src/app/routes/PrivateRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Navigate } from 'react-router-dom'

import useUserDataStore from '@/features/hooks/store/useUserDataStore'
import GlobalLayout from '@/shared/layout/GlobalLayout'

const PrivateRoute = () => {
// 로그인 로직 구현 후 수정 예정
const isAuthorized = true
const { userData } = useUserDataStore()
const userRole = userData?.roles

return isAuthorized ? <GlobalLayout /> : <Navigate to="/signIn" />
return userRole?.includes('ROLE_ADMIN') ? (
<GlobalLayout />
) : (
<Navigate to="/signIn" />
)
}

export default PrivateRoute
13 changes: 13 additions & 0 deletions src/entities/interfaces/baseResponses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @ BaseResponse
* : 기본 API 응답
* - code: 응답 코드
* - msg: 응답 message
* - data: 응답 data
*/

export type BaseResponse<T = string> = {
code: number
msg: string
data?: T
}
42 changes: 42 additions & 0 deletions src/entities/interfaces/dto/auth/login.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { BaseResponse } from '../../baseResponses'
import { LoginFormType } from '../../form'
import { UserRole } from '../../user'

/**
* @ LoginRequest
* - LoginFormType: { email: string, password: string }
*/
export interface LoginRequest extends LoginFormType {}

/**
* @ LoginUserData
* - member_id: 사용자 id (number)
* - nickname: 사용자 닉네임 (string)
* - experience: 사용자 경험치 (number)
* - introduction: 사용자 소개글 (string)
* - image_url: 사용자 프로필 사진 URL (string)
* - level: 사용자 레벨 정보 (number)
* - roles: 사용자 역할 (사용자, 멘토, 관리자)
*/
export type LoginUserData = {
member_id: number
nickname: string
experience: number
introduction: string
image_url: string
level: number
roles: UserRole[]
}

/**
* @ LoginTokenData
* - accesstoken (string)
* - refreshtoken (string)
*/
export type LoginTokenData = {
token_dto: { access_token: string; refresh_token: string }
}

export type LoginPayload = LoginUserData & LoginTokenData

export interface LoginResponse extends BaseResponse<LoginPayload> {}
4 changes: 4 additions & 0 deletions src/entities/interfaces/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type LoginFormType = {
email: string
password: string
}
1 change: 1 addition & 0 deletions src/entities/interfaces/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type UserRole = 'ROLE_USER' | 'ROLE_MENTOR' | 'ROLE_ADMIN'
File renamed without changes.
Empty file removed src/features/hooks/.gitkeep
Empty file.
57 changes: 57 additions & 0 deletions src/features/hooks/auth/useAuthCookies.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import tokenKey from '@/shared/constants/tokenKey'

import { getCookie, removeCookie, setCookie } from '../../utils/cookies/cookies'

const useAuthCookies = () => {
const getAccessToken = () => getCookie(tokenKey.ACCESS_TOKEN)

const getAuthCookies = () => {
try {
const [accessToken, refreshToken] = [
getCookie(tokenKey.ACCESS_TOKEN),
getCookie(tokenKey.REFRESH_TOKEN),
]

return {
accessToken: accessToken?.value,
refreshToken: refreshToken?.value,
}
} catch (err) {
return {
accessToken: undefined,
refreshToken: undefined,
}
}
}

const setAuthCookies = (
accessToken: string,
refreshToken: string,
expires: string,
) => [
setCookie(tokenKey.ACCESS_TOKEN, accessToken, {
path: '/',
expires: new Date(expires),
// httpOnly: true,
}),
setCookie(tokenKey.REFRESH_TOKEN, refreshToken, {
path: '/',
maxAge: 60 * 60 * 24 * 20,
// httpOnly: true,
}),
]

const removeAuthCookies = () => {
removeCookie(tokenKey.ACCESS_TOKEN)
removeCookie(tokenKey.REFRESH_TOKEN, { maxAge: 60 * 60 * 24 * 20 })
}

return {
getAccessToken,
getAuthCookies,
setAuthCookies,
removeAuthCookies,
}
}

export default useAuthCookies
56 changes: 56 additions & 0 deletions src/features/hooks/auth/useCrypto.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const CRYPTO_KEY_NAME = import.meta.env.VITE_CRYPTO_KEY_NAME
const CRYPTO_KEY_LENGTH = import.meta.env.VITE_CRYPTO_KEY_LENGTH

const useCrypto = () => {
const encrypt = async (data: string) => {
const encoder = new TextEncoder()
const dataBuffer = encoder.encode(data)

const key = await crypto.subtle.generateKey(
{ name: CRYPTO_KEY_NAME, length: Number(CRYPTO_KEY_LENGTH) },
true,
['encrypt'],
)

const encryptedBuffer = await crypto.subtle.encrypt(
{ name: CRYPTO_KEY_NAME, iv: crypto.getRandomValues(new Uint8Array(12)) },
key,
dataBuffer,
)

const encryptedArray = Array.from(new Uint8Array(encryptedBuffer))

return btoa(String.fromCharCode.apply(null, encryptedArray))
}

const decrypt = async (data: string) => {
const encryptedData = data

// 암호화된 데이터가 없을 경우
if (!encryptedData) return

const encryptedBuffer = Uint8Array.from(atob(encryptedData), c =>
c.charCodeAt(0),
)

const key = await crypto.subtle.generateKey(
{ name: CRYPTO_KEY_NAME, length: Number(CRYPTO_KEY_LENGTH) },
true,
['decrypt'],
)
const decryptedBuffer = await crypto.subtle.decrypt(
{ name: CRYPTO_KEY_NAME, iv: crypto.getRandomValues(new Uint8Array(12)) },
key,
encryptedBuffer,
)

const decoder = new TextDecoder()
const decryptedData = decoder.decode(decryptedBuffer)

return JSON.parse(decryptedData)
}

return { encrypt, decrypt }
}

export default useCrypto
27 changes: 27 additions & 0 deletions src/features/hooks/store/useUserDataStore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'

import { LoginUserData } from '@/entities/interfaces/dto/auth/login.dto'

type UserDataStoreType = {
userData: LoginUserData | undefined
// eslint-disable-next-line no-unused-vars
setUserData: (data: LoginUserData) => void
clearUserData: () => void
}

const useUserDataStore = create<UserDataStoreType>()(
persist(
set => ({
userData: undefined,
setUserData: (data: LoginUserData) => set({ userData: data }),
clearUserData: () => set({ userData: undefined }),
}),
{
name: 'user-data-storage',
storage: createJSONStorage(() => sessionStorage),
},
),
)

export default useUserDataStore
Empty file removed src/features/utils/.gitkeep
Empty file.
Loading

0 comments on commit d12fd67

Please sign in to comment.