Github Login #05

0_CyberLover_0·2022년 4월 22일
0

Node.JS #05

목록 보기
5/19

저번 파트에서 뭘했는지 살펴 보기로 한다.

Github가 준 코드를 가지고 access_token으로 교환을 했다.

export const finishGithubLogin = async (req, res) => {
  const baseUrl = "https://github.com/login/oauth/access_token";
  const config = {
    client_id: process.env.GH_CLIENT,
    client_secret: process.env.GH_SECRET,
    code: req.query.code,
  };
export const finishGithubLogin = async (req, res) => {
  const baseUrl = "https://github.com/login/oauth/access_token";
  const config = {
    client_id: process.env.GH_CLIENT,
    client_secret: process.env.GH_SECRET,
    code: req.query.code,
  };
  const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  const tokenRequest = await (
    await fetch(finalUrl, {
      method: "POST",
      headers: {
        Accept: "application/json",
      },
    })
  ).json();
  if ("access_token" in tokenRequest) {
    const { access_token } = tokenRequest;
    const userRequest = await (
      await fetch("https://api.github.com/user", {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userRequest);
  } else {
    return res.redirect("/login");
  }

access_tokenGithub API URLfetch하는데 사용 되었다.

그랬더니 얻은 건 userpublic정보였다. 이건 scope로 요청했기 때문에 가능했다.

만일 read:user를 요청하지 않았다면 받은 코드로는 user의 정보를 읽을 수 있는

access_token을 받을 수 없을거다.

어찌 되었든 Github API에 대해 얘기하고 있고 Github API로 가면 무얼 할수 있는지 전부 볼수 있다.

https://docs.github.com/en/rest/users/users#get-the-authenticated-user

예를 들면 현재 한것 처럼 user를 불러올수 있다.

https://api.github.com/user

이 부분이 user를 가져오고 있는거다. 다른 많은 것들로 할수 있다.

user를 차단 시킬수도 있고 user를 팔로우 할수도 있다.

이것들을 한번 해보는 것도 좋을 것 같다. PUT request/user/following/{username}으로 보낸다 하면

지금은 이걸 허용하는 access_token이 없으니 작동하진 않을거다.

access_token이 모든걸 할수 있도록 허용하는건 아니기 때문이다.

scope에 적은 내용에 대해서만 허용해줄 뿐이다.

다시 말해 access_tokenuser가 모든걸 할수 있게 해주진 않는다.

scope에 명시하면 Github가 코드를 줄거다.

그 코드에는 이미 하고자 하는 바가 명시 되어 있다.

그리고 그 코드를 access_token으로 바꾸게 되고 access_token은 한다고 한 것 하게 된다.

현재 경우에서 access_token을 가지고 하려는건 첫번째 scope이다.

export const startGithubLogin = (req, res) => {
  const baseUrl = "https://github.com/login/oauth/authorize";
  const config = {
    client_id: process.env.GH_CLIENT,
    allow_signup: false,
    scope: "read:user user:email",
  };

user의 정보를 읽어 들이고 있다. 하지만 아직 두번째 요구인 email을 읽어 들이고 있지 않다.

이건 access_token을 가지고 더 많은 걸 할수 있다는걸 보여주기 위해 일부러 이렇게 만들어 본거다.

여기서 user의 데이터를 가져오고 있으니까

  if ("access_token" in tokenRequest) {
    const { access_token } = tokenRequest;
    const userRequest = await (
      await fetch("https://api.github.com/user", {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })

userRequestuserData로 바꿔 준다.

const userData = await (
      await fetch("https://api.github.com/user", {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userData);

어떤 user들은 public(공개된) email을 가지고 있을 수도 있다.

하지만 이 경우엔 고의로 이렇게 만들었다. public email이 아닌걸로 말이다.

그렇기 때문에 useremail을 요청하기 위해 똑같은 access_token을 써야 한다.

Github API를 잠깐 살펴보면 email만 다른 부분을 볼수 있다.

https://docs.github.com/en/rest/users/emails

primary email설정하기는 현재 상태랑 관계없다. 모든 email주소를 부여주기가 현재 필요한 거다.

그러면 모든 email주소들을 가져오도록 한다.

if ("access_token" in tokenRequest) {
    const { access_token } = tokenRequest;
    const apiUrl = "https://api.github.com";
    const userData = await (
      await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userData);
    const emailData = await (
      await fetch(`${apiUrl}/user/emails`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(emailData);
  } else {
    return res.redirect("/login");
  }
};

fetch에서 뒷부분은 편리하게 사용 할수 있게 만들어 준다.

이제 user데이터를 볼러 올수 있다는걸 안다. 하지만 이제 email데이터도 원한다.

await부분도 반복되기에 그대로 복사해서 넣어주고 url일부분 수정해 준다.

그리고 console.log(emailData)를 해준다.

이제 두개의 request를 하고 있다. 그리고 당연히 json도 넣어 준다.

이렇게 하면 짧게 쓸 수 있다. fetch를 하고 await json()을 쓰는 거다.

이제 email데이터를 사용할 준비가 된거다. 그럼 콘솔로 돌아가서 다시 한번 시도한다.

이제 두개의 데이터를 받았다. 하나는 이미 알고 있는 공개된 프로필이고

다른 하나는 email들이다. 이제 emailverified이면서 primary인 것들을 찾아야한다.

그러니 email들 중 verified(확인)이면서 primary(일순위)인 걸 찾아 볼거다.

왜냐하면 가끔 Github으로 계정 생성을 해도 primary혹은 verified가 안 되었을수도 있다.

useremail을 얻고 있다. 다시 말하자면 /user/emails

/user로 보내는 이 request들은 access_token이 볼수 있게끔 허락해줬기 때문에 작동하는거다.

모든건 scope부분에서 출발한다. 현재 이 경우가 제일 익숙하지 않다.

await (
    await fetch(finalUrl, {
      method: "POST",
      headers: {
        Accept: "application/json",
      },
    })
  ).json();

ES6에 익숙지 않아서 그렇다. 그런데 이 두 부분은 지금 써보려는 방식과 비슷하다.

fetch(x).then(response => response.json()).then(json => )
await (
    await fetch(finalUrl, {
      method: "POST",
      headers: {
        Accept: "application/json",
      },
    })
  ).json();

하지만 이렇게 작성하는건 좋지 못하다. 왜냐하면 이 말인 즉슨 .then안으로 들어가야 하기 때문이다.

if ("access_token" in tokenRequest) {
    const { access_token } = tokenRequest;

예를 들어 이 부분에서 access_token을 얻으면 그렇다는건 여기에서 access_token을 얻는다는 거다.

fetch(x).then(response => response.json()).then(json => access_token)
await (
    await fetch(finalUrl, {
      method: "POST",
      headers: {
        Accept: "application/json",
      },
    })
  ).json();

그럼 이줄에서 fetch를 한번더 해야 한다는 소리이다.

fetch(x).then(response => response.json()).then(json => access_token fetch ())

왜냐하면 이것도 가져와야 하기 때문이다.

await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })

결국 fetch를 써야 하는데 만일 여기서 했던 것처럼 똑같이 하고 싶다면

이렇게 해야 될거다.

const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;

  fetch(finalUrl, {
    method: "POST",
    headers: {
      Accept: "application/json",
    },
  }).then(response => response.json()).then(json => {
    if ("access_token" in json) {
      const { access_token } = tokenRequest;
      const apiUrl = "https://api.github.com"; 
        await fetch(`${apiUrl}/user`, {
          headers: {
            Authorization: `token ${access_token}`,
          },
        }).then(response => response.json()).then(json => {
          fetch(`${apiUrl}/user/emails`, {
            headers: {
              Authorization: `token ${access_token}`,
            },
          })
        });
      }
  });
const tokenRequest = await (
    await fetch(finalUrl, {
      method: "POST",
      headers: {
        Accept: "application/json",
      },
    })
  ).json();
  if ("access_token" in tokenRequest) {
    const { access_token } = tokenRequest;
    const apiUrl = "https://api.github.com";
    const userData = await (
      await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(userData);
    const emailData = await (
      await fetch(`${apiUrl}/user/emails`, {
        headers: {
          Authorization: `token ${access_token}`,
        },
      })
    ).json();
    console.log(emailData);
  } else {
    return res.redirect("/login");
  }
};

이것들을 전부 fetch하고 then을 쓰는 거다. 맨위 부터 시작해서 then안에
then, 또 then안에 then 그리고 또 fetch를 한다.

이거 완전 이상한 짓이다. 이걸 user email이랑 user 프로필에도 해줘야 하는데

그러다간 머리가 터질지도 모른다.

그래서 then을 쓰는 대신에 그냥 원래대로 하는거다.너무나 당연히 이 방법이 훯씬 좋다.

어쨋든 user데이터를 불러오고 있고, email도 불러오고 있다.

이제 user데이터랑 email데이터를 가지고 있으니까 유저의 Github ID도 알고 있다는거고,

useremail들도 알고 있다는 거다.

이제 primaryverified가 모두 trueemail을 찾아야 한다.

그래서 이 부분을 콘솔로 복붙해서 사용해 본다.

[
  {
    email: 'pkpanda@naver.com',
    primary: true,
    verified: true,
    visibility: 'private'
  },
  {
    email: '91738673+JooMercury@users.noreply.github.com',
    primary: false,
    verified: true,
    visibility: null
  }
]
const emails = [
  {
    email: 'pkpanda@naver.com',
    primary: true,
    verified: true,
    visibility: 'private'
  },
  {
    email: '91738673+JooMercury@users.noreply.github.com',
    primary: false,
    verified: true,
    visibility: null
  }
]

2개의 email을 가진 배열을 만들었다. 그런 다음에 이렇게 입력해 본다.

emails.find(email => email.primary === true && email.verified === true)
{email: 'pkpanda@naver.com', primary: true, verified: true, visibility: 'private'}

emails.find()을 써서 primaryverifiedtrueemail을 찾는다.

그러면 이런 식으로 결과가 나온다. 이런 코드를 쓰고 싶었던 거다.

이걸 브라우저에서 하는게 좋을거다. 콘솔이 훨씬 인터렉티브 하니까 말이다.

이렇게 primaryverified가 모두trueemail을 찾아 봤다.

현재 찾고 싶은걸 array에서 찾는 방법이였다.

const email = emailData.find(
      (email) => email.primary === true && email.verified === true
    );
    if (!email) {
      return res.redirect("/login");
    }
  } else {
    return res.redirect("/login");
  }
};

아까 쓴 find()를 쓰면 된다. 그리고 만일 email이 없다면 user한테 verifiedemail이 없을 수도 있기때문이다.

다시 login화면으로 redirect 한다.

나중에는 에러 notificaton을 보여주면서 redirect 시켜 본다.

만일 코드가 도착하게 되면 primary이면서 verifiedemail이 있다는 뜻이다.

user데이터 또한 받게 된다. 그래서 user를 로그인 시킬수도 있고 아니면 계정을 생성 시킬수도 있다.

왜냐하면 email이 없다는 뜻일 테니깐 말이다.

그리고 동일한 user email은 갖고 있지만 한명은 일반 password로 로그인 하고

다른 하나는 Github로 로그인 하는 user를 어떻게 다룰지 알아 볼거다.

emailpassword로 계정을 생성한 userGithub로 로그인하려고 하면 어떻게 할 것인지 생각해 보는거다.

그리고 똑같은 email이 있다면 어떻게 할지도 말이다.

두개의 계정을 만들게 할 것인가? 아니면 그 계정들을 하나로 통합 할것인가?

user에게 이미 해당 email로 만든 계정이 있다고 에러를 보낼 것인가?

다음 파트에서 다뤄 보도록 한다.

현재는 만족스럽다. 왜냐하면 email이 있으니 어떤 user인지 알수 있다.

다음 파트에선 좀더 생각해봐야 할 필요가 있다.어떻게 중복된 email을 처리할건지 말이다.

profile
꿈꾸는 개발자

0개의 댓글