프로그래밍/Web

Authentication Level 3 - Managing Cookies and Sessions

studylida 2024. 4. 11. 21:00
// solution.js
// Not code written by me. This is example code

import express from "express";
import bodyParser from "body-parser";
import pg from "pg";
import bcrypt from "bcrypt";
import passport from "passport";
import { Strategy } from "passport-local";
import session from "express-session";
import env from "dotenv";

const app = express();
const port = 3000;
const saltRounds = 10;
env.config();

app.use(
  session({
    secret: "TOPSECRETWORD",
    resave: false,
    saveUninitialized: true,
  })
);

/*
1. **`secret` (비밀 키)**:
   - `secret`은 세션 데이터를 암호화하는 데 사용되는 비밀 키입니다.
   - 이 값은 무작위로 생성된 문자열로, 세션 데이터의 보안을 강화합니다.
   - 다른 사람이 이 값을 알게 되면 세션 데이터가 노출될 수 있으므로, 보안 상 주의해야 합니다.

2. **`resave`**:
   - `resave`는 세션 데이터가 변경되지 않았더라도 서버에 다시 저장할지 여부를 결정합니다.
   - `false`로 설정하면 세션 데이터가 변경되지 않았을 때도 서버에 저장하지 않습니다.
   - 일반적으로 `false`로 설정하여 성능을 최적화합니다.

3. **`saveUninitialized`**:
   - `saveUninitialized`는 초기화되지 않은 세션 데이터를 저장할지 여부를 결정합니다.
   - `true`로 설정하면 초기화되지 않은 세션 데이터도 저장됩니다.
   - 사용자가 로그인하지 않은 경우에도 세션을 생성하고 저장하려면 `true`로 설정합니다.
*/

app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));

app.use(passport.initialize());
app.use(passport.session());

/*
1. **Passport 설정 및 사용**:
   - `passport` 모듈을 가져옵니다.
   - `app.use(passport.initialize())`와 `app.use(passport.session())`을 통해 Passport를 초기화하고 세션 관리를 설정합니다.
*/

const db = new pg.Client({
  user: "postgres",
  host: "localhost",
  database: "secrets",
  password: "123456",
  port: 5432,
});
db.connect();

app.get("/", (req, res) => {
  res.render("home.ejs");
});

app.get("/login", (req, res) => {
  res.render("login.ejs");
});

app.get("/register", (req, res) => {
  res.render("register.ejs");
});

app.get("/logout", (req, res) => {
  req.logout(function (err) {
    if (err) {
      return next(err);
    }
    res.redirect("/");
  });
});

app.get("/secrets", (req, res) => {
  // console.log(req.user);
  if (req.isAuthenticated()) {
    res.render("secrets.ejs");
  } else {
    res.redirect("/login");
  }
});

app.post(
  "/login",
  passport.authenticate("local", {
    successRedirect: "/secrets",
    failureRedirect: "/login",
  })
);
/*
4. **로그인 처리**:
   - `/login` 경로에서 POST 요청이 들어오면 `passport.authenticate("local", ...)`을 통해 로그인 처리를 합니다.
   - 인증 성공 시 `/secrets`로 리디렉션하고, 실패 시 `/login`으로 리디렉션합니다.
*/

app.post("/register", async (req, res) => {
  const email = req.body.username;
  const password = req.body.password;

  try {
    const checkResult = await db.query("SELECT * FROM users WHERE email = $1", [
      email,
    ]);

    if (checkResult.rows.length > 0) {
      req.redirect("/login");
    } else {
      bcrypt.hash(password, saltRounds, async (err, hash) => {
        if (err) {
          console.error("Error hashing password:", err);
        } else {
          const result = await db.query(
            "INSERT INTO users (email, password) VALUES ($1, $2) RETURNING *",
            [email, hash]
          );
          const user = result.rows[0];
          req.login(user, (err) => {
            console.log("success");
            res.redirect("/secrets");
          });
        }
      });
    }
  } catch (err) {
    console.log(err);
  }
});

/*
5. **사용자 등록 처리**:
   - `/register` 경로에서 POST 요청이 들어오면 사용자의 이메일과 비밀번호를 받습니다.
   - 이메일이 이미 데이터베이스에 있는지 확인하고, 없다면 bcrypt를 사용하여 비밀번호를 해시하고 데이터베이스에 저장합니다.
   - 사용자를 로그인 상태로 변경하고 `/secrets`로 리디렉션합니다.
*/

passport.use(
  new Strategy(async function verify(username, password, cb) {
    try {
      const result = await db.query("SELECT * FROM users WHERE email = $1 ", [
        username,
      ]);
      if (result.rows.length > 0) {
        const user = result.rows[0];
        const storedHashedPassword = user.password;
        bcrypt.compare(password, storedHashedPassword, (err, valid) => {
          if (err) {
            //Error with password check
            console.error("Error comparing passwords:", err);
            return cb(err);
          } else {
            if (valid) {
              //Passed password check
              return cb(null, user);
            } else {
              //Did not pass password check
              return cb(null, false);
            }
          }
        });
      } else {
        return cb("User not found");
      }
    } catch (err) {
      console.log(err);
    }
  })
);

/*
2. **Local Strategy**:
   - 로컬 전략을 사용하여 사용자 이름과 비밀번호를 기반으로 인증합니다.
   - `passport.use(new Strategy(...))`를 통해 로컬 전략을 정의합니다.
   - 사용자가 로그인할 때, 입력한 비밀번호를 데이터베이스에 저장된 해시된 비밀번호와 비교합니다.
*/

passport.serializeUser((user, cb) => {
  cb(null, user);
});
passport.deserializeUser((user, cb) => {
  cb(null, user);
});

/*
3. **serializeUser와 deserializeUser**:
   - `passport.serializeUser`와 `passport.deserializeUser`를 사용하여 사용자 정보를 세션에 저장하고 불러옵니다.
   - 사용자 정보를 직렬화하여 세션에 저장하고, 역직렬화하여 세션에서 사용자 정보를 가져옵니다.
*/

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});