From daf29a975f7f4f1fada7b8bf5bd2a045b6a5c536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=90=EC=8A=B9=EC=9A=B0?= Date: Fri, 16 Dec 2022 21:09:50 +0900 Subject: [PATCH] fix: fix AccessToken --- backend-api/.env.sample.dev | 10 - backend-api/Dockerfile.dev | 34 +-- backend-api/db-init-files/drop.sql | 1 + backend-api/docker-compose.yml | 96 ++++----- backend-api/package-lock.json | 200 ++++++++++++------ backend-api/package.json | 2 +- backend-api/src/app.module.ts | 2 +- backend-api/src/auth/auth.service.ts | 4 +- backend-api/src/auth/guards/jwt-auth.guard.ts | 41 ++-- .../src/auth/guards/local-auth.guard.ts | 44 ++-- .../src/dashboard/dashboard.controller.ts | 5 + .../src/database/database.controller.ts | 2 + backend-api/src/login/login.controller.ts | 2 - .../src/share-url/share-url.controller.ts | 7 +- .../src/share-url/share-url.service.ts | 27 ++- backend-api/src/user/user.controller.ts | 1 + backend-api/src/utils/swagger.ts | 1 + backend-api/swagger-spec.json | 2 +- 18 files changed, 268 insertions(+), 213 deletions(-) delete mode 100644 backend-api/.env.sample.dev create mode 100644 backend-api/db-init-files/drop.sql diff --git a/backend-api/.env.sample.dev b/backend-api/.env.sample.dev deleted file mode 100644 index d0ed9e8..0000000 --- a/backend-api/.env.sample.dev +++ /dev/null @@ -1,10 +0,0 @@ -DB_TYPE='mysql' -DB_HOST='localhost' -DB_PORT=3306 -DB_USERNAME='vanillameta' -DB_PASSWORD='pw' -DB_NAME='vanillameta' - -CORS_ORIGIN='*' - -api propertyfile fix \ No newline at end of file diff --git a/backend-api/Dockerfile.dev b/backend-api/Dockerfile.dev index 159b6ac..722f991 100644 --- a/backend-api/Dockerfile.dev +++ b/backend-api/Dockerfile.dev @@ -1,30 +1,14 @@ -FROM ubuntu:18.04 +FROM ubuntu:14.04 +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN echo "mysql-server mysql-server/root_password password" | debconf-set-selections +RUN echo "mysql-server mysql-server/root_password_again password" | debconf-set-selections +RUN apt-get install -y mysql-server -RUN apt-get -qq update -RUN apt-get -qq upgrade --yes -RUN apt-get -qq install curl --yes -RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - -RUN apt-get -qq install nodejs --yes +ADD ./db-init-files /docker-entrypoint-initdb.d -RUN apt-get install chromium-browser --yes +EXPOSE 3306 -RUN useradd -ms /bin/bash frog -USER frog -WORKDIR /home/frog - -COPY package*.json ./ - -RUN npm install - -COPY . . -COPY tsconfig.json . -COPY tsconfig.build.json . - - -RUN npm run build - -EXPOSE 3000 - -CMD ["npm", "run", "start:prod"] +ENTRYPOINT mysqld \ No newline at end of file diff --git a/backend-api/db-init-files/drop.sql b/backend-api/db-init-files/drop.sql new file mode 100644 index 0000000..06829a6 --- /dev/null +++ b/backend-api/db-init-files/drop.sql @@ -0,0 +1 @@ +truncate table user \ No newline at end of file diff --git a/backend-api/docker-compose.yml b/backend-api/docker-compose.yml index 00ad759..a3a0ff7 100644 --- a/backend-api/docker-compose.yml +++ b/backend-api/docker-compose.yml @@ -9,40 +9,38 @@ services: networks: - vanillameta depends_on: -# - mysql -# - pg - - mssql + - mysql links: -# - "mysql:mysqldb" + - "mysql" # - "pg" - - mssql env_file: - - .env - .env.dev + environment: + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} restart: always -# mysql: -# container_name: vanillameta_mysql -# image: mysql -# ports: -# - "3306:3306" -# networks: -# - vanillameta -# -# environment: -# MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} -# MYSQL_CHARSET: utf8mb4 -# MYSQL_DATABASE: ${DB_NAME} -# MYSQL_USER: ${DB_USERNAME} -# MYSQL_PASSWORD: ${DB_PASSWORD} -# TZ: Asia/Seoul -# restart: always -# command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci -# --default_authentication_plugin=mysql_native_password -# -# volumes: -# - mysql:/var/lib/mysql -# - ./db/conf.d:/etc/mysql/conf.d + mysql: + container_name: vanillameta_mysql + image: mysql + ports: + - "3306:3306" + networks: + - vanillameta + environment: + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + MYSQL_CHARSET: utf8mb4 + MYSQL_DATABASE: ${DB_NAME} + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} + TZ: Asia/Seoul + restart: always + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + --default_authentication_plugin=mysql_native_password + + volumes: + - mysql:/var/lib/mysql + - ./db/conf.d:/etc/mysql/conf.d + - ./mysql/initdb.d:/docker-entrypoint-initdb.d # mariadb: # container_name: vanillameta_mariadb @@ -106,28 +104,28 @@ services: # - ./db/conf.d:/etc/porstgressql/data +# +# mssql: +# container_name: vanillameta_mssql +# image: mcr.microsoft.com/mssql/server:2019-latest +# user: frog +# ports: +# - "1433:1433" +# networks: +# - vanillameta +# environment: +# ACCEPT_EULA: "Y" +# MSSQL_SA_PASSWORD: 'Qkslffk@123123' +# +# +# +# restart: always +# +# volumes: +# - ./sqlvolume:/home/frog +# - mssql: - container_name: vanillameta_mssql - image: mcr.microsoft.com/mssql/server:2019-latest - user: frog - ports: - - "1433:1433" - networks: - - vanillameta - environment: - ACCEPT_EULA: "Y" - MSSQL_SA_PASSWORD: 'Qkslffk@123123' - - - - restart: always - - volumes: - - ./sqlvolume:/home/frog - - - +# volumes: mysql: networks: diff --git a/backend-api/package-lock.json b/backend-api/package-lock.json index c38d911..e219ce8 100644 --- a/backend-api/package-lock.json +++ b/backend-api/package-lock.json @@ -8878,6 +8878,106 @@ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true }, + "fastify-plugin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.1.tgz", + "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" + }, + "fastify-static": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.7.0.tgz", + "integrity": "sha512-zZhCfJv/hkmud2qhWqpU3K9XVAuy3+IV8Tp9BC5J5U+GyA2XwoB6h8lh9GqpEIqdXOw01WyWQllV7dOWVyAlXg==", + "requires": { + "fastify-static-deprecated": "npm:fastify-static@4.6.1", + "process-warning": "^1.0.0" + } + }, + "fastify-static-deprecated": { + "version": "npm:fastify-static@4.6.1", + "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.6.1.tgz", + "integrity": "sha512-vy7N28U4AMhuOim12ZZWHulEE6OQKtzZbHgiB8Zj4llUuUQXPka0WHAQI3njm1jTCx4W6fixUHfpITxweMtAIA==", + "requires": { + "content-disposition": "^0.5.3", + "encoding-negotiator": "^2.0.1", + "fastify-plugin": "^3.0.0", + "glob": "^7.1.4", + "p-limit": "^3.1.0", + "readable-stream": "^3.4.0", + "send": "^0.17.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "requires": { + "ee-first": "1.1.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" + } + } + }, "fastify-swagger": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/fastify-swagger/-/fastify-swagger-5.2.0.tgz", @@ -8908,71 +9008,6 @@ "json-schema-resolver": "^1.3.0", "openapi-types": "^10.0.0", "rfdc": "^1.3.0" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "fastify-plugin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.1.tgz", - "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" - }, - "fastify-static": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.7.0.tgz", - "integrity": "sha512-zZhCfJv/hkmud2qhWqpU3K9XVAuy3+IV8Tp9BC5J5U+GyA2XwoB6h8lh9GqpEIqdXOw01WyWQllV7dOWVyAlXg==", - "requires": { - "fastify-static-deprecated": "npm:fastify-static@4.6.1", - "process-warning": "^1.0.0" - }, - "dependencies": { - "fastify-static-deprecated": { - "version": "npm:fastify-static@4.6.1", - "resolved": "https://registry.npmjs.org/fastify-static/-/fastify-static-4.6.1.tgz", - "integrity": "sha512-vy7N28U4AMhuOim12ZZWHulEE6OQKtzZbHgiB8Zj4llUuUQXPka0WHAQI3njm1jTCx4W6fixUHfpITxweMtAIA==", - "requires": { - "content-disposition": "^0.5.3", - "encoding-negotiator": "^2.0.1", - "fastify-plugin": "^3.0.0", - "glob": "^7.1.4", - "p-limit": "^3.1.0", - "readable-stream": "^3.4.0", - "send": "^0.17.1" - } - } - } - }, - "json-schema-resolver": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-1.3.0.tgz", - "integrity": "sha512-EX7W1r8aZ/T3j8GbbBxPXi60bnsELfT90OiA1QrbGMvwzVSbyMNOAzvMFcFb8m7gKCXZLJpGe+cJOvWgoFl29A==", - "requires": { - "debug": "^4.1.1", - "rfdc": "^1.1.4", - "uri-js": "^4.2.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "openapi-types": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-10.0.0.tgz", - "integrity": "sha512-Y8xOCT2eiKGYDzMW9R4x5cmfc3vGaaI4EL2pwhDmodWw1HlK18YcZ4uJxc7Rdp7/gGzAygzH9SXr6GKYIXbRcQ==" - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" - } } }, "http-errors": { @@ -11722,6 +11757,31 @@ } } }, + "json-schema-resolver": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-1.3.0.tgz", + "integrity": "sha512-EX7W1r8aZ/T3j8GbbBxPXi60bnsELfT90OiA1QrbGMvwzVSbyMNOAzvMFcFb8m7gKCXZLJpGe+cJOvWgoFl29A==", + "requires": { + "debug": "^4.1.1", + "rfdc": "^1.1.4", + "uri-js": "^4.2.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -13541,6 +13601,11 @@ "is-wsl": "^2.1.1" } }, + "openapi-types": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-10.0.0.tgz", + "integrity": "sha512-Y8xOCT2eiKGYDzMW9R4x5cmfc3vGaaI4EL2pwhDmodWw1HlK18YcZ4uJxc7Rdp7/gGzAygzH9SXr6GKYIXbRcQ==" + }, "optional": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", @@ -15164,6 +15229,11 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", diff --git a/backend-api/package.json b/backend-api/package.json index 4e8487e..a44416a 100644 --- a/backend-api/package.json +++ b/backend-api/package.json @@ -90,7 +90,7 @@ "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", - "eslint": "^7.31.0", + "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "jest": "28.1.3", diff --git a/backend-api/src/app.module.ts b/backend-api/src/app.module.ts index 1c9051b..8bbebad 100644 --- a/backend-api/src/app.module.ts +++ b/backend-api/src/app.module.ts @@ -32,7 +32,7 @@ import { ShareUrlModule } from './share-url/share-url.module'; autoLoadEntities: true, entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: false, - logging: process.env.NODE_ENV == 'prod', + logging: process.env.NODE_ENV == 'dev', retryAttempts: 1, }), DatabaseModule, diff --git a/backend-api/src/auth/auth.service.ts b/backend-api/src/auth/auth.service.ts index beb8659..bf81bf7 100644 --- a/backend-api/src/auth/auth.service.ts +++ b/backend-api/src/auth/auth.service.ts @@ -24,7 +24,7 @@ export class AuthService { { accessKeyData }, { secret: process.env.ACCESS_SECRET, - expiresIn: `10800s`, + expiresIn: `21600s`, }, ); return accessToken; @@ -56,7 +56,7 @@ export class AuthService { }; const refreshToken = await this.jwtService.sign( { refreshKeyData }, - { secret: process.env.REFRESH_SECRET, expiresIn: '212600s' }, + { secret: process.env.REFRESH_SECRET, expiresIn: '43200s' }, ); return refreshToken; // accesstoken이 없을때 diff --git a/backend-api/src/auth/guards/jwt-auth.guard.ts b/backend-api/src/auth/guards/jwt-auth.guard.ts index 1b7a5eb..ee6cd71 100644 --- a/backend-api/src/auth/guards/jwt-auth.guard.ts +++ b/backend-api/src/auth/guards/jwt-auth.guard.ts @@ -15,7 +15,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') { throw new HttpException('accessTokenExpired', HttpStatus.UNAUTHORIZED); } if (authorization !== undefined) { - const token = authorization.replace('Bearer ', ''); //authorization-url + const token = authorization.replace('Bearer ', ''); //authorization const boolean = true; // 일반 대시보드일시 const userInfo = await this.validate(token, boolean); if (userInfo) request.user = userInfo; @@ -30,25 +30,28 @@ export class JwtAuthGuard extends AuthGuard('jwt') { } async validate(payload: any, accessPath: boolean) { - // try { if (accessPath === true) { - const secretKey = process.env.ACCESS_SECRET; - const verify = await this.jwtService.verify(payload, { - secret: secretKey, - }); - return verify; + try { + const secretKey = process.env.ACCESS_SECRET; + const verify = await this.jwtService.verify(payload, { + secret: secretKey, + }); + return verify; + } catch { + throw new HttpException('accessTokenExpired', HttpStatus.UNAUTHORIZED); + } + if (accessPath === false) { + try { + // const secretUrlKey = process.env.URL_ACCESS_SECRET; + const secretUrlKey = 'test1234'; + const verifyUrl = await this.jwtService.verify(payload, { + secret: secretUrlKey, + }); + return verifyUrl; + } catch { + throw new HttpException('accessTokenExpired', HttpStatus.UNAUTHORIZED); + } + } } - if (accessPath === false) { - // const secretUrlKey = process.env.URL_ACCESS_SECRET; - const secretUrlKey = 'test1234'; - const verifyUrl = await this.jwtService.verify(payload, { - secret: secretUrlKey, - }); - return verifyUrl; - } - throw new HttpException('accessTokenExpired', HttpStatus.UNAUTHORIZED); } - // } catch { - // throw new HttpException('accessTokenExpired', HttpStatus.UNAUTHORIZED); - // }} } diff --git a/backend-api/src/auth/guards/local-auth.guard.ts b/backend-api/src/auth/guards/local-auth.guard.ts index 15bb941..85d516c 100644 --- a/backend-api/src/auth/guards/local-auth.guard.ts +++ b/backend-api/src/auth/guards/local-auth.guard.ts @@ -4,28 +4,28 @@ import { AuthGuard } from '@nestjs/passport'; @Injectable() export class LocalAuthGuard extends AuthGuard('local') { - constructor(private jwtService: JwtService) { - super(); - } - async canActivate(context: ExecutionContext): Promise { - const request = context.switchToHttp().getRequest(); - const { cookie } = request.headers; - if (cookie === undefined) { - throw new HttpException('Unathorization', HttpStatus.UNAUTHORIZED); - } - const token = cookie.replace('Bearer ', '').split('=')[1]; - const userInfo = await this.validate(token); - if (userInfo) request.user = userInfo; - return !!userInfo; + constructor(private jwtService: JwtService) { + super(); + } + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const { cookie } = request.headers; + if (cookie === undefined) { + throw new HttpException('Unathorization', HttpStatus.UNAUTHORIZED); } + const token = cookie.replace('Bearer ', '').split('=')[1]; + const userInfo = await this.validate(token); + if (userInfo) request.user = userInfo; + return true; + } - async validate(payload: any) { - try { - const secretKey = process.env.REFRESH_SECRET; - const verify = await this.jwtService.verify(payload, { secret: secretKey }); - return verify - } catch { - throw new HttpException('Unathorization', HttpStatus.UNAUTHORIZED); - } + async validate(payload: any) { + try { + const secretKey = process.env.REFRESH_SECRET; + const verify = await this.jwtService.verify(payload, { secret: secretKey }); + return verify; + } catch { + throw new HttpException('Unathorization', HttpStatus.UNAUTHORIZED); } -} \ No newline at end of file + } +} diff --git a/backend-api/src/dashboard/dashboard.controller.ts b/backend-api/src/dashboard/dashboard.controller.ts index 0847a8e..4a8e8a6 100644 --- a/backend-api/src/dashboard/dashboard.controller.ts +++ b/backend-api/src/dashboard/dashboard.controller.ts @@ -11,6 +11,7 @@ export class DashboardController { @UseGuards(JwtAuthGuard) @Post() + @ApiBearerAuth('AccessKey') create(@Body() createDashboardDto: CreateDashboardDto, @Req() req) { const { accessKeyData } = req.user; return this.dashboardService.create(createDashboardDto, accessKeyData.id); @@ -18,6 +19,7 @@ export class DashboardController { @UseGuards(JwtAuthGuard) @Get() + @ApiBearerAuth('AccessKey') findAll(@Req() req) { const { accessKeyData } = req.user; return this.dashboardService.findAll(accessKeyData.id); @@ -25,18 +27,21 @@ export class DashboardController { @UseGuards(JwtAuthGuard) @Get(':id') + @ApiBearerAuth('AccessKey') findOne(@Param('id') id: string) { return this.dashboardService.findOne(+id); } @UseGuards(JwtAuthGuard) @Put(':id') + @ApiBearerAuth('AccessKey') update(@Param('id') id: string, @Body() updateDashboardDto: UpdateDashboardDto) { return this.dashboardService.update(+id, updateDashboardDto); } @UseGuards(JwtAuthGuard) @Delete(':id') + @ApiBearerAuth('AccessKey') remove(@Param('id') id: string) { return this.dashboardService.remove(+id); } diff --git a/backend-api/src/database/database.controller.ts b/backend-api/src/database/database.controller.ts index 6f57580..115a43a 100644 --- a/backend-api/src/database/database.controller.ts +++ b/backend-api/src/database/database.controller.ts @@ -17,8 +17,10 @@ import { QueryExecuteDto } from './dto/query-execute.dto'; import { ConnectionService } from '../connection/connection.service'; import { DatasetType } from '../common/enum/dataset-type.enum'; import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; +import { ApiTags } from '@nestjs/swagger'; @Controller('database') +@ApiTags('dashboard') export class DatabaseController { constructor( private readonly databaseService: DatabaseService, diff --git a/backend-api/src/login/login.controller.ts b/backend-api/src/login/login.controller.ts index 092cf19..88ab581 100644 --- a/backend-api/src/login/login.controller.ts +++ b/backend-api/src/login/login.controller.ts @@ -15,10 +15,8 @@ import { } from '@nestjs/common'; import { LoginService } from './login.service'; import { CreateLoginDto } from './dto/create-login.dto'; -import { UpdateLoginDto } from './dto/update-login.dto'; import { LoginUserDto } from '../login/dto/login-user.dto'; import { AuthService } from 'src/auth/auth.service'; -import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; import { LocalAuthGuard } from 'src/auth/guards/local-auth.guard'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; diff --git a/backend-api/src/share-url/share-url.controller.ts b/backend-api/src/share-url/share-url.controller.ts index f542f58..8dba30b 100644 --- a/backend-api/src/share-url/share-url.controller.ts +++ b/backend-api/src/share-url/share-url.controller.ts @@ -2,7 +2,7 @@ import { Controller, Get, Post, Body, Param, UseGuards, Req } from '@nestjs/comm import { ShareUrlService } from './share-url.service'; import { ShareUrlOnDto } from './dto/create-share-url.dto'; import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; @Controller('share-url') @ApiTags('url공유화 API') @@ -12,6 +12,7 @@ export class ShareUrlController { @UseGuards(JwtAuthGuard) @Post('share-on/:dashboardId') @ApiOperation({ summary: '공유 url On' }) + @ApiBearerAuth('AccessKey') checkShareUrlOn(@Req() req, @Param() params, @Body() shareUrlOnDto: ShareUrlOnDto) { const { userId } = req.user.accessKeyData; const { dashboardId } = params; @@ -21,16 +22,18 @@ export class ShareUrlController { @UseGuards(JwtAuthGuard) @Post('share-off/:dashboardId') @ApiOperation({ summary: '공유 url Off' }) + @ApiBearerAuth('AccessKey') checkShareUrlOff(@Req() req, @Param() params, @Body() shareUrlOnDto: ShareUrlOnDto) { const { userId } = req.user.accessKeyData; const { dashboardId } = params; - return this.shareUrlService.checkShareUrlOff(userId, dashboardId, shareUrlOnDto); + return this.shareUrlService.checkShareUrlOff(userId, dashboardId); } @Get('share-dashboard/:uuid') @ApiOperation({ summary: '공유 url 접속' }) async shareDashboardInfo(@Param() param) { const { uuid } = param; + console.log(uuid); return await this.shareUrlService.shareDashboardInfo(uuid); } } diff --git a/backend-api/src/share-url/share-url.service.ts b/backend-api/src/share-url/share-url.service.ts index dee4750..b2d26f5 100644 --- a/backend-api/src/share-url/share-url.service.ts +++ b/backend-api/src/share-url/share-url.service.ts @@ -6,7 +6,6 @@ import { User } from '../user/entities/user.entity.js'; import { Repository } from 'typeorm'; import { YesNo } from '../common/enum/yn.enum.js'; import { ShareUrlOnDto } from './dto/create-share-url.dto'; -import { UpdateShareUrlDto } from './dto/update-share-url.dto'; import { DashboardService } from '../dashboard/dashboard.service.js'; import { DashboardShare } from '../dashboard/entities/dashboard_share.js'; @@ -42,7 +41,7 @@ export class ShareUrlService { } // 공유기능 on시 공유토큰과 endDate를 저장 - async checkShareUrlOff(userId: string, dashboardId: number, shareUrlOnDto: ShareUrlOnDto) { + async checkShareUrlOff(userId: string, dashboardId: number) { const findUser = await this.userRepository.findOne({ where: { userId: userId } }); if (!findUser) { return 'not exist user'; @@ -61,21 +60,21 @@ export class ShareUrlService { // 공유기능 off시 쉐어토큰, endDate을 없애고 사용가능여부를 N으로 저장 async shareDashboardInfo(uuid: string) { - const findDashboardShareUrl = await this.dashboardShareRepository.findOne({ - where: { uuid: uuid }, - }); - const findDashboard = await this.dashboardRepository.findOne({ - where: { shareId: findDashboardShareUrl.id }, - }); + let findDashboard = null; + let findDashboardShareUrl = null; + try { + findDashboardShareUrl = await this.dashboardShareRepository.findOne({ + where: { uuid: uuid }, + }); + findDashboard = await this.dashboardRepository.findOne({ + where: { shareId: findDashboardShareUrl.id }, + }); + } catch { + throw new HttpException({ message: 'not exist share dashboard' }, HttpStatus.NOT_FOUND); + } const today = `${new Date().getFullYear()}-${new Date().getMonth() + 1}-${ new Date().getDate() - 1 }`; - if (!findDashboardShareUrl) { - throw new HttpException({ message: 'not exist uuid' }, HttpStatus.NOT_FOUND); - } - if (!findDashboard) { - throw new HttpException({ message: 'not exist share dashboard' }, HttpStatus.NOT_FOUND); - } if (new Date(today) > findDashboardShareUrl.endDate) { throw new HttpException({ message: 'expired date' }, HttpStatus.UNAUTHORIZED); } diff --git a/backend-api/src/user/user.controller.ts b/backend-api/src/user/user.controller.ts index 20bc90d..030a0d0 100644 --- a/backend-api/src/user/user.controller.ts +++ b/backend-api/src/user/user.controller.ts @@ -36,6 +36,7 @@ export class UserController { @UseGuards(JwtAuthGuard) @Delete('delete-account') @ApiOperation({ summary: ' 해당유저 삭제 ' }) + @ApiBearerAuth('AccessKey') deleteUser(@Req() req, @Body() createUserDto: CreateUserDto) { const { userId } = req.user.accessKeyData; const { password } = createUserDto; diff --git a/backend-api/src/utils/swagger.ts b/backend-api/src/utils/swagger.ts index b062d02..d9141be 100644 --- a/backend-api/src/utils/swagger.ts +++ b/backend-api/src/utils/swagger.ts @@ -27,6 +27,7 @@ export function setupSwagger(app: INestApplication): void { .addTag('유저 API') .addTag('url공유화 API') .addTag('로그인 관련 API') + .addTag('dashboard') .build(); const document = SwaggerModule.createDocument(app, options); diff --git a/backend-api/swagger-spec.json b/backend-api/swagger-spec.json index 8df0928..b4629b3 100644 --- a/backend-api/swagger-spec.json +++ b/backend-api/swagger-spec.json @@ -1 +1 @@ -{"openapi":"3.0.0","paths":{"/":{},"/database/type":{},"/database/data":{},"/database/info/{id}":{},"/database":{},"/database/test":{},"/database/execute":{},"/database/{id}":{},"/dataset":{},"/dataset/{id}":{},"/widget":{},"/widget/{id}":{},"/dashboard":{},"/dashboard/{id}":{},"/template":{},"/template/{id}":{},"/template/recommend":{},"/template/dashboard":{},"/component/seed":{},"/component":{},"/component/{id}":{},"/user/userinfo":{"get":{"operationId":"UserController_findOne","summary":"해당유저정보 가져오기","parameters":[],"responses":{"200":{"description":""}},"tags":["유저 API"],"security":[{"AccessKey":[]}]}},"/user/change-info":{"patch":{"operationId":"UserController_updateUsername","summary":"유저정보 수정","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":""}},"tags":["유저 API"],"security":[{"AccessKey":[]}]}},"/user/delete-account":{"delete":{"operationId":"UserController_deleteUser","summary":" 해당유저 삭제 ","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"200":{"description":""}},"tags":["유저 API"]}},"/user/get-access-token":{"post":{"operationId":"UserController_reissuanceAccessToken","summary":"AccessToken 재 발급","parameters":[],"responses":{"201":{"description":""}},"tags":["유저 API"]}},"/user/get-dashboard":{"get":{"operationId":"UserController_findDashboardId","summary":"해당유정의 대시보드 목록 가져오기","parameters":[],"responses":{"200":{"description":""}},"tags":["유저 API"],"security":[{"AccessKey":[]}]}},"/login/signin":{"post":{"operationId":"LoginController_logIn","summary":" 로그인 ","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginUserDto"}}}},"responses":{"201":{"description":""}},"tags":["로그인 관련 API"]}},"/login/signup":{"post":{"operationId":"LoginController_create","summary":"회원가입","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLoginDto"}}}},"responses":{"201":{"description":""}},"tags":["로그인 관련 API"]}},"/login/signout":{"post":{"operationId":"LoginController_signOut","summary":"로그아웃","parameters":[],"responses":{"201":{"description":""}},"tags":["로그인 관련 API"]}},"/share-url/share-on/{dashboardId}":{"post":{"operationId":"ShareUrlController_checkShareUrlOn","summary":"공유 url On","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareUrlOnDto"}}}},"responses":{"201":{"description":""}},"tags":["url공유화 API"]}},"/share-url/share-off/{dashboardId}":{"post":{"operationId":"ShareUrlController_checkShareUrlOff","summary":"공유 url Off","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareUrlOnDto"}}}},"responses":{"201":{"description":""}},"tags":["url공유화 API"]}},"/share-url/share-dashboard/{uuid}":{"get":{"operationId":"ShareUrlController_shareDashboardInfo","summary":"공유 url 접속","parameters":[],"responses":{"200":{"description":""}},"tags":["url공유화 API"]}}},"info":{"title":"NestJS Study API Docs","description":"NestJS Study API description","version":"1.0.0","contact":{}},"tags":[{"name":"유저 API","description":""},{"name":"url공유화 API","description":""},{"name":"로그인 관련 API","description":""}],"servers":[],"components":{"securitySchemes":{"AccessKey":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"Enter jwt token","in":"header"}},"schemas":{"CreateDatabaseDto":{"type":"object","properties":{"name":{"type":"string","example":"mysql 데이터베이스","description":"데이터베이스 이름"},"description":{"type":"string","example":"상세 내용","description":"데이터베이스 상세 내용"},"connectionConfig":{"type":"string","example":"{}","description":"설정 JSON 상세"},"engine":{"type":"string","example":"mysql","description":"데이터베이스 엔진"},"type":{"type":"string","example":"mysql","description":"데이터베이스 구분"},"timezone":{"type":"string","example":"Asia/Seoul","description":"서비스 타임존"}},"required":["name","description","connectionConfig","engine","type","timezone"]},"QueryExecuteDto":{"type":"object","properties":{"id":{"type":"number","example":"1","description":"database id"},"query":{"type":"string","example":"select * from sample_table1","description":"실행 쿼리"}},"required":["id","query"]},"UpdateDatabaseDto":{"type":"object","properties":{}},"CreateDatasetDto":{"type":"object","properties":{}},"UpdateDatasetDto":{"type":"object","properties":{}},"CreateWidgetDto":{"type":"object","properties":{}},"UpdateWidgetDto":{"type":"object","properties":{}},"CreateDashboardDto":{"type":"object","properties":{}},"UpdateDashboardDto":{"type":"object","properties":{}},"CreateTemplateDto":{"type":"object","properties":{}},"UpdateTemplateDto":{"type":"object","properties":{}},"CreateComponentDto":{"type":"object","properties":{}},"UpdateComponentDto":{"type":"object","properties":{}},"UpdateUserDto":{"type":"object","properties":{"userId":{"type":"string","description":"유저Id"},"password":{"type":"string","description":"유저password"},"email":{"type":"string","description":"유저email"}}},"CreateUserDto":{"type":"object","properties":{"userId":{"type":"string","description":"유저Id"},"password":{"type":"string","description":"유저password"},"email":{"type":"string","description":"유저email"}},"required":["userId","password","email"]},"LoginUserDto":{"type":"object","properties":{"userId":{"type":"string","description":"유저Id"},"password":{"type":"string","description":"유저password"}},"required":["userId","password"]},"CreateLoginDto":{"type":"object","properties":{}},"ShareUrlOnDto":{"type":"object","properties":{"userId":{"type":"string","description":"유저Id"},"endDate":{"type":"string","description":"공유 url 공유기간"}},"required":["userId","endDate"]}}}} \ No newline at end of file +{"openapi":"3.0.0","paths":{"/":{},"/database/type":{"get":{"operationId":"DatabaseController_findTypeList","parameters":[],"responses":{"200":{"description":""}},"tags":["dashboard"]}},"/database/data":{"get":{"operationId":"DatabaseController_findData","parameters":[{"name":"datasetType","required":true,"in":"query","schema":{"type":"string"}},{"name":"databaseId","required":true,"in":"query","schema":{"type":"number"}},{"name":"datasetId","required":true,"in":"query","schema":{"type":"number"}},{"name":"tableName","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["dashboard"]}},"/database/info/{id}":{"get":{"operationId":"DatabaseController_findOneInfo","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["dashboard"]}},"/database":{"post":{"operationId":"DatabaseController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDatabaseDto"}}}},"responses":{"201":{"description":""}},"tags":["dashboard"]},"get":{"operationId":"DatabaseController_findAll","parameters":[],"responses":{"200":{"description":""}},"tags":["dashboard"]}},"/database/test":{"post":{"operationId":"DatabaseController_testConnection","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDatabaseDto"}}}},"responses":{"201":{"description":""}},"tags":["dashboard"]}},"/database/execute":{"post":{"operationId":"DatabaseController_executeQuery","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryExecuteDto"}}}},"responses":{"201":{"description":""}},"tags":["dashboard"]}},"/database/{id}":{"get":{"operationId":"DatabaseController_findOne","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["dashboard"]},"put":{"operationId":"DatabaseController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDatabaseDto"}}}},"responses":{"200":{"description":""}},"tags":["dashboard"]},"delete":{"operationId":"DatabaseController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["dashboard"]}},"/dataset":{},"/dataset/{id}":{},"/widget":{},"/widget/{id}":{},"/dashboard":{},"/dashboard/{id}":{},"/template":{},"/template/{id}":{},"/template/recommend":{},"/template/dashboard":{},"/component/seed":{},"/component":{},"/component/{id}":{},"/user/userinfo":{"get":{"operationId":"UserController_findOne","summary":"해당유저정보 가져오기","parameters":[],"responses":{"200":{"description":""}},"tags":["유저 API"],"security":[{"AccessKey":[]}]}},"/user/change-info":{"patch":{"operationId":"UserController_updateUsername","summary":"유저정보 수정","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":""}},"tags":["유저 API"],"security":[{"AccessKey":[]}]}},"/user/delete-account":{"delete":{"operationId":"UserController_deleteUser","summary":" 해당유저 삭제 ","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"200":{"description":""}},"tags":["유저 API"],"security":[{"AccessKey":[]}]}},"/user/get-access-token":{"post":{"operationId":"UserController_reissuanceAccessToken","summary":"AccessToken 재 발급","parameters":[],"responses":{"201":{"description":""}},"tags":["유저 API"]}},"/user/get-dashboard":{"get":{"operationId":"UserController_findDashboardId","summary":"해당유정의 대시보드 목록 가져오기","parameters":[],"responses":{"200":{"description":""}},"tags":["유저 API"],"security":[{"AccessKey":[]}]}},"/login/signin":{"post":{"operationId":"LoginController_logIn","summary":" 로그인 ","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginUserDto"}}}},"responses":{"201":{"description":""}},"tags":["로그인 관련 API"]}},"/login/signup":{"post":{"operationId":"LoginController_create","summary":"회원가입","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLoginDto"}}}},"responses":{"201":{"description":""}},"tags":["로그인 관련 API"]}},"/login/signout":{"post":{"operationId":"LoginController_signOut","summary":"로그아웃","parameters":[],"responses":{"201":{"description":""}},"tags":["로그인 관련 API"]}},"/share-url/share-on/{dashboardId}":{"post":{"operationId":"ShareUrlController_checkShareUrlOn","summary":"공유 url On","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareUrlOnDto"}}}},"responses":{"201":{"description":""}},"tags":["url공유화 API"],"security":[{"AccessKey":[]}]}},"/share-url/share-off/{dashboardId}":{"post":{"operationId":"ShareUrlController_checkShareUrlOff","summary":"공유 url Off","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareUrlOnDto"}}}},"responses":{"201":{"description":""}},"tags":["url공유화 API"],"security":[{"AccessKey":[]}]}},"/share-url/share-dashboard/{uuid}":{"get":{"operationId":"ShareUrlController_shareDashboardInfo","summary":"공유 url 접속","parameters":[],"responses":{"200":{"description":""}},"tags":["url공유화 API"]}}},"info":{"title":"NestJS Study API Docs","description":"NestJS Study API description","version":"1.0.0","contact":{}},"tags":[{"name":"유저 API","description":""},{"name":"url공유화 API","description":""},{"name":"로그인 관련 API","description":""},{"name":"dashboard","description":""}],"servers":[],"components":{"securitySchemes":{"AccessKey":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"Enter jwt token","in":"header"}},"schemas":{"CreateDatabaseDto":{"type":"object","properties":{"name":{"type":"string","example":"mysql 데이터베이스","description":"데이터베이스 이름"},"description":{"type":"string","example":"상세 내용","description":"데이터베이스 상세 내용"},"connectionConfig":{"type":"string","example":"{}","description":"설정 JSON 상세"},"engine":{"type":"string","example":"mysql","description":"데이터베이스 엔진"},"type":{"type":"string","example":"mysql","description":"데이터베이스 구분"},"timezone":{"type":"string","example":"Asia/Seoul","description":"서비스 타임존"}},"required":["name","description","connectionConfig","engine","type","timezone"]},"QueryExecuteDto":{"type":"object","properties":{"id":{"type":"number","example":"1","description":"database id"},"query":{"type":"string","example":"select * from sample_table1","description":"실행 쿼리"}},"required":["id","query"]},"UpdateDatabaseDto":{"type":"object","properties":{}},"CreateDatasetDto":{"type":"object","properties":{}},"UpdateDatasetDto":{"type":"object","properties":{}},"CreateWidgetDto":{"type":"object","properties":{}},"UpdateWidgetDto":{"type":"object","properties":{}},"CreateDashboardDto":{"type":"object","properties":{}},"UpdateDashboardDto":{"type":"object","properties":{}},"CreateTemplateDto":{"type":"object","properties":{}},"UpdateTemplateDto":{"type":"object","properties":{}},"CreateComponentDto":{"type":"object","properties":{}},"UpdateComponentDto":{"type":"object","properties":{}},"UpdateUserDto":{"type":"object","properties":{"userId":{"type":"string","description":"유저Id"},"password":{"type":"string","description":"유저password"},"email":{"type":"string","description":"유저email"}}},"CreateUserDto":{"type":"object","properties":{"userId":{"type":"string","description":"유저Id"},"password":{"type":"string","description":"유저password"},"email":{"type":"string","description":"유저email"}},"required":["userId","password","email"]},"LoginUserDto":{"type":"object","properties":{"userId":{"type":"string","description":"유저Id"},"password":{"type":"string","description":"유저password"}},"required":["userId","password"]},"CreateLoginDto":{"type":"object","properties":{}},"ShareUrlOnDto":{"type":"object","properties":{"userId":{"type":"string","description":"유저Id"},"endDate":{"type":"string","description":"공유 url 공유기간"}},"required":["userId","endDate"]}}}} \ No newline at end of file