[TIL] 211221

Lee SyongΒ·2021λ…„ 12μ›” 21일
1

TIL

λͺ©λ‘ 보기
125/204
post-thumbnail

πŸ“ 였늘 ν•œ 것

  1. multer S3 - 폴더 λ§Œλ“€κΈ° / process.env.NODE_ENV

  2. github access_token κ΄€λ ¨ CROS μ—λŸ¬ (λ―Έν•΄κ²°)


πŸ“š 배운 것

1. 배포

1) multer S3

(1) 버킷에 폴더 생성

multerUploaderλ₯Ό s3AvatarUploader와 s3VideoUploader둜 λ‚˜λˆ΄λ‹€.

// middlewares.js
const s3AvatarUploader = multerS3({
  s3: s3,
  bucket: "syong/avatars", // 폴더 생성
  acl: "public-read",
});

const s3VideoUploader = multerS3({
  s3: s3,
  bucket: "syong/videos", // 폴더 생성
  acl: "public-read",
});

export const uploadAvatar = multer({
  dest: "uploads/avatars/",
  limit: {
    fileSize: 5000000,
  },
  storage: s3AvatarUploader,
});

export const uploadVideo = multer({
  dest: "uploads/vidoes/",
  limit: {
    fileSize: 10000000,
  },
  storage: s3VideoUploader,
});

(2) process.env.NODE_ENV

herokuμ—μ„œ μž‘μ—…ν•  λ•Œλ§Œ 파일 μ—…λ‘œλ“œ μ‹œ multer S3λ₯Ό μ‚¬μš©ν•˜κ³ , λ‚΄ μ»΄ν“¨ν„°μ—μ„œ μž‘μ—…(ν…ŒμŠ€νŠΈ)ν•  땐 λ‘œμ»¬μ— μ €μž₯λ˜λ„λ‘ ν•˜λ €κ³  ν•œλ‹€.

process.env.NODE_ENVλŠ” productionμ΄λΌλŠ” 값을 κ°€μ§€λŠ”, heroku에 μ •μ˜λ˜μ–΄ μžˆλŠ” λ³€μˆ˜μ΄λ‹€.
즉, herokuμ—μ„œ μž‘μ—… 쀑일 λ•Œ(이 λ³€μˆ˜κ°€ 값을 κ°€μ§ˆ λ•Œ)만 multer S3λ₯Ό μ‚¬μš©ν•˜λ„λ‘ ν•œλ‹€.
localhostμ—μ„œ μž‘μ—… 쀑일 λ•Œ(이 λ³€μˆ˜κ°€ 값을 가지지 μ•Šμ„ λ•Œ)λŠ” νŠΉλ³„ν•œ storageλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³  λ‘œμ»¬μ— λ§Œλ“€μ–΄λ‘” uploads 폴더에 νŒŒμΌμ„ μ €μž₯ν•œλ‹€.

// middlewares.js
const isHeroku = process.env.NODE_ENV === "production";

const s3AvatarUploader = multerS3({
  s3: s3,
  bucket: "syong/avatars",
  acl: "public-read",
});

const s3VideoUploader = multerS3({
  s3: s3,
  bucket: "syong/videos",
  acl: "public-read",
});

export const uploadAvatar = multer({
  dest: "uploads/avatars/",
  limit: {
    fileSize: 5000000,
  },
  storage: isHeroku ? s3AvatarUploader : undefined,
});

export const uploadVideo = multer({
  dest: "uploads/vidoes/",
  limit: {
    fileSize: 10000000,
  },
  storage: isHeroku ? s3VideoUploader : undefined,
});

그런데 μ΄λ ‡κ²Œ ν•˜λ©΄ λ‘œμ»¬μ—μ„œ μž‘μ—…ν•  λ•Œ avatarUrl 값인 file.location이 null이 되기 λ•Œλ¬Έμ— 상황에 따라 file.pathκ°€ 될 수 μžˆλ„λ‘ μ½”λ“œλ₯Ό μˆ˜μ •ν–ˆλ‹€.

// userController.js
export const postEdit = async (req, res) => {
  // μ€‘λž΅
  const isHeroku = process.env.NODE_ENV === "production"; // μΆ”κ°€ ❗
  const updatedUser = await User.findByIdAndUpdate(
    _id,
    {
      avatarUrl: file ? (isHeroku ? file.location : file.path) : avatarUrl, // μˆ˜μ • ❗
      name,
      email,
      username,
      location,
    },
    { new: true }
  );
  // μ€‘λž΅
};
// videoController.js
export const postUpload = async (req, res) => {
  // μ€‘λž΅
  const isHeroku = process.env.NODE_ENV === "production"; // μΆ”κ°€ ❗
  try {
    const newVideo = await Video.create({
      fileUrl: isHeroku ? video[0].location : video[0].path, // μˆ˜μ • ❗
      thumbUrl: isHeroku ? thumb[0].location : thumb[0].path, // μˆ˜μ • ❗
      title,
      description,
      hashtags: Video.formatHashtags(hashtags),
      owner: _id,
    });
	// μ€‘λž΅
  } catch (error) {
    // μ€‘λž΅
    });
  }
  return res.redirect("/");
};

λ‘œμ»¬μΈμ§€ Heroku인지λ₯Ό μ•ŒκΈ° μœ„ν•΄ isHeroku λ³€μˆ˜λ₯Ό ν…œν”Œλ¦Ώμ—μ„œ μ‚¬μš©ν•  수 μžˆλ„λ‘ localsMiddleware에 μ§€μ •ν–ˆλ‹€.
파일이 λ‘œμ»¬μ— μ €μž₯될 λ•ŒλŠ” 파일이 μƒλŒ€ κ²½λ‘œκ°€ λ˜μ§€ μ•Šλ„λ‘ img의 src 속성에 "/"λ₯Ό μΆ”κ°€ν•΄μ•Ό ν•œλ‹€.
μ‹€μ œλ‘œ μ¨μ£Όμ§€λŠ” μ•Šμ•˜λ‹€.

// middlewares.js
const isHeroku = process.env.NODE_ENV === "production";

export const localsMiddleware = (req, res, next) => {
  res.locals.siteName = "Wetube";
  res.locals.loggedIn = Boolean(req.session.loggedIn);
  res.locals.loggedInUser = req.session.loggedInUser || {};
  res.locals.isHeroku = isHeroku; // μΆ”κ°€ ❗
  next();
};

2) πŸ™‹β€β™€οΈ github avatar κ°€μ Έμ˜€κΈ°

πŸ”₯πŸ”₯ 질문 πŸ”₯πŸ”₯

λ¬Έμ œλŠ” github둜 둜그인 ν–ˆμ„ λ•Œ github avatar 즉, ν”„λ‘œν•„ 사진이 μ•ˆ λ³΄μΈλ‹€λŠ” 것이닀.
3일간 CORS만 κ²€μƒ‰ν•˜κ³  κ³΅λΆ€ν–ˆμ§€λ§Œ 결과적으둜 μ—λŸ¬λŠ” ν•΄κ²°ν•˜μ§€ λͺ»ν–ˆλ‹€.

일단 μ§€κΈˆκΉŒμ§€ μ•Œκ²Œ 된 κ±Έ μˆœμ„œλŒ€λ‘œ μ •λ¦¬ν•˜μžλ©΄

  1. λ‚˜λŠ” githubμ—μ„œ access_token을 λ°›μ•„μ™€μ„œ userData에 μ ‘κ·Όν•΄ ν”„λ‘œν•„ 사진을 κ°€μ Έμ˜€κ³  μ‹Άλ‹€.

  2. λΈŒλΌμš°μ €λŠ” λ³΄μ•ˆ μƒμ˜ 이유둜 same origin policy(SOP)λ₯Ό νƒν•˜κ³  μžˆμ–΄ 기본적으둜 동일 μΆœμ²˜κ°€ μ•„λ‹ˆλΌλ©΄ λ¦¬μ†ŒμŠ€λ₯Ό κ³΅μœ ν•  수 μ—†λ‹€. λ‹€λ₯Έ μΆœμ²˜μ—μ„œ λ¦¬μ†ŒμŠ€λ₯Ό κ³΅μœ ν•˜λ €λ©΄ CORS(ꡐ차 좜처 λ¦¬μ†ŒμŠ€ 곡유) 정책을 μ·¨ν•΄μ•Ό ν•œλ‹€.

  3. CORS의 λ™μž‘ λ°©μ‹μœΌλ‘œλŠ” simple request, preflight request, credentials request μ„Έ 가지가 μžˆλ‹€. λ‚˜μ˜ 경우 preflight requestμ΄λ©΄μ„œ credentials requestμ˜€λ‹€.

  4. 처음 뜬 μ—λŸ¬λŠ” λ‹€μŒκ³Ό κ°™μ•˜λ‹€.
    일반 λ‘œκ·ΈμΈμ΄λ“  github λ‘œκ·ΈμΈμ΄λ“  λͺ¨λ“  λ‘œκ·ΈμΈμ—μ„œ ν”„λ‘œν•„ 사진이 보이지 μ•Šκ³  μ—λŸ¬κ°€ λ–΄λ‹€.

ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep 200

  1. require-corpλ₯Ό credentialless둜 λ°”κΎΌ ν›„ img νƒœκ·Έμ—μ„œ crossorigin 속성을 crossorigin="use-credentials"으둜 μˆ˜μ •ν–ˆλ‹€.
    (SharedArrayBuffer λ•Œλ¬Έμ— COEP / COOPλ₯Ό μ§€μšΈ μˆ˜λŠ” μ—†λ‹€.)
// server.js
app.use((req, res, next) => {
  res.header("Cross-Origin-Embedder-Policy", "credentialless");
  res.header("Cross-Origin-Opener-Policy", "same-origin");
  next();
});
//- header.pug & profile.pug
img(src=loggedInUser.avatarUrl, crossorigin="use-credentials").profile-img
  1. 일반 λ‘œκ·ΈμΈμ„ ν–ˆμ„ λ•ŒλŠ” ν”„λ‘œν•„ 사진이 잘 λ³΄μ˜€λ‹€.
    κ·ΈλŸ¬λ‚˜ μ—¬μ „νžˆ github둜 λ‘œκ·ΈμΈμ„ ν•˜λ©΄ 사진 μ•„μ΄μ½˜λ§Œ 뜨고 사진이 μ•ˆ λ³΄μ΄λ©΄μ„œ μ½˜μ†”μ—λŠ” λ‹€μŒκ³Ό 같은 μ—λŸ¬κ°€ λ–΄λ‹€.

~ ~ ~ has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
GET https: //avatars.githubusercontent.com/u/~ ~ ~ net::ERR_FAILED 200

  1. μ—λŸ¬ λ©”μ‹œμ§€ κ·ΈλŒ€λ‘œ Access-Control-Allow-Originλ₯Ό *κ°€ μ•„λ‹ˆλΌ ꡬ체적으둜 λͺ…μ‹œν•  ν•„μš”κ°€ μžˆμ—ˆλ‹€.
    μ°Ύμ•„λ³΄λ‹ˆ Heroku의 Settings λ©”λ‰΄μ—μ„œ λ”°λ‘œ CORSλ₯Ό ꡬ성할 μˆ˜κ°€ μžˆμ—ˆλ‹€.
    μ•ˆλ‚΄ λ¬Έμ„œλ₯Ό μ°Έκ³ ν•΄ λ‹€μŒκ³Ό 같이 μž‘μ„±ν–ˆλ‹€.

    μ†”μ§νžˆ μ—¬κΈ°κΉŒμ§€ μ°ΎλŠ” 데만 해도 정말 였래 κ±Έλ Έλ‹€. credentials requestλŠ” Access-Control-Allow-Credentialsλ₯Ό true둜 μ„€μ •ν•΄μ•Ό ν•˜κ³ , Access-Control-Allow-Origin'을 *둜 μ„€μ •ν•˜λ©΄ μ•ˆλ˜κ³  ꡬ체적으둜 지정해야 ν•œλŒ€μ„œ express cors middleware도 μ„€μΉ˜ν•΄μ„œ originκ³Ό credentials true μ˜΅μ…˜λ„ 넣어보고, server.js νŒŒμΌμ—μ„œ res.header둜 섀정해보고 근데 또 trueλŠ” λ¬Έμžμ—΄λ‘œ 써야 ν•˜λŠ”μ§€ boolean κ°’μœΌλ‘œ 써야 ν•˜λŠ”μ§€ μ°Ύμ•„λ³΄λŠ” κ³³λ§ˆλ‹€ 말이 틀리고 μ‚¬μ†Œν•œ κ±° ν•˜λ‚˜ν•˜λ‚˜κΉŒμ§€ 이해가 μ•ˆλ˜κ³  ν—·κ°ˆλ Έλ‹€.

    μ΄λ ‡κ²Œ λ‚˜μ—΄ν•˜λ‹ˆκΉŒ μ°Έ 별 κ±° μ—†μ–΄ 보인닀. λˆ„κ΅°κ°€λŠ” 10λΆ„ μ•ˆμ— 끝날 μ‹œλ„λ“€μ΄μ œκ² μ§€λ§Œ μ˜μ–΄ λ¬Έμ„œ ν•΄μ„ν•˜κ³  이둠 μ΄ν•΄ν•˜κ³  μ‹€μ œλ‘œ λ‚΄ 사둀에 맞좰 μ μš©ν•˜λŠ”λ° ν•œμ„Έμ›”μ΄μ—ˆλ‹€. 아직도 μ™„λ²½νžˆ 이해 λͺ»ν–ˆλŠ”데 ν•˜λ„ λ°˜λ³΅ν•΄μ„œ 보닀 λ³΄λ‹ˆ μΌλΆ€λΆ„λ§Œ 머리에 λ°•νžŒ μƒνƒœ κ°™λ‹€.

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "HEAD",
            "PUT",
            "GET",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "https://syongtube.herokuapp.com"
        ],
        "ExposeHeaders": []
    }
]
  1. ν•˜μ§€λ§Œ μ—¬μ „νžˆ 해결은 μ•ˆ 됐닀.
    정말 의문인 것은 λΆ„λͺ… origin을 λͺ…μ‹œν•΄μ€¬λŠ”λ° network 탭을 ν™•μΈν•˜λ©΄ 응닡 ν—€λ”μ˜ Access-Control-Allow-Origin이 *λΌλŠ” 것이닀.
    일반 둜그인 ν›„ ν”„λ‘œν•„ 사진을 ν΄λ¦­ν•˜λ©΄ origin이 μ§€μ •ν•œ μ›Ή μ‚¬μ΄νŠΈ url둜 잘 λ‚˜μ˜€λŠ”λ° github 둜그인만 ν•˜λ©΄ μ €λ ‡κ²Œ λ³€ν•΄ μžˆλ‹€.
    κ·ΈλŸ¬λ‹€ 이 λ¬Έμ„œλ₯Ό λ°œκ²¬ν–ˆλ‹€.

cors issue on github oauth

While all the actual GitHub API endpoints support CORS by sending the right response headers, it is a known issue that the https: //github.com/login/oauth/access_token endpoint for creating an OAuth access token does not support CORS requests from Web applications.

The very specific workaround for this case is to use https: //github.com/prose/gatekeeper:

Gatekeeper: Enables client-side applications to dance OAuth with GitHub.

Because of some security-related limitations, Github prevents you from implementing the OAuth Web Application Flow on a client-side only application.

This is a real bummer. So we built Gatekeeper, which is the missing piece you need in order to make it work.

  1. ???? λ‚΄κ°€ 맞게 μ΄ν•΄ν•œ 건가. λ‹Ήμž₯ κ΄€λ ¨ 문제λ₯Ό κ²€μƒ‰ν•΄λ΄€λŠ”λ° 생각보닀 글이 μ•ˆ λ‚˜μ™”λ‹€. λ‚΄κ°€ 검색을 λͺ»ν•˜λŠ” 건지 μ˜μ–΄λ‘œ 검색해도 글이 λ³„λ‘œ μ—†μ—ˆλ‹€. μ•„λ¬΄λž˜λ„ λ‚΄κ°€ 잘λͺ» μ΄ν•΄ν•œ 건지 이 정보가 ν‹€λ¦° 건지.

    μ–΄μ¨Œλ“  해결은 ν•΄μ•Ό ν–ˆκΈ°μ— 일단 μ•ˆλ‚΄μ— 따라 gatekeeperλ₯Ό heroku에 λ°°ν¬ν–ˆλ‹€.
    근데 μ‚¬μš©λ²•μ΄ ν•˜ν•„ jquery둜 λ‚˜μ™€ μžˆμ—ˆλ‹€. jqueryλ₯Ό 1도 λͺ°λΌμ„œ $.getJSON()을 바닐라 JS둜 μ–΄λ–»κ²Œ λ°”κΎΈλŠ”μ§€ 검색해봀닀.
    λ‚΄κ°€ μ΄ν•΄ν•œ λ°”λ‘œλŠ” const json = await (await fetch()).json() ν•˜κ³  받은 json 값을 μ΄μš©ν•΄ μ½”λ“œλ₯Ό μ¨λ‚˜κ°€λŠ” 건데 이것뢀터 맞게 μ΄ν•΄ν•œ 건지 μ „ν˜€ ν™•μ‹€ν•˜μ§€ μ•Šκ³  μ–΄μ¨Œλ“  μ½”λ“œλ₯Ό μˆ˜μ •ν–ˆλ‹€.

    기쑴에 λ‚΄κ°€ μž‘μ„±ν–ˆλ˜, githubμ—μ„œ access_token을 λ°›μ•„μ˜€λŠ” μ½”λ“œλ₯Ό λ‹€μŒκ³Ό 같이 κ³ μ³€λ‹€.
    .env 파일과 herokuμ—μ„œ λ³€μˆ˜λ„ μˆ˜μ •ν•΄μ€¬λ‹€.

// userController.js
export const startGithubLogin = (req, res) => {
  const baseUrl = "https://github.com/login/oauth/authorize";
  const config = {
    client_id: process.env.OAUTH_CLIENT_ID,
    allow_signup: false,
    scope: "read:user user:email",
  };
  const params = new URLSearchParams(config).toString();
  const finalUrl = `${baseUrl}?${params}`;
  return res.redirect(finalUrl);
};

export const finishGithubLogin = async (req, res) => {
  // μ—¬κΈ°μ„œλΆ€ν„°
  const code = window.location.href.match(/\?code=(.*)/)[1];
  const tokenRequest = await (
    await fetch("http://localhost:9999/authenticate/" + code, {
      method: "POST",
      headers: {
        Accept: "application/json",
      },
    })
  ).json();
  // μ—¬κΈ°κΉŒμ§€ μˆ˜μ •
  // μ›λž˜λŠ” fetch ν•¨μˆ˜λ‘œ https://github.com/login/oauth/access_token에 post μš”μ²­μ„ λ³΄λ‚΄μ„œ tokenRequestλ₯Ό λ°›μ•„μ˜€λŠ” μ½”λ“œμ˜€λ‹€.
  if ("token" in tokenRequest) {
    const { token } = tokenRequest;
    const apiUrl = "https://api.github.com";
    const userData = await (
      await fetch(`${apiUrl}/user`, {
        headers: {
          Authorization: `token ${token}`,
        },
      })
    ).json();
    const emailObj = emailData.find(
      (email) => email.primary === true && email.verified === true
    );
    if (!emailObj) {
      req.flash("error", "No valid email.");
      return res.redirect("/login");
    }
    let user = await User.findOne({ email: emailObj.email });
    if (!user) {
      user = await User.create({
        avatarUrl: userData.avatar_url,
        name: userData.name,
        email: emailObj.email,
        username: userData.login,
        password: "", 
        socialOnly: true,
        location: userData.location,
      });
    }
    req.session.loggedIn = true;
    req.session.user = user;
    return res.redirect("/");
  } else {
    req.flash("error", "No access token");
    return res.redirect("/login");
  }
};
  1. μ½˜μ†” 창을 ν™•μΈν•œ κ²°κ³Ό, μ—λŸ¬κ°€ λ°”λ€Œμ—ˆλ‹€! 말 κ·ΈλŒ€λ‘œ λ°”λ€Œμ—ˆλ‹€. 해결이 μ•„λ‹ˆλΌ.
    κ²°κ³Όμ μœΌλ‘œλŠ” μ΄μ œλŠ” 둜그인이 μ•„μ˜ˆ μ•ˆ λœλ‹€.
    finishGithubLogin 컨트둀러λ₯Ό κ±΄λ“œλ ΈμœΌλ‹ˆ 둜그인이 μ•ˆλ  μˆ˜λ°–μ—...
    μ—λŸ¬ λ‚΄μš©μ€ λ‹€μŒκ³Ό κ°™κ³  network 탭을 ν™•μΈν•˜λ©΄ μ•„κΉŒλŠ” μ•ˆ λ–΄λ˜ λ‚΄μš©μ΄ λ–  μžˆλ‹€.
    client IDκ°€ 막 λ…ΈμΆœλ˜μ–΄ μžˆλŠ”λ° 뭐가 κ³΅κ°œν•΄λ„ 되고 μ•ˆ λ˜λŠ”μ§€ 뢄간이 μ•ˆλΌμ„œ μ˜¬λ¦¬μ§€λŠ” λͺ»ν•˜κ² λ‹€.
    λ°”κΏ” λ§ν•˜λ©΄ κ·Έλž˜μ„œ gatekeeperλŠ” 적용이 된 건지 μ•ˆλœ 건지 였λ₯˜λŠ” μ–΄λ””μ„œ λ‚œ 것인지 찾지λ₯Ό λͺ»ν•˜κ² λ‹€.

GET https://syongtube.herokuapp.com/users/github/finish?code=435043a20b7f90bb35ad 503 (Service Unavailable)

  1. λ‹€μ‹œ μ›λž˜λŒ€λ‘œ μ½”λ“œλ₯Ό 돌리고 μ΄λ²ˆμ—” ν”„λ‘μ‹œ μ„œλ²„λ₯Ό μ‹œλ„ν•΄λ΄€λ‹€.
    cors-anywhere, http-proxy-middle 등을 μ΄μš©ν•΄ κ°„νŽΈν•˜κ²Œ λ§Œλ“€ 수 μžˆλ‹€κ³  ν•΄μ„œ cors-anywhereλ₯Ό μ΄μš©ν•΄λ΄€λ‹€.
    heroku에 배포λ₯Ό 끝낸 ν›„ λ§Œλ“€μ–΄μ§„ ν”„λ‘μ‹œ μ„œλ²„ 뒀에 μš”μ²­ 보낼 (access_token κ΅¬ν•˜λŠ”) url을 λΆ™μ—¬μ„œ fetch ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄ λ΄€μ§€λ§Œ μ—­μ‹œλ‚˜ μ‹€νŒ¨ν–ˆλ‹€.

  2. μ—¬κΈ°κΉŒμ§€ μ§„ν–‰ν–ˆλ‹€κ°€ 503 μ—λŸ¬μ— λŒ€ν•΄ λ‹€μ‹œ κ²€μƒ‰ν•΄λ΄€λŠ”λ° 이런 글을 μ°Ύμ•˜λ‹€.
    λ‚΄κ°€ μ½”λ“œλ₯Ό 잘λͺ» κ±΄λ“€μ—¬μ„œ 둜그인'μ‘°μ°¨' μ•ˆ λ˜λŠ” 거라고 μƒκ°ν–ˆλŠ”λ° 그게 μ•„λ‹Œ 건가. μ•„λ‹ˆλ©΄ λ‚΄ κ²½μš°μ—” 해당이 μ•ˆ λ˜λŠ” 말인 걸까.

What is a 503 Service Unavailable Error (And How Can I Fix It)?

An important thing to remember is that the 503 error is a server-side error. That means the problem exists with the website you’re trying to access, and not with your computer. That’s both good and bad news. It’s good news because there’s nothing wrong with your computer, and it’s bad news because there’s usually nothing you can do to solve the problem from your end.


✨ 내일 ν•  것

  1. μˆ˜μ • 및 볡슡
profile
λŠ₯λ™μ μœΌλ‘œ μ‚΄μž, ν–‰λ³΅ν•˜κ²ŒπŸ˜

0개의 λŒ“κΈ€