From 4a92be75a0aedf248913898c7f403e32e4289a6a Mon Sep 17 00:00:00 2001 From: koomchang Date: Wed, 20 Nov 2024 21:05:35 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20api=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20#158?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/user/user.controller.ts | 7 ++++++- backend/src/user/user.service.ts | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/src/user/user.controller.ts b/backend/src/user/user.controller.ts index 4bbfcb37..01c9638e 100644 --- a/backend/src/user/user.controller.ts +++ b/backend/src/user/user.controller.ts @@ -1,7 +1,12 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Get, Param } from '@nestjs/common'; import { UserService } from './user.service'; @Controller('users') export class UserController { constructor(private readonly userService: UserService) {} + + @Get('/:id') + async getUserInfo(@Param('id') id: number) { + return await this.userService.getUserInfo(id); + } } diff --git a/backend/src/user/user.service.ts b/backend/src/user/user.service.ts index 4278da27..14505301 100644 --- a/backend/src/user/user.service.ts +++ b/backend/src/user/user.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { UserRepository } from './user.repository'; import { CreateUserRequest } from './dto/CreateUserRequest'; +import { UserIconResponse } from '@src/user/dto/UserIconResponse'; @Injectable() export class UserService { @@ -22,4 +23,9 @@ export class UserService { const newUser = await this.userRepository.save(user); return { userId: newUser.id, role: newUser.role }; } + + async getUserInfo(userId: number) { + const user = await this.userRepository.findById(userId); + return UserIconResponse.from(user); + } } From 4456f5bcc295fc733f9c9277d5b7d2a2da063113 Mon Sep 17 00:00:00 2001 From: koomchang Date: Wed, 20 Nov 2024 21:12:08 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=EB=B3=B8?= =?UTF-8?q?=EC=9D=B8=EC=9D=B4=20=EC=95=84=EB=8B=88=EB=A9=B4=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=AC=20=EC=88=98=20=EC=97=86=EB=8F=84=EB=A1=9D=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EB=B6=80=EC=97=AC=20#158?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/CoursePermissionException.ts | 2 +- .../user/exception/UserPermissionException.ts | 11 ++++++++++ .../src/user/guards/UserPermissionGuard.ts | 20 +++++++++++++++++++ backend/src/user/user.controller.ts | 5 ++++- 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 backend/src/user/exception/UserPermissionException.ts create mode 100644 backend/src/user/guards/UserPermissionGuard.ts diff --git a/backend/src/course/exception/CoursePermissionException.ts b/backend/src/course/exception/CoursePermissionException.ts index e19a1b2b..d848939a 100644 --- a/backend/src/course/exception/CoursePermissionException.ts +++ b/backend/src/course/exception/CoursePermissionException.ts @@ -1,4 +1,4 @@ -import { BaseException } from '../../common/exception/BaseException'; +import { BaseException } from '@src/common/exception/BaseException'; import { HttpStatus } from '@nestjs/common'; export class CoursePermissionException extends BaseException { diff --git a/backend/src/user/exception/UserPermissionException.ts b/backend/src/user/exception/UserPermissionException.ts new file mode 100644 index 00000000..145df4fd --- /dev/null +++ b/backend/src/user/exception/UserPermissionException.ts @@ -0,0 +1,11 @@ +import { BaseException } from '@src/common/exception/BaseException'; + +export class UserPermissionException extends BaseException { + constructor(id: number) { + super({ + code: 2001, + message: `id:${id} 유저에 대한 권한이 없습니다.`, + status: 403, + }); + } +} diff --git a/backend/src/user/guards/UserPermissionGuard.ts b/backend/src/user/guards/UserPermissionGuard.ts new file mode 100644 index 00000000..5ceb97ba --- /dev/null +++ b/backend/src/user/guards/UserPermissionGuard.ts @@ -0,0 +1,20 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { UserService } from '@src/user/user.service'; +import { UserPermissionException } from '@src/user/exception/UserPermissionException'; + +@Injectable() +export class UserPermissionGuard implements CanActivate { + constructor(private readonly userService: UserService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const userId = Number(request.params.id); + const requesterId = Number(request.user.userId); + + const user = await this.userService.getUserInfo(userId); + if (user.id !== requesterId) { + throw new UserPermissionException(userId); + } + return true; + } +} diff --git a/backend/src/user/user.controller.ts b/backend/src/user/user.controller.ts index 01c9638e..2db73c62 100644 --- a/backend/src/user/user.controller.ts +++ b/backend/src/user/user.controller.ts @@ -1,11 +1,14 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import { UserService } from './user.service'; +import { JwtAuthGuard } from '@src/auth/JwtAuthGuard'; +import { UserPermissionGuard } from '@src/user/guards/UserPermissionGuard'; @Controller('users') export class UserController { constructor(private readonly userService: UserService) {} @Get('/:id') + @UseGuards(JwtAuthGuard, UserPermissionGuard) async getUserInfo(@Param('id') id: number) { return await this.userService.getUserInfo(id); } From 8fca12bc0b8108ade70f84d3f968246f0b774002 Mon Sep 17 00:00:00 2001 From: koomchang Date: Wed, 20 Nov 2024 21:37:01 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=B3=B8=EC=9D=B8=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=EC=97=90=EB=8A=94=20id=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20=EC=9E=85=EB=A0=A5=20=EB=B0=9B=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20#158?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/exception/UserNotFoundException.ts | 11 ++++++++++ .../user/exception/UserPermissionException.ts | 11 ---------- .../src/user/guards/UserPermissionGuard.ts | 20 ------------------- backend/src/user/user.controller.ts | 12 +++++------ backend/src/user/user.service.ts | 4 ++++ 5 files changed, 21 insertions(+), 37 deletions(-) create mode 100644 backend/src/user/exception/UserNotFoundException.ts delete mode 100644 backend/src/user/exception/UserPermissionException.ts delete mode 100644 backend/src/user/guards/UserPermissionGuard.ts diff --git a/backend/src/user/exception/UserNotFoundException.ts b/backend/src/user/exception/UserNotFoundException.ts new file mode 100644 index 00000000..d2f9c13f --- /dev/null +++ b/backend/src/user/exception/UserNotFoundException.ts @@ -0,0 +1,11 @@ +import { BaseException } from '@src/common/exception/BaseException'; + +export class UserNotFoundException extends BaseException { + constructor(id: number) { + super({ + code: 2001, + message: `id:${id} 사용자를 찾을 수 없습니다.`, + status: 404, + }); + } +} diff --git a/backend/src/user/exception/UserPermissionException.ts b/backend/src/user/exception/UserPermissionException.ts deleted file mode 100644 index 145df4fd..00000000 --- a/backend/src/user/exception/UserPermissionException.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BaseException } from '@src/common/exception/BaseException'; - -export class UserPermissionException extends BaseException { - constructor(id: number) { - super({ - code: 2001, - message: `id:${id} 유저에 대한 권한이 없습니다.`, - status: 403, - }); - } -} diff --git a/backend/src/user/guards/UserPermissionGuard.ts b/backend/src/user/guards/UserPermissionGuard.ts deleted file mode 100644 index 5ceb97ba..00000000 --- a/backend/src/user/guards/UserPermissionGuard.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; -import { UserService } from '@src/user/user.service'; -import { UserPermissionException } from '@src/user/exception/UserPermissionException'; - -@Injectable() -export class UserPermissionGuard implements CanActivate { - constructor(private readonly userService: UserService) {} - - async canActivate(context: ExecutionContext): Promise { - const request = context.switchToHttp().getRequest(); - const userId = Number(request.params.id); - const requesterId = Number(request.user.userId); - - const user = await this.userService.getUserInfo(userId); - if (user.id !== requesterId) { - throw new UserPermissionException(userId); - } - return true; - } -} diff --git a/backend/src/user/user.controller.ts b/backend/src/user/user.controller.ts index 2db73c62..ed99a104 100644 --- a/backend/src/user/user.controller.ts +++ b/backend/src/user/user.controller.ts @@ -1,15 +1,15 @@ -import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { Controller, Get, UseGuards } from '@nestjs/common'; import { UserService } from './user.service'; import { JwtAuthGuard } from '@src/auth/JwtAuthGuard'; -import { UserPermissionGuard } from '@src/user/guards/UserPermissionGuard'; +import { AuthUser } from '@src/auth/AuthUser.decorator'; @Controller('users') export class UserController { constructor(private readonly userService: UserService) {} - @Get('/:id') - @UseGuards(JwtAuthGuard, UserPermissionGuard) - async getUserInfo(@Param('id') id: number) { - return await this.userService.getUserInfo(id); + @Get('/info') + @UseGuards(JwtAuthGuard) + async getUserInfo(@AuthUser() user: AuthUser) { + return await this.userService.getUserInfo(user.userId); } } diff --git a/backend/src/user/user.service.ts b/backend/src/user/user.service.ts index 14505301..61acc2d1 100644 --- a/backend/src/user/user.service.ts +++ b/backend/src/user/user.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { UserRepository } from './user.repository'; import { CreateUserRequest } from './dto/CreateUserRequest'; import { UserIconResponse } from '@src/user/dto/UserIconResponse'; +import { UserNotFoundException } from '@src/user/exception/UserNotFoundException'; @Injectable() export class UserService { @@ -26,6 +27,9 @@ export class UserService { async getUserInfo(userId: number) { const user = await this.userRepository.findById(userId); + if (!user) { + throw new UserNotFoundException(userId); + } return UserIconResponse.from(user); } } From b798f04d290b0d9a92022ed454861d228743b097 Mon Sep 17 00:00:00 2001 From: Soap Date: Wed, 20 Nov 2024 23:48:26 +0900 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../elasticsearch/log_index_template.json | 47 -------- .../kibana/level.painless | 26 ----- .../kibana/log-message.painless | 26 ----- .../kibana/request.painless | 46 -------- .../logstash/logstash.conf | 34 ------ .../ubuntu@dailyroad.site/setup-elk.sh | 110 ------------------ 6 files changed, 289 deletions(-) delete mode 100644 backend/resources/scripts/ubuntu@dailyroad.site/elasticsearch/log_index_template.json delete mode 100644 backend/resources/scripts/ubuntu@dailyroad.site/kibana/level.painless delete mode 100644 backend/resources/scripts/ubuntu@dailyroad.site/kibana/log-message.painless delete mode 100644 backend/resources/scripts/ubuntu@dailyroad.site/kibana/request.painless delete mode 100644 backend/resources/scripts/ubuntu@dailyroad.site/logstash/logstash.conf delete mode 100644 backend/resources/scripts/ubuntu@dailyroad.site/setup-elk.sh diff --git a/backend/resources/scripts/ubuntu@dailyroad.site/elasticsearch/log_index_template.json b/backend/resources/scripts/ubuntu@dailyroad.site/elasticsearch/log_index_template.json deleted file mode 100644 index 519f124e..00000000 --- a/backend/resources/scripts/ubuntu@dailyroad.site/elasticsearch/log_index_template.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "index_patterns": [ - "dailyroad-*" - ], - "template": { - "settings": { - "number_of_shards": 1, - "number_of_replicas": 1 - }, - "mappings": { - "properties": { - "@timestamp": { - "type": "date" - }, - "log_level": { - "type": "keyword" - }, - "log_message": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 256 - } - } - }, - "req": { - "properties": { - "id": { - "type": "integer" - }, - "method": { - "type": "keyword" - }, - "url": { - "type": "keyword" - }, - "headers": { - "type": "object", - "enabled": false - } - } - } - } - } - } -} diff --git a/backend/resources/scripts/ubuntu@dailyroad.site/kibana/level.painless b/backend/resources/scripts/ubuntu@dailyroad.site/kibana/level.painless deleted file mode 100644 index 09217398..00000000 --- a/backend/resources/scripts/ubuntu@dailyroad.site/kibana/level.painless +++ /dev/null @@ -1,26 +0,0 @@ -if (!params['_source'].containsKey('message')) { - emit("no message"); - return; -} - -String message = params['_source']['message']; -if (message == null) { - emit("message is null"); - return; -} - -// "level":" 를 기준으로 값 추출 -int startIndex = message.indexOf('"level":"'); -if (startIndex == -1) { - emit("no msg in message"); - return; -} - -startIndex += 9; // "level":"의 길이만큼 이동 -int endIndex = message.indexOf('"', startIndex); -if (endIndex == -1) { - emit("no end of msg"); - return; -} - -emit(message.substring(startIndex, endIndex)); diff --git a/backend/resources/scripts/ubuntu@dailyroad.site/kibana/log-message.painless b/backend/resources/scripts/ubuntu@dailyroad.site/kibana/log-message.painless deleted file mode 100644 index 824f65dc..00000000 --- a/backend/resources/scripts/ubuntu@dailyroad.site/kibana/log-message.painless +++ /dev/null @@ -1,26 +0,0 @@ -if (!params['_source'].containsKey('message')) { - emit("no message"); - return; -} - -String message = params['_source']['message']; -if (message == null) { - emit("message is null"); - return; -} - -// "msg":" 를 기준으로 값 추출 -int startIndex = message.indexOf('"msg":"'); -if (startIndex == -1) { - emit("no msg in message"); - return; -} - -startIndex += 7; // "msg":"의 길이만큼 이동 -int endIndex = message.indexOf('"', startIndex); -if (endIndex == -1) { - emit("no end of msg"); - return; -} - -emit(message.substring(startIndex, endIndex)); diff --git a/backend/resources/scripts/ubuntu@dailyroad.site/kibana/request.painless b/backend/resources/scripts/ubuntu@dailyroad.site/kibana/request.painless deleted file mode 100644 index bb2dfcf9..00000000 --- a/backend/resources/scripts/ubuntu@dailyroad.site/kibana/request.painless +++ /dev/null @@ -1,46 +0,0 @@ -if (!params['_source'].containsKey('message')) { - emit("no message"); - return; -} - -String message = params['_source']['message']; -if (message == null) { - emit("message is null"); - return; -} - -// "method":" 를 기준으로 method 값 추출 -int methodStartIndex = message.indexOf('"method":"'); -if (methodStartIndex == -1) { - emit("no method in message"); - return; -} - -methodStartIndex += 10; // "method":"의 길이만큼 이동 -int methodEndIndex = message.indexOf('"', methodStartIndex); -if (methodEndIndex == -1) { - emit("no end of method"); - return; -} - -String method = message.substring(methodStartIndex, methodEndIndex); - -// "url":" 를 기준으로 url 값 추출 -int urlStartIndex = message.indexOf('"url":"'); -if (urlStartIndex == -1) { - emit("no url in message"); - return; -} - -urlStartIndex += 7; // "url":"의 길이만큼 이동 -int urlEndIndex = message.indexOf('"', urlStartIndex); -if (urlEndIndex == -1) { - emit("no end of url"); - return; -} - -String url = message.substring(urlStartIndex, urlEndIndex); - -// method와 url을 합쳐 request 생성 -String request = method + " " + url; -emit(request); diff --git a/backend/resources/scripts/ubuntu@dailyroad.site/logstash/logstash.conf b/backend/resources/scripts/ubuntu@dailyroad.site/logstash/logstash.conf deleted file mode 100644 index d16ffdd9..00000000 --- a/backend/resources/scripts/ubuntu@dailyroad.site/logstash/logstash.conf +++ /dev/null @@ -1,34 +0,0 @@ -input { - beats { - port => 5044 - } - - tcp { - port => 50000 - codec => json_lines # JSON 형태의 데이터를 처리 - } -} - -filter { - json { - source => "message" - target => "parsed_message" - } - - mutate { - rename => { "[parsed_message][level]" => "log_level" } - rename => { "[parsed_message][msg]" => "log_message" } - rename => { "[parsed_message][time]" => "@timestamp" } - - remove_field => ["message", "parsed_message"] - } -} - -output { - elasticsearch { - index => "%{[@metadata][service_name]}-%{+YYYY.MM.dd}" - hosts => "elasticsearch:9200" - user => "logstash_internal" - password => "${LOGSTASH_INTERNAL_PASSWORD}" - } -} diff --git a/backend/resources/scripts/ubuntu@dailyroad.site/setup-elk.sh b/backend/resources/scripts/ubuntu@dailyroad.site/setup-elk.sh deleted file mode 100644 index a635086c..00000000 --- a/backend/resources/scripts/ubuntu@dailyroad.site/setup-elk.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/bash - -# ELK 스택 설정 초기화 스크립트 - -# 환경 설정 -export ELASTIC_PASSWORD="example" # 변경 필요 -export LOGSTASH_PASSWORD="example" -export KIBANA_PASSWORD="example" -export ELASTIC_VERSION="8.15.3" - -export SERVICE_NAME="dailyroad" - -# 기본 경로 설정 -SETUP_DIR=$(dirname "$(readlink -f "$0")") -DOCKER_ELK_DIR="$SETUP_DIR/../docker-elk" # ELK 스택 클론 경로 - -LOGSTASH_CONFIG="$SETUP_DIR/logstash/logstash.conf" -TEMPLATE_FILE="$SETUP_DIR/elasticsearch/log_index_template.json" -KIBANA_SCRIPTS_DIR="$SETUP_DIR/kibana" - -# 필요 패키지 업데이트 및 설치 -echo "Updating packages..." -sudo apt update -y && sudo apt upgrade -y -echo "Installing Docker and Docker Compose..." -sudo apt install -y docker.io docker-compose - -# Docker와 Docker Compose 설치 확인 -if ! command -v docker &> /dev/null || ! command -v docker-compose &> /dev/null; then - echo "Docker or Docker Compose installation failed. Please check your setup." - exit 1 -fi - -# Docker-ELK 클론 또는 디렉토리로 이동 -if [ ! -d "$DOCKER_ELK_DIR" ]; then - echo "Cloning ELK stack repository to $DOCKER_ELK_DIR..." - git clone https://github.com/deviantony/docker-elk.git "$DOCKER_ELK_DIR" -fi - -cd "$DOCKER_ELK_DIR" || exit - -# .env 파일에서 버전 및 비밀번호 설정 -echo "Setting up environment file..." -sed -i "s/^ELASTIC_VERSION=.*/ELASTIC_VERSION=${ELASTIC_VERSION}/" .env -sed -i "s/^ELASTIC_PASSWORD=.*/ELASTIC_PASSWORD=${ELASTIC_PASSWORD}/" .env -sed -i "s/^LOGSTASH_PASSWORD=.*/LOGSTASH_PASSWORD=${LOGSTASH_PASSWORD}/" .env -sed -i "s/^KIBANA_PASSWORD=.*/KIBANA_PASSWORD=${KIBANA_PASSWORD}/" .env -sed -i "s/^SERVICE_NAME=.*/SERVICE_NAME=${SERVICE_NAME}/" .env -sed -i "s/'//g" .env # 작은 따옴표 제거 - -# Logstash 구성 복사 -if [ -f "$LOGSTASH_CONFIG" ]; then - echo "Copying Logstash configuration..." - cp "$LOGSTASH_CONFIG" logstash/pipeline/logstash.conf -else - echo "Logstash configuration file not found: $LOGSTASH_CONFIG" - exit 1 -fi - -# Elasticsearch 인덱스 템플릿 등록 -if [ -f "$TEMPLATE_FILE" ]; then - echo "Registering Elasticsearch index template..." - curl -u elastic:"${ELASTIC_PASSWORD}" -X PUT "http://localhost:9200/_index_template/${SERVICE_NAME}-template" \ - -H "Content-Type: application/json" \ - -d @"$TEMPLATE_FILE" -else - echo "Index template file not found: $TEMPLATE_FILE" - exit 1 -fi - -# Kibana Painless 스크립트 설정 -if [ -d "$KIBANA_SCRIPTS_DIR" ]; then - for SCRIPT in "$KIBANA_SCRIPTS_DIR"/*.painless; do - SCRIPT_NAME=$(basename "$SCRIPT" .painless) - echo "Adding Kibana Painless script: $SCRIPT_NAME" - curl -u elastic:"${ELASTIC_PASSWORD}" -X POST "http://localhost:9200/_scripts/$SCRIPT_NAME" \ - -H "Content-Type: application/json" \ - -d @"$SCRIPT" - done -else - echo "Kibana scripts directory not found: $KIBANA_SCRIPTS_DIR" -fi - -# 초기 사용자 및 권한 설정 -echo "Setting up initial users and permissions..." -docker-compose up setup - -# ELK 스택 시작 -echo "Starting ELK stack..." -docker-compose up -d - -# Kibana 초기화 대기 -echo "Waiting for Kibana to initialize..." -sleep 60 - -# Logstash 사용자에게 writer 권한 부여 -echo "Assigning writer role to Logstash user..." -curl -u elastic:"${ELASTIC_PASSWORD}" -X PUT "http://localhost:9200/_security/role/logstash_writer" -H "Content-Type: application/json" -d "{ - \"cluster\": [\"manage_index_templates\", \"monitor\", \"manage_ilm\"], - \"indices\": [ - { - \"names\": [\"${SERVICE_NAME}-*\"], - \"privileges\": [\"write\", \"create_index\"] - } - ] -}" - -# ELK 스택 상태 확인 -docker-compose ps - -echo "ELK stack setup completed. Access Kibana at http://localhost:5601 with the default user credentials."