이제 스스로에게 물어본다(?)
"로그인 규칙을 어떻게 만들것인가?" 예를 들어, 데이터베이스에
user가 하나 있는데 이 user는 이런 email을 가지고 있다.
{ "_id" : "yuoIkA5t7NB_axs1qVST5xflGbrR45jh", "expires" : ISODate("2022-05-06T07:39:42.013Z"), "session" : "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"},\"loggedIn\":true,\"user\":{\"_id\":\"625942ace3564e09811a5f21\",\"email\":\"pkpanda@naver.com\",\"username\":\"Cyber Lover\",\"password\":\"$2b$05$WMO/VH/yctvvPJST0SyLq.QRQfSNeLJ5zAJPFfRMwLgg5ZFq1KtBm\",\"name\":\"Mercury\",\"location\":\"NYC\",\"__v\":0}}" }
이미 해당 email로 계정도 있고 password도 있다.
즉 이 user는 username과 passoword로 login을 할수 있다.
그런데 만일 Github으로 로그인 버튼을 누르게 된다면 토큰 작업등 전부 거친 뒤
Github으로 로그인 한user는 데이터베이스상에 똑같은 email과 password를 가진 user를 받는다.
그러니까 웹사이트로 와서 email과 password로 계정을 생성하고 한달 후에 돌아와서
Github으로 로그인 하려한다.
보다시피 Github은 email을 주고 있다. 그런데 이 email이 똑같다.
그러면 어떻게 하는게 좋을까? 두가지 옵션이 있다. 하나는 user에게 "그건 안된다.
이미 password가 있으니 그걸로 로그인하라" 라고 말 할수 있고
또는 "똑같은 email이 있다는걸 증명했으니 Github로 로그인해도 된다" 라고 할수 있다.
만일 user가 다른 무언가로 로그인을 했다면 예를 들어 카카오톡으로 로그인 했다하면
카카오톡이 이런식으로 email을 줄거다. 이렇게 user를 로그인 시킬수도 있다.
이런 방식을 채택한 여러 사이트들이 있다. 그치만 몇몇 사이트들은 password로 계정을 만들게 하는 곳도 있다.
"password로 로그인 하세요"라고 하면서 말이다.
혹은 email과 password로만 로그인하려 하는데 사실 password없이 Github 로그인으로
계정을 만들었다면 "user는 있지만 password는 없다. 그러니까 Github로 로그인해라" 라고 할거다.
보다시피 옵션은 정말 많다. 그래서 Github로그인 같은 소셜 로그인을 할때
만일 email에 접근 권한이 있다는게 증명이 된다면 즉, password가 있거나
Github의 email이 verified된거라면 email의 주인이라는 뜻이니까
로그인 시켜 줄수 있다. 꼭 이렇게 할 필요는 없다. 더 많은 조건들을 만들어도 된다.
예를 들어 user가 password를 갖고 있다는걸 알게 된다면 로그인을 시켜선 안되는 거다.
이 같은 경우엔 여기에 primary랑 verified가 있기 때문에 그렇게 하지 않는다.
그래서 primary이면서 verified된 email을 찾는거다. 두개다 중요하기 때문이다.
그래서 user가 두개의 조건을 만족한다면 "email이 있으니 로그인 시켜준다"라고 할거다.
이 부분 또한 바뀐다. 예를 들어 Github login으로 계정을 만든 user있을때
즉 email은 있지만 password가 없는 경우를 말하는 거다.
이럴때는 로그인 화면에서 user에게 이렇게 말해야 한다.
"email은 있는데 password가 없다"이건 그들이 Github으로 로그인해야 한다는 뜻이다.
이렇게 고려해야 할 사항들이 굉장히 많다.
이제 무엇을 하냐면 만약 primary인 email을 받고 데이터베이스에서 같은 email을 가진 user를 발견하면
user들을 로그인 시켜 준다. email은 사실 string이 아닌 객체 이다.
그래서 find가 객체를 주고 있는 거다. 그런데 email그 자체가 필요하다.
이 부분을 emailObj라 바꿔 본다.
const emailObj = emailData.find(
(email) => email.primary === true && email.verified === true
);
if (!emailObj) {
return res.redirect("/login");
}
const existingUser = await User.findOne({ email: emailObj.email });
if (existingUser) {
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
}
} else {
return res.redirect("/login");
}
};
그리고 기존 user를 찾는거다. 그리고 emailObj.email을 가지고 user를 찾는다.
만일 해당 email을 가지는 user가 이미 있다면 그 유저가 전에 Github로 로그인했든
password로 계정을 생성했든 신경 쓰지 않는다.
핵심은 해당 email을 가진 user가 이미 있는지 찾는 것이고 이런 유저를 로그인 시켜 줄거다.
로그인은 어떻게 시키냐면 위에서 있는 코드가 있으니 그것을 그대로 활용한다.
req.session.loggedIn = true;
req.session.user = user;
return res.redirect()을 써서 "/"/으로 돌아가게 한다.
하지만 이번에는 로그인이 된 상태가 된다.
다시 한번 확인하면 깃헙이 주는 list에서 primary이면서 verified된 email객체를 찾아야한다.
그리고 같은 email을 가진 user가 이미 있다면 그 유저를 로그인시켜 줄거다.
나중에 이 부분에서는 계정을 생성하는걸 추가해줘야 한다.
const existingUser = await User.findOne({ email: emailObj.email });
if (existingUser) {
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
} else {
}// create an account
} else {
return res.redirect("/login");
}
};
무슨 말이냐면 해당 email로 user가 없으니까 계정을 생성해야 한다는 거다.
다행히도 이미 아주 만은 데이터를 가지고 있다.
email도 있고 location도 있고 name도 있고 Github ID도 있고
username도 있다. 필요한 모든 것이 갖춰져 있다.
여태까지의 변경 사항들을 저장한 다음 테스트 해본다.
실행시켜보면 이 코드가 실행될거다.
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
왜냐하면 해당 email을 가지고 있는 user가 이미 데이터 베이스에 있고
Github의 API에서 해당 email을 주기 때문이다.
테스트 해본다. 새로고침하고 나면 에러가 뜬다.
ReferenceError: user is not defined
user가 정의되지 않았다고 뜬다.
const existingUser = await User.findOne({ email: emailObj.email });
if (existingUser) {
req.session.loggedIn = true;
req.session.user = existingUser;
user를 existingUser으로 변경해준다. 그러고 나서 다시 테스트를 해본다.
이제 로그인이 잘 된다.
이제 이 user를 지우고 "만일 계정이 없다면 어떻게 할 것인가"에 대해 해보도록 한다.
그래서 하나를 만들어야 한다. 그리고 일종의 password 같은걸 만들어야 한다.
mongodb에서 db.sessions.remove({})를 해주고 나면
이 코드는 실행 되지 않을거다.
const existingUser = await User.findOne({ email: emailObj.email });
if (existingUser) {
req.session.loggedIn = true;
req.session.user = existingUser;
return res.redirect("/");
} else {
// create an account
// create an account 뭐가 실행되냐면 이게 실행될거다.
이제 user를 생성해야 한다.
await User.create({
name,
username,
email,
password,
location,
이부분을 이용해서 사용한다. 똑같이 해보도록 한다. await User.create()로 user를 만들어 주고
const existingUser = await User.findOne({ email: emailObj.email });
if (existingUser) {
req.session.loggedIn = true;
req.session.user = existingUser;
return res.redirect("/");
} else {
const user = await User.create({
name: userData.name,
username: userData.login,
email: emailObj.email,
password: "",
location: userData.location,
});
그리고 나서 이제 값을 채우면 된다. name은 userData.name이다.
이 데이터는 객체이다. 그래서 userData.name이라고 해준다.
다음은 username이건 userData.login이 될거다.
email은 userData.email이 아닌 emailObj.email이다.
password는 없으니 " "으로 해준다.
location은 이미 있으니 userData.location으로 해준다.
그리고 User.js에서 하나 추가해준다.
const userSchema = new mongoose.Schema({
githubId: { type: Number },
바로 githubId이다. type은 Number이고 required:flase, unique:true로 해준다.
그냥 number만 써준다. 이렇게 하는 이유는 user가 Github로 로그인했는지 여부를 알기 위해서이다.
이건 로그인 페이지에서 유저가 email로 로그인하려는데 password가 없을때 유용할수 있다.
githubId를 체크하면 된다. 사실 githubId가 아니라 githubLoginOnly라 해준다.
아니면 noPasswordAccount 아니면 socialOnly type은 Boolean으로 해준다.
default는 false로 해준다.
socialOnly: { type: Boolean, default: false },
이렇게 계정을 만들어주면 이 계정은 Github을 이용해 계정을 만들었다면 password는 없게 된다.
그렇다면 username과 password form을 사용 할수 없다.
그래서 계정이 socialOnly=true라는걸 알려줘야 한다.
const user = await User.create({
name: userData.name,
username: userData.login,
email: emailObj.email,
password: "",
socialOnly: true,
location: userData.location,
});
이렇게 user를 생성하도록 만들었다. 여기서 User.create()는 새로 만든 user를 return시켜준다.
이 user를 로그인 시켜줘야 한다.
const user = await User.create({
name: userData.name,
username: userData.login,
email: emailObj.email,
password: "",
socialOnly: true,
location: userData.location,
});
req.session.loggedIn = true;
req.session.user = user;
return res.redirect("/");
그래서 밑에 이렇게 넣어준다. 이 부분을 다듬어 보는건 다음에 해보도록 한다.
그리고 이건 다른 옵션이다. 데이터베이스에 해당 email을 가진 user가 없을때
이렇게 password없이 Github의 데이터로 user를 생성하고
그런 user에게는 socialOnly:true값을 주고 있다.
그리고 여기 postLogin때 유용 할거다.
export const postLogin = async (req, res) => {
왜냐하면 user를 찾을때 몇몇 password들을 비교해볼텐데
그러기 위해 if else를 써야 될거 같다.
user가 Github로 계정을 만들고 password로 로그인을 시도한다면
user에게 "password가 없으니 Github로 로그인"하라고 말해줘야 한다.
아니면 pasword를 새로 생성하는 페이지를 만들든지 해야 한다.
이제 테스트 해보도록 한다. 데이터베이스엔 user가 없고
이 계정은 Github데이터로만 만들어졌을거다.
const user = await User.create({
name: userData.name,
username: userData.login,
email: emailObj.email,
password: "",
socialOnly: true,
location: userData.location,
});
그리고 Github데이터는 avatarUrl이라 하는 것이 있는데 그건 나중에 써보도록 한다.
테스트를 해본다. login -> Continue with Github하면 계정이 생성되고
(해당 강의에서는 에러가 발생하는데 그냥 로그인이 잘된다.)
에러는 User 인증에 실패했습니다. User validation failed라고 뜬다.
password가 필수 조건이라고 한다.
password: { type: String, required: false },
password required값을 false로 바꿔준다. 유일한 옵션이다.
(그냥 없애도 잘 작동 한다.)
이건 몇몇 user들에게는 password가 없을테니 그런거다.
다시 실행해 보면 잘된다. (수정을 안 한 상태에서도 잘 작동하였지만 혹시 모를 에러때문에 이렇게 수정을 하고 진행하도록 한다. )
Github으로 부터 온 프로필 이름이 나온다.
mongodb로 가서 user를 보면 socialOnly:true인 계정이 생성 되었다.
password는 없다. 왜냐하면 비어있는 해시값이기 때문이다.
username은 Github username일 테고 email은 Github email이다.
location이런 것들 전부 Github에서 오고 있다.