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