Commit 5e259cfd authored by Kim, MinGyu's avatar Kim, MinGyu
Browse files

context를 사용하여 login 구성

parent 81cba2c9
......@@ -5,12 +5,22 @@ import { Login, Signup } from "./auth";
import { Board } from "./board";
import { Header, Body } from "./home";
import Posting from "./post/posting";
import { AuthProvider } from "./auth/auth.context";
import Layout from "./commons/layout";
export const App = () => {
return (
<BrowserRouter>
<Routes>
<Route path="login" element={<Login />} />
{/* <Route element={<Layout children={} />}> */}
<Route
path="login"
element={
<AuthProvider>
<Login />
</AuthProvider>
}
/>
<Route path="signup" element={<Signup />} />
<Route path="/" element={<Header />}>
......@@ -18,6 +28,7 @@ export const App = () => {
<Route path="board" element={<Board />} />
<Route path="posting" element={<Posting />} />
</Route>
{/* </Route> */}
</Routes>
</BrowserRouter>
);
......
import React, { FC } from "react";
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "./auth.context";
export const RequireAuth: FC<{ children: JSX.Element }> = ({ children }) => {
const { user } = useAuth();
const location = useLocation();
if (!user.isLoggedIn) {
return (
<Navigate to={"/login"} state={{ from: location.pathname }} replace />
);
}
return children;
};
import React, {
createContext,
FC,
ReactNode,
useContext,
useState,
} from "react";
import { IUser } from "../types";
import { getLocalUser, handleLogin, handleLogout } from "./auth.helper";
interface IAuthContext {
login: (email: string, password: string, cb?: VoidFunction) => Promise<void>;
logout: (cb?: VoidFunction) => Promise<void>;
user: IUser;
}
const AuthContext = createContext<IAuthContext>({
login: async () => {},
logout: async () => {},
user: { isLoggedIn: false },
});
export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState(getLocalUser());
const login = async (
email: string,
password: string,
cb: VoidFunction = () => {}
) => {
const user = await handleLogin(email, password);
setUser(user);
cb();
};
const logout = async (cb: VoidFunction = () => {}) => {
await handleLogout();
setUser({ ...user, isLoggedIn: false });
cb();
};
return (
<AuthContext.Provider value={{ login, logout, user }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
import { authApi } from "../apis";
import { IUser } from "../types";
const LOCAL_USER_INFO = "survey-user-info";
/**
* 1. 백엔드 로그인을 호출하여 로그인 정보를 얻습니다.
* 2. 로컬 저장소에 저장합니다.
* 3. 사용자 정보를 반환합니다.
* @param email 이메일
* @param password 비밀번호
* @returns 사용자 정보
*/
export const handleLogin = async (email: string, password: string) => {
const user: IUser = await authApi.login(email, password);
// 로컬 저장소에는 로그인 여부만 저장
localStorage.setItem(
LOCAL_USER_INFO,
JSON.stringify({
isLoggedIn: user.isLoggedIn,
})
);
return user;
};
/**
* 로컬 저장소의 정보를 삭제합니다.
* 백엔드 로그아웃을 호출하여 쿠키를 제거합니다.
*/
export const handleLogout = async () => {
console.log("handle logout called");
localStorage.removeItem(LOCAL_USER_INFO);
try {
await authApi.logout();
} catch (error) {
console.log("logout 중에 에러 발생:", error);
}
};
/**
* 1. 로컬 저장소에 저장된 사용자 로그인 정보를 반환합니다.
* 2. 로컬 저장소에 정보가 없으면 { isLoggedIn: false }를 반환합니다.
* @returns 로컬 저장소에 저장된 사용자 정보
*/
export const getLocalUser = () => {
console.log("get local user called");
const userInfo = localStorage.getItem(LOCAL_USER_INFO);
const user: IUser = { isLoggedIn: false };
if (!userInfo) {
return user;
}
const userData = JSON.parse(userInfo);
if (userData.isLoggedIn) {
user.isLoggedIn = true;
} else {
user.isLoggedIn = false;
}
return user;
};
import { Link } from "react-router-dom";
import React, { useState, FormEventHandler } from "react";
import { Link, useNavigate } from "react-router-dom";
import React, { useState, useEffect, FormEvent } from "react";
import { LoginUser } from "../types";
import { catchErrors } from "../helpers";
import { useAuth } from "./auth.context";
interface login {
id: string;
password: string;
}
export default function Login() {
const [user, setUser] = useState<LoginUser>({
email: "",
password: "",
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [disabled, setDisabled] = useState(false);
const [success, setSuccess] = useState(false);
const navigate = useNavigate();
const { login } = useAuth();
// const fake = { id: "asdf", password: "qwer" };
useEffect(() => {
setDisabled(!(user.email && user.password));
}, [user]);
function Logindata() {
const [id, setId] = useState("");
const [password, setPassword] = useState("");
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const { name, value } = event.currentTarget;
setUser({ ...user, [name]: value });
}
function login() {
fetch(`http://localhost:3000/api/auth/login`, {
method: "POST",
async function handleSubmit(event: FormEvent) {
event.preventDefault();
try {
setError("");
console.log("user data", user);
body: JSON.stringify({
email: `${id}`,
password: `${password}`,
}),
}).then((response) => {
console.log(response.json());
});
// setLoading(true);
await login(user.email, user.password, () => {
navigate("/", { replace: true });
});
// console.log("서버연결됬나요", res);
// console.log("로그인");
// setSuccess(true);
// setError("");
} catch (error) {
console.log("에러발생");
// setError("이메일 혹은 비밀번호를 다시 입력해주세요.");
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
return (
<div className="flex flex-col md:w-2/3 md:gap-2">
<input
className="placeholder:text-slate-300
bg-white border border-slate-500 rounded-2xl
py-2 pl-9 pr-3
<div className="flex flex-col items-center my-10">
<div className="bg-white w-1/2 md:w-1/3 my-8 text-center text-2xl">
<Link to="/">Travel Report</Link>
</div>
<div className="flex flex-col w-full md:w-1/2 p-8 md:p-4 md:p-0">
<form onSubmit={handleSubmit}>
<div className="flex flex-col md:flex-row border-2 border-black rounded-xl p-8 md:p-12 gap-y-4 md:gap-x-6">
<div className="flex flex-col md:w-2/3 md:gap-2 ">
<input
className="placeholder:text-slate-300
bg-white border border-slate-500 md:rounded-2xl
py-3 md:py-2 pl-9 pr-3
focus:border-black
"
placeholder="Id"
type="text"
name="Id"
onChange={(e) => setId(e.target.value)}
/>
<input
className="placeholder:italic placeholder:text-slate-300
bg-white border border-slate-500 rounded-2xl
py-2 pl-9 pr-3
placeholder="이메일"
type="email"
name="email"
onChange={handleChange}
/>
<input
className="placeholder:italic placeholder:text-slate-300
bg-white border border-slate-500 md:rounded-2xl
py-3 md:py-2 pl-9 pr-3
focus:border-black
"
placeholder="Password"
type="password"
name="Password"
onChange={(e) => setPassword(e.target.value)}
/>
<button
type="submit"
className="md:w-1/3 bg-sky-600 hover:bg-sky-700 rounded-xl"
onClick={login}
>
<Link to={"/"}>login</Link>
</button>
</div>
);
}
export default function Login() {
return (
<div>
{/* <form onSubmit={loginsubmit}> */}
<div className="flex flex-row grid-rows-2">
<div className=" p-12 w-1/2 h-1/2 md:w-40 md:h-40 bg-red-400 place-self-center rounded-2xl">
<Link to="/">Travel Report</Link>
</div>
<div className=" flex-row w-auto h-60 md:w-1/2 bg-white border-2 border-black grid place-items-center rounded-xl place-self-center">
<div className="flex flex-col w-full md:flex-row md:p-20 md:gap-10">
<Logindata />
</div>
<div className="flex-row grid grid-cols-3">
<button className="bg-white bottom-0 right-0">
<Link to="/signup">회원가입</Link>
placeholder="Password"
type="password"
name="password"
onChange={handleChange}
/>
</div>
<button
disabled={disabled}
type="submit"
className="my-4 md:my-0 md:w-1/3 bg-sky-600 hover:bg-sky-700 rounded-xl text-xl py-4"
>
login
</button>
</div>
</form>
<div className="flex justify-around m-4">
<button className="bg-white ">
<Link to="/signup">회원가입</Link>
</button>
<div className="grid grid-cols-2 divide-x-2">
<div></div>
<div></div>
<button className="bg-white inset-x-0">
<Link to="/forgot">비밀번호 찾기</Link>
</button>
</div>
<button className="bg-white">
<Link to="/forgot">비밀번호 찾기</Link>
</button>
</div>
</div>
{/* </form> */}
</div> // Login Page
</div>
);
}
import React, { FormEvent, useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Link, Navigate } from "react-router-dom";
import { authApi } from "../apis";
import { catchErrors } from "../helpers";
import { SpinnerIcon } from "../icons";
......@@ -17,7 +17,6 @@ export default function Signup() {
const [error, setError] = useState("");
const [disabled, setDisabled] = useState(false);
const [success, setSuccess] = useState(false);
const navigate = useNavigate();
useEffect(() => {
setDisabled(!(user.name && user.email && user.password && user.password2));
......@@ -65,7 +64,7 @@ export default function Signup() {
if (success) {
alert("회원가입 되었습니다");
navigate("/login", { replace: true });
return <Navigate to={"/login"} replace />;
}
return (
......
import React, { ReactNode } from "react";
import { AuthProvider } from "../auth/auth.context";
export default function Layout({ children }: { children: ReactNode }) {
return <AuthProvider>{children}</AuthProvider>;
}
import React from "react";
import {Link, Outlet} from "react-router-dom";
import React, { useState } from "react";
import { Link, Outlet } from "react-router-dom";
import { useAuth } from "../auth/auth.context";
import "tailwindcss/tailwind.css";
export default function Header() {
return (
<div className="flex flex-col ">
<div className="flex flex-row px-5 py-20 md:place-content-between">
<button className="px-5 py-2">
<Link to="/" className="hover:bg-gray-200 focus:text-purple-500">Travel Report</Link>
</button>
<div className="flex flex-row-reverse">
<button className="px-5 py-2 bg-teal-400 rounded">
<Link to="/login" className="hover:bg-teal-100 focus:text-purple-500 ">Login</Link>
</button>
<button className="px-5 py-2 bg-purple-400 rounded">
<Link to="/board" className="hover:bg-purple-300 focus:text-purple-500 ">Board</Link>
const { logout } = useAuth();
return (
<div className="flex flex-col ">
<div className="flex flex-row px-5 py-20 md:place-content-between">
<button className="px-5 py-2">
<Link to="/" className="hover:bg-gray-200 focus:text-purple-500">
Travel Report
</Link>
</button>
<div className="flex flex-row-reverse">
<button className="px-5 py-2 bg-teal-400 rounded">
<Link
to="/login"
className="hover:bg-teal-100 focus:text-purple-500 "
>
Login
</Link>
<button
onClick={() => {
logout();
}}
>
Logout
</button>
<div>
<label>
{/* <span className="sr-only">Search</span> */}
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
<svg className="h-5 w-5 fill-slate-300" viewBox="0 0 20 20"></svg>
</span>
<input className="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-2 pl-9 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm" placeholder="Search for anything..." type="text" name="search" />
</label>
</div>
</button>
<button className="px-5 py-2 bg-purple-400 rounded">
<Link
to="/board"
className="hover:bg-purple-300 focus:text-purple-500 "
>
Board
</Link>
</button>
<div>
<label>
{/* <span className="sr-only">Search</span> */}
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
<svg
className="h-5 w-5 fill-slate-300"
viewBox="0 0 20 20"
></svg>
</span>
<input
className="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-2 pl-9 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm"
placeholder="Search for anything..."
type="text"
name="search"
/>
</label>
</div>
</div>
<Outlet/>
</div>
);
}
\ No newline at end of file
<Outlet />
</div>
);
}
......@@ -70,7 +70,7 @@ function SelectTheme() {
<option value="history">문화재</option>
<option value="zoo">동물원</option>
<option value="cycling">사이클링</option>
<option value="cycling">{selectTheme}</option>
{/* <option value="cycling">{selectTheme}</option> */}
</select>
);
}
......
......@@ -13,3 +13,14 @@ export interface SignupUser {
name: string;
password: string;
}
export interface LoginUser {
email: string;
password: string;
}
export interface IUser {
email?: string;
isLoggedIn: boolean;
_id?: string;
}
......@@ -25,15 +25,18 @@ export const login = asyncWrap(async (req, res) => {
return res.status(401).send("잘못된 비밀번호를 입력하셨습니다");
}
// 3) 비밀번호가 맞으면 토큰 생성
const token = jwt.sign({ userId: user.id }, jwtCofig.secret, { //userId를 토큰에다 넣는 중.
const token = jwt.sign({ userId: user.id }, jwtCofig.secret, {
//userId를 토큰에다 넣는 중.
expiresIn: jwtCofig.expires,
});
// 4) 토큰을 쿠키에 저장
res.cookie(cookieConfig.name, token, {//token은 쿠키에 무엇을 실렸는가 이다. 항상 갖고 있다가 홈페이지 들어가면 서버로 접속
maxAge: cookieConfig.maxAge,// 이 기간 한에서만 유효
path: "/",//어떠한 경로에 관해서만 쓴다. 지금은 전부에 쓴다.
httpOnly: envConfig.mode === "production", //false 면 브라우저에서 쿠키를 조작, true면 조작할 수 있다.
secure: envConfig.mode === "production", //true 면 https 를 통해서만 쿠키 전달, false면
res.cookie(cookieConfig.name, token, {
//token은 쿠키에 무엇을 실렸는가 이다. 항상 갖고 있다가 홈페이지 들어가면 서버로 접속
maxAge: cookieConfig.maxAge, // 이 기간 내에서만 유효
path: "/", //어떠한 경로에 관해서만 쓴다. 지금은 전부에 쓴다.
httpOnly: envConfig.mode === "production", //false면 브라우저에서 쿠키를 조작, true면 조작할 수 없다.
secure: envConfig.mode === "production", //true 면 https를 통해서만 쿠키 전달, false면
});
// 5) 사용자 반환
res.json({
......@@ -59,7 +62,7 @@ export const requireLogin = asyncWrap(async (reqExp, res, next) => {
const decodedUser = jwt.verify(token, jwtCofig.secret); // 아까보낸 토근을 디코딩중.
// 3) 요청 객체에 토큰 사용자 객체 추가
req.auth = decodedUser;
next();// 에러가 안나오면 next 사용, 나오면 catch쪽으로.
next(); // 에러가 안나오면 next 사용, 나오면 catch쪽으로.
} catch (error) {
res.clearCookie(cookieConfig.name);
console.log("error in requreLogin===\n", error);
......@@ -84,13 +87,13 @@ export const signup = asyncWrap(async (req, res) => {
if (userExist) {
return res.status(422).send(`${email} 사용자가 이미 존재합니다`);
}
// 3) 비밀번호 암호화
// 3) 비밀번호 암호화는 useDb.createUser에서 처리
const hash = await bcrypt.hash(password, 10);
// 4) 새로운 사용자 만들기
const newUser = await userDb.createUser({
email,
password: hash,
password,
});
// 5) 사용자 반환
// 5) 사용자 반환(내부적으로 몽구스가 toJSON() 호출)
res.json(newUser);
});
......@@ -10,5 +10,5 @@ export const createUser = asyncWrap(async (req, res) => {
const user = req.body;
console.log("user body", user);
const newUser = await userDb.createUser(user);
return res.json(user);
return res.json(newUser);
});
import bcrypt from "bcryptjs";
import { IUser, User } from "../models";
export const createUser = async (user: IUser) => {
const newUser = await User.create(user);
// 비밀번호 암호화
const hash = await bcrypt.hash(user.password, 10);
const newUser = await User.create({ email: user.email, password: hash });
return newUser;
};
......
import { model, Schema, Types } from "mongoose";
import { model, Schema, Types, version } from "mongoose";
export interface IUser {
email: string;
......@@ -12,16 +12,27 @@ const validateEmail = (email: string) => {
return re.test(email);
};
const schema = new Schema<IUser>({
email: {
type: String,//mongoose type 인 String 으로 일반적인 string 과는 겉으로는 대문자 차이
rquired: true,
unique: true,
validate: [validateEmail, "이메일을 입력해주세요"],
const schema = new Schema<IUser>(
{
email: {
type: String, //mongoose type인 String으로 일반적인 string과는 겉으로는 대문자 차이
rquired: true,
unique: true,
validate: [validateEmail, "이메일을 입력해주세요"],
},
name: { type: String },
password: { type: String, required: true, select: false },
role: { type: Schema.Types.ObjectId, ref: "Role" },
},
name: { type: String },
password: { type: String, required: true, select: false },
role: { type: Schema.Types.ObjectId, ref: "Role" },
});
{
toJSON: {
versionKey: false,
transform(doc, ret, options) {
delete ret.password;
},
},
}
);
export default model<IUser>("User", schema);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment