본문 바로가기

코딩/project

pj, 5일차

// controller/auth.js

import * as authRepository from '../data/auth.js';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { config } from '../config.js';



// async function makeToken(id){
//     const token = jswonwebtoken.sign({
//         id: id,
//         isAdmin: false
//     }, secret, {expiresIn: '1h'})
//     return token;
// }


function createJwtToken(id){
   return jwt.sign({id}, config.jwt.secretKey, {expiresIn: config.jwt.expiresInSec});
}


// 회원가입
export async function signup(req, res, next){
    const { username, password, name, email, url } = req.body;
    const found = await authRepository.findByUsername(username);
    if(found){
        return res.status(409).json({message:`${username}이 이미 있습니다`});
    }
    const hashed =bcrypt.hash(password, config.bcrypt.saltRounds);
    const userId = await authRepository.createUser({username, hashed, name, email, url});
    const token = createJwtToken(userId);
    res.status(201).json({token, username});
    
}
//로그인
export async function login(req, res, next){
    const { username, password } = req.body;
    // const user = await authRepository.login(username);\
    const user = await authRepository.findByUsername(username);
    if(!user){
        return res.status(401).json({message: `아이디를 찾을 수 없음`});
    }
    const isValidpassword = await bcrypt.compareSync(password, user.password);
    if(!isValidpassword){
        return res.status(401).json({message: `비밀번호가 틀렸음`});
    }
    const token =createJwtToken(user.id);
    res.status(200).json({token, username});
}

//토큰이 있는지 여부, 로그인 흔적 확인
// export async function verify(req, res, next){
//     const token = req.header['Token'];
//     if(token){
//         res.status(200).json(token);
//     }
// }

export async function me(req, res, next){
    const user = await authRepository.findById(req.userId);
    if(!user){
        return res.status(404).json({message: `일치하는 사용자가 없음`});
    }
    res.status(200).json({token: req.token, username: user.username});
}

 

// /data/auth.js

let users = [
    {
        id: '1',
        // userid: "apple",
        username: "apple",
        password: "$2b$10$TvIhNWYtvCNzOA0ZkmNg6.YU3EG49msPLzxphLYahMT63DVWRWZTe",
        name: "김사과",
        email: "apple@apple.com",
        url: "https://www.logoyogo.com/web/wp-content/uploads/edd/2021/02/logoyogo-1-45.jpg"
    },
    {
        id: '2',
        // userid: "banana",
        username: "banana",
        password: "$2b$10$TvIhNWYtvCNzOA0ZkmNg6.YU3EG49msPLzxphLYahMT63DVWRWZTe",
        name: "반하나",
        email: "banana@banana.com",
        url: "https://img.freepik.com/premium-vector/banana-cute-kawaii-style-fruit-character-vector-illustration_787461-1772.jpg"
    }
];

// 아이디 중복검사
export async function findByUsername(username){
    return users.find((user) => user.username === username);
}

// id 중복검사
export async function findById(id){
    return users.find((user) => user.id === id);
}

export async function createUser(user){
    const created = {id: '10', ...user }
    users.push(created);
    return created.id;
}
export async function login(username){
    return users.find((users) => users.username === username);
}

 

//middleware/auth.js

import jwt from 'jsonwebtoken';
import * as authRepository from '../data/auth.js';

const AUTH_ERROR = {message: "인증에러"};
export const isAuth = async (req, res, next) => {
    const authHeader = req.get('Authorization');
    console.log(authHeader);
    if(!(authHeader && authHeader.startsWith('Bearer '))){
        console.log('에러1');
        return res.status(401).json(AUTH_ERROR);
    }
    const token = authHeader.split(' ')[1];
    jwt.verify(
        token, 'abcd1234%^&*', async(error, decoded) => {
            if(error){
                console.log('에러2');
                return res.status(401).json(AUTH_ERROR);
            }
            const user = await authRepository.findById(decoded.id);
            if(!user){
                console.log('에러3');
                return res.status(401).json(AUTH_ERROR);
            }
            req.userId = user.id;
            next();
        }
    );
}

 

// router/auth.js

import express from "express";
import { body } from 'express-validator';
import *  as authController from '../controller/auth.js';
import { validate } from "../middleware/validator.js";
import { isAuth } from '../middleware/auth.js';

const router = express.Router();

const validateLogin = [
    body('username').trim().notEmpty().withMessage('username을 입력하세요'),
    body('password').trim().isLength({min:4}).withMessage('password는 최소 4자 이상 입력하세요'), validate
];

const validateSignup = [
    ... validateLogin,
    body('name').trim().notEmpty().withMessage('name을 입력하세요'),
    body('email').isEmail().withMessage('이메일 형식 확인하세요'), validate,
    body('url').isURL().withMessage('URL 형식 확인하세요'), validate
];

router.post('/signup', validateSignup, authController.signup);

router.post('/login', validateLogin, authController.login);

router.get('/me', isAuth, authController.me);

export default router;

 

// .env

# DB
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=****

# JWT
JWT_SECRET=abcd1234%^&*
JWT_EXPIRES_SEC=2d

# BCRYPT
BCRYPT_SALT_ROUNDS=10

# SERVER
PORT=8080

 

// client.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>클라이언트</title>
    <script src="https://cdn.socket.io/4.7.5/socket.io.min.js" integrity="sha384-2huaZvOR9iDzHqslqwpR87isEmrfxqyWOF7hr7BY6KG0+hVKLoEXMPUJw3ynWuhO" crossorigin="anonymous"></script>
    <script>
        const socket = io();

        function setNickname(){
            const nickname = document.getElementById('nickname').value;
            socket.emit('setNickname', nickname);
        }

        function sendMessage(){
            const message = document.getElementById('message').value;
            socket.emit('message', message);
        }

        function addEmoji(emoji){
            const message = document.getElementById('message');
            message.value += emoji;
        }

        function setChannel(){
            const channel = document.getElementById('channel').value;
            socket.emit('setChannel', channel);
        }

        // 메세지 수신 이벤트 처리
        socket.on('setNickname', (message) => {
            const chatBox = document.getElementById('chatBox');
            const newMessage = document.createElement('p');
            newMessage.textContent = message;
            chatBox.appendChild(newMessage);
        });

        socket.on('message', (message) => {
            console.log(message);
            const chatBox = document.getElementById('chatBox');
            const newMessage = document.createElement('p');
            newMessage.textContent = `${message.sender}: ${message.message}`;
            chatBox.appendChild(newMessage);
            document.getElementById('message').value = '';
        });

        socket.on('updateChannelList', (channelList) => {
            const channelListElement = document.getElementById('channelList');
            channelListElement.innerHTML = '';
            channelList.forEach((channel) => {
                const channelItem = document.createElement('li');
                channelItem.textContent = channel;
                channelListElement.appendChild(channelItem);
            });
        })
    </script>
</head>
<body>
    <h2>간단한 채팅</h2>
    <form>
        <p>닉네임: <input type="text" id="nickname"> <button type="button" onclick="setNickname()">설정</button></p>
        <p>채널: <input type="text" id="channel"> <button type="button" onclick="setChannel()">입장</button></p>
        <p><ul id="channelList"></ul></p>
        <p><input type="text" id="message"> <button type="button" onclick="sendMessage()">보내기</button> <button type="button" onclick="addEmoji('😎')">😎</button> <button type="button" onclick="addEmoji('🎃')">🎃</button> <button type="button" onclick="addEmoji('😛')">😛</button> <button type="button" onclick="addEmoji('😊')">😊</button> <button type="button" onclick="addEmoji('🤣')">🤣</button></p>
    </form>
    <div id="chatBox"></div>
</body>
</html>

 

// config.js

import dotenv from 'dotenv';
dotenv.config();
function required(key, defaultValue=undefined){
    const value = process.env[key] || defaultValue; // or: 앞의 값이 true로 판별되면 앞의 값이 대입되고 값이 false로 판별되면 뒤에 값이 대입됨
    if(value == null){
        throw new Error(`키 ${key}는 undefined!!`);
    }
    return value;
}
export const config = {
    jwt: {
        secretKey: required('JWT_SECRET'),
        expiresInSec: parseInt(required('JWT_EXPIRES_SEC', 172800))
    },
    bcrypt: {
        saltRounds: parseInt(required('BCRYPT_SALT_ROUNDS', 10))
    },
    host: {
        port: parseInt(required('HOST_PORT', 8080))
    }
}

 

// server.js

import express from 'express';
import http from 'http';
import { Server } from 'socket.io';
import path from 'path';
import { fileURLToPath } from 'url';

const app = express();
const server = http.createServer(app);
const io = new Server(server);

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// 정적 파일 서빙
// localhost:8080/client.html
app.use(express.static(__dirname));

const channels = new Set();

io.on('connection', (socket) => {
    console.log('사용자가 연결되었습니다');

    let nickname = '';
    let channel = '';

    // 닉네임 설정
    socket.on('setNickname', (name) => {
        nickname = name;
        console.log(`닉네임 설정: ${nickname}`);
        io.emit('setNickname', `알림: 닉네임 설정됨 ${nickname}`);
    });

    // 메세지 설정
    socket.on('message', (message) => {
        console.log(`클라이언트: ${channel}에서 ${nickname} -> ${message}`);
        io.to(channel).emit('message', {sender: nickname, message});
    });

    // 채널 설정
    socket.on('setChannel', (ch) => {
        channel = ch;
        socket.join(channel);
        channels.add(channel);
        console.log(`클라이언트: ${nickname}님이 채널 ${channel}에 입장`);
        io.emit('updateChannelList', Array.from(channels))
    });


    // 소켓 종료
    socket.on('disconnect', () => {
        console.log(`클라이언트: ${nickname} 접속 종료`);
    });
});


server.listen(8080, () => {
    console.log('서버가 8080포트에서 실행중!!');
})

 

 

 

🤍

 

ChatGPT에게 물어본 오늘의 공부-

 

✔️ textContent

textContent는 DOM 요소의 텍스트 콘텐츠를 나타내는 속성입니다. 이 속성은 해당 요소의 하위 요소들을 제외하고 텍스트만을 반환합니다. 즉, HTML 태그 내의 텍스트 내용을 가져오거나 설정하는 데 사용됩니다.

 

 

✔️ innerHTML

innerHTML은 DOM 요소의 HTML 내용을 나타내는 속성입니다. 이 속성을 사용하면 해당 요소의 자식 요소를 포함한 HTML 코드를 가져오거나 설정할 수 있습니다

 

innerHTML을 사용하면 HTML 태그 내의 모든 HTML 코드를 포함한 문자열이 반환되며, 이를 통해 HTML 내용을 동적으로 변경하거나 수정할 수 있습니다.

그러나 주의할 점은 innerHTML을 사용할 때 HTML을 직접 조작하는 것이므로 보안 측면에서 주의해야 합니다. 사용자 입력과 같은 신뢰할 수 없는 데이터를 innerHTML에 직접 대입하면 XSS(크로스 사이트 스크립팅) 공격에 취약해질 수 있습니다. 가능하면 textContent를 사용하여 텍스트를 조작하는 것이 좋습니다.

 

✔️ dotenv.config()와 process.env 객체

dotenv.config()는 Node.js 애플리케이션에서 .env 파일을 로드하여 환경 변수로 설정하는 데 사용됩니다. .env 파일은 주로 애플리케이션의 설정 정보나 민감한 정보(예: API 키, 데이터베이스 연결 정보 등)를 저장하는 데 사용됩니다. 이러한 정보들은 코드에 하드코딩하는 것보다 .env 파일에 저장하여 보안을 강화하고, 애플리케이션의 환경에 따라 설정을 유연하게 관리할 수 있습니다.

process.env 객체는 Node.js의 내장 객체로, 현재 실행 중인 프로세스의 환경 변수에 접근할 수 있게 해줍니다. 환경 변수는 시스템의 설정을 제어하는데 사용되며, 보안 토큰, 데이터베이스 연결 정보, 서버 포트 등과 같은 중요한 정보를 저장할 수 있습니다. dotenv.config()를 사용하여 .env 파일에서 환경 변수를 로드하면, 이러한 환경 변수는 process.env 객체에 저장되어 애플리케이션 전역에서 사용할 수 있게 됩니다.

따라서 dotenv.config()를 호출하면 .env 파일의 내용이 process.env 객체에 로드되고, 이후에 process.env[key]를 사용하여 해당 환경 변수의 값을 가져올 수 있습니다. 여기서 key는 환경 변수의 이름을 나타냅니다. 예를 들어, .env 파일에 PORT=3000과 같이 설정되어 있다면, process.env.PORT를 통해 포트 번호를 가져올 수 있습니다.

 

✔️ required 함수

required 함수는 환경 변수를 가져오는 역할을 합니다. 이 함수는 주어진 키에 해당하는 환경 변수를 가져오고, 만약 해당 키에 대한 환경 변수가 존재하지 않는다면 에러를 발생시킵니다. 이러한 접근 방식은 프로그램이 실행될 때 필요한 환경 변수들이 정의되어 있는지 확인하고, 없는 경우 프로그램이 올바르게 동작하지 못하도록 방지하는데 도움이 됩니다.

보안상의 이유로 비밀 값들(예: JWT 시크릿 키)은 소스 코드에 직접 하드코딩하는 것이 아니라 환경 변수로 관리하는 것이 좋습니다. 이렇게 함으로써 보안에 민감한 정보가 소스 코드에 노출되는 것을 방지할 수 있습니다. required 함수는 이러한 환경 변수들을 가져오고, 만약 해당 환경 변수가 존재하지 않는다면 에러를 발생시켜 프로그램이 정상적으로 실행되지 않도록 합니다.

 

✔️ Int와 parseInt

Int와 parseInt은 JavaScript에서 서로 다른 기능을 수행하는 함수입니다.

  1. Int: JavaScript의 내장 함수가 아니며, 대신 몇 가지 라이브러리에서 사용될 수 있습니다. 예를 들어, Python의 int()와 유사하게 문자열을 정수로 변환하는 데 사용될 수 있습니다. 예를 들어, Int('10')은 문자열 '10'을 정수 10으로 변환합니다. 하지만 JavaScript의 기본 내장 함수가 아니기 때문에 모든 환경에서 사용할 수 있는 것은 아닙니다.
  2. parseInt: JavaScript의 내장 함수로, 문자열을 정수로 변환하는 데 사용됩니다. parseInt는 문자열의 시작 부분부터 숫자를 읽고, 숫자가 아닌 문자를 만나면 변환을 중지합니다. 예를 들어, parseInt('10')은 문자열 '10'을 정수 10으로 변환합니다. 기본적으로 parseInt는 10진수로 해석합니다. 또한 두 번째 매개 변수로 진법(base)을 지정하여 다른 진법의 문자열도 파싱할 수 있습니다. 예를 들어, parseInt('10', 2)는 이진수 문자열 '10'을 정수 2로 변환합니다.

따라서 Int는 JavaScript의 기본 내장 함수가 아니며 일부 라이브러리에서 사용되는 경우가 있고, parseInt는 JavaScript의 내장 함수로 문자열을 정수로 변환하는 데 사용됩니다.

 

 

✔️ http 라이브러리

http 라이브러리는 Node.js의 내장 모듈 중 하나로, HTTP 프로토콜을 사용하여 서버 및 클라이언트 간의 통신을 처리하는 데 사용됩니다. 이 라이브러리는 HTTP 요청을 생성하고 서버를 생성하고 관리하는 데 필요한 다양한 함수와 클래스를 제공합니다.

주요 기능은 다음과 같습니다:

  1. 서버 생성: http.createServer() 함수를 사용하여 HTTP 서버를 생성할 수 있습니다. 이를 통해 클라이언트의 요청을 수신하고 해당 요청에 대한 응답을 제공할 수 있습니다.
  2. 요청 처리: 서버가 요청을 수신하면 request 이벤트가 발생하고, 이를 처리하기 위해 콜백 함수를 등록할 수 있습니다. 이 콜백 함수는 요청(request)과 응답(response) 객체를 매개변수로 받아 클라이언트 요청을 처리하고 응답을 생성합니다.
  3. 응답 생성: 요청 처리기에서 응답 객체를 사용하여 클라이언트에게 응답을 보낼 수 있습니다. 응답을 생성하는 메서드에는 response.writeHead() 및 response.end()와 같은 메서드가 포함됩니다.
  4. 클라이언트 요청 생성: http.request() 함수를 사용하여 HTTP 클라이언트 요청을 생성할 수 있습니다. 이를 통해 다른 서버로 HTTP 요청을 보내고 해당 서버의 응답을 수신할 수 있습니다.

http 라이브러리는 Node.js의 기본 라이브러리로 제공되며, 웹 서버 및 클라이언트를 작성하는 데 사용됩니다. 하지만 보다 높은 수준의 추상화와 기능을 제공하는 외부 라이브러리인 Express.js 등이 더 흔히 사용되기도 합니다.

 

 

✔️ fileURLToPath(import.meta.url)

이 코드는 ECMAScript 모듈에서 현재 실행 중인 파일의 경로를 가져오는 데 사용됩니다.

  1. import.meta.url: import.meta 객체는 현재 모듈의 정보를 포함하는 객체입니다. 그 중 url 속성은 현재 모듈 파일의 URL을 제공합니다.
  2. fileURLToPath(): 이 함수는 파일 URL을 파일 시스템 경로로 변환합니다. Node.js에서는 파일 경로를 URL로 사용하지 않기 때문에 이러한 변환이 필요합니다.

따라서 import.meta.url을 사용하여 현재 모듈의 URL을 가져와서 fileURLToPath() 함수를 통해 파일 시스템 경로로 변환한 다음, 그 값을 __filename에 할당하여 현재 실행 중인 파일의 경로를 얻을 수 있습니다. 이렇게 얻은 파일 경로는 Node.js에서 파일 시스템 작업 등에 사용될 수 있습니다.

 

 

✔️ path.dirname() 함수

path.dirname() 함수는 주어진 파일 경로의 디렉토리 이름을 반환하는 Node.js의 내장 함수입니다.

예를 들어, path.dirname('/usr/local/bin/file.txt')를 호출하면 '/usr/local/bin'이 반환됩니다. 이 함수는 파일 경로에서 파일 이름을 제외한 디렉토리 경로를 추출할 때 유용하게 사용됩니다.

 

✔️ express.static()

정적 파일 서빙은 웹 애플리케이션에서 정적 파일(이미지, CSS 파일, JavaScript 파일 등)을 클라이언트에게 제공하는 프로세스를 말합니다. 이러한 파일은 서버에서 변경되지 않고 고정된 상태로 제공되며, 서버에서는 클라이언트의 요청에 따라 이러한 파일들을 그대로 전달합니다.

Express의 express.static() 미들웨어를 사용하면 이러한 정적 파일을 쉽게 제공할 수 있습니다. express.static()은 지정된 디렉토리 내의 파일들을 웹 애플리케이션에서 정적 파일로 제공합니다. 위의 코드에서 __dirname은 현재 실행 중인 스크립트의 디렉토리 경로를 나타냅니다. 따라서 app.use(express.static(__dirname))는 현재 스크립트의 디렉토리를 정적 파일의 루트 디렉토리로 설정하여 해당 디렉토리의 모든 정적 파일을 웹 애플리케이션에서 제공하게 됩니다

 

✔️ socket.on, emit 부분

  1. socket.on('setNickname', (name) => { ... }): 이 부분은 클라이언트에서 'setNickname' 이벤트를 수신할 때의 동작을 정의합니다. 클라이언트에서 'setNickname' 이벤트를 보내면 서버는 이를 수신하고 해당 동작을 수행합니다. 즉, 클라이언트에서 닉네임을 설정할 때 실행할 코드를 정의하고 있습니다. 이 코드 블록에서는 받은 닉네임을 변수에 저장하고, 설정된 닉네임을 콘솔에 로그로 출력하며, 모든 클라이언트에게 'setNickname' 이벤트를 발생시켜 닉네임이 설정되었음을 알립니다.
  2. io.emit('setNickname', 알림: 닉네임 설정됨 ${nickname}): 이 부분은 모든 클라이언트에게 'setNickname' 이벤트를 발생시킵니다. 즉, 클라이언트들에게 닉네임이 설정되었다는 알림을 보냅니다. 해당 이벤트에는 알림 메시지가 포함되어 있으며, 이 메시지는 클라이언트에서 화면에 표시될 수 있도록 설정됩니다.

✔️  Set

channels 변수는 새로운 Set 객체를 생성하는 것입니다. Set은 JavaScript의 내장 객체 중 하나로, 고유한 값의 컬렉션을 저장하는 자료 구조입니다. Set은 중복된 값을 허용하지 않으며, 값의 순서를 보장합니다.

여기서는 채널 목록을 저장하기 위해 Set을 사용하고 있습니다. 이를 통해 채널 목록이 중복되지 않고, 채널을 추가하거나 제거할 때 효율적으로 관리할 수 있습니다. Set 객체를 사용하면 채널 목록에 새로운 채널을 추가하거나 중복된 채널을 방지할 수 있습니다.

 

✔️ io.to(channel), sender

여기서 to(channel)은 특정한 방(room)으로 메시지를 보내는 것을 의미합니다. Socket.IO에서 to() 메서드는 클라이언트가 속한 특정 방으로 메시지를 전송하는 데 사용됩니다. 즉, channel 변수에 저장된 방으로 메시지를 전송합니다.

sender는 메시지를 보내는 사람을 나타냅니다. 여기서는 메시지를 보낸 사용자의 닉네임을 나타내는 변수입니다. 이 정보는 메시지를 받는 클라이언트에서 메시지의 송신자를 식별하는 데 사용될 수 있습니다.

 

 

✔️ {sender: nickname, message}

sender와 message는 서로 다른 정보입니다.

  • sender: 메시지를 보낸 사용자의 닉네임을 나타내는 변수입니다.
  • message: 보낸 메시지 내용을 나타내는 변수입니다.

따라서 sender는 누가 메시지를 보냈는지를 나타내고, message는 보낸 메시지 내용 자체를 나타냅니다. 이 두 가지 정보를 함께 전송하여 클라이언트가 메시지를 받을 때 누가 보냈는지와 메시지 내용을 모두 확인할 수 있습니다.

 

✔️ Array.from() 

Array.from() 메서드는 유사 배열 객체나 반복 가능한 객체를 얕게 복사해 새로운 배열 객체를 만듭니다. 즉, 이 메서드는 배열 형태로 변환합니다. 이 메서드를 사용하면 배열이 아닌 객체나 이터러블(iterable)을 배열로 바꿀 수 있습니다.

예를 들어, Set 객체를 Array.from()을 사용하여 배열로 변환할 수 있습니다. Set은 중복을 허용하지 않는 값들의 집합이므로, 이를 배열로 변환하면 중복되지 않은 값들의 배열이 생성됩니다.

 

위 코드에서 Array.from()을 사용하여 Set을 배열로 변환한 것을 볼 수 있습니다.

 

🤍

'코딩 > project' 카테고리의 다른 글

pj, 7일차  (0) 2024.05.09
pj, 6일차  (0) 2024.05.08
pj, 4일차  (0) 2024.05.02
pj, 3일차  (0) 2024.04.30
pj, 2일차  (0) 2024.04.30