diff --git a/.gitignore b/.gitignore index bcc3246..1e093d6 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,12 @@ web_modules/ .production.env .stage.env +#typeORM environment files +orm.config.ts + +#jwt Configuration files +jwt.config.ts + # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache diff --git a/package-lock.json b/package-lock.json index 541e7fc..6fa0d0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,14 +12,20 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "*", + "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.2", + "@types/passport-jwt": "^4.0.1", "bcrypt": "^5.1.1", "bcrypto": "^5.5.2", "class-validator": "^0.14.1", "dotenv": "^16.4.5", "mysql2": "^3.11.4", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "typeorm": "^0.3.20" @@ -32,6 +38,7 @@ "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", + "@types/passport-local": "^1.0.38", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", @@ -1736,6 +1743,19 @@ } } }, + "node_modules/@nestjs/jwt": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz", + "integrity": "sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.5", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, "node_modules/@nestjs/mapped-types": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", @@ -1756,6 +1776,16 @@ } } }, + "node_modules/@nestjs/passport": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-10.0.3.tgz", + "integrity": "sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "passport": "^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "10.4.7", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.7.tgz", @@ -2034,7 +2064,6 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -2045,7 +2074,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -2101,7 +2129,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -2114,7 +2141,6 @@ "version": "4.19.6", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -2137,7 +2163,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { @@ -2185,6 +2210,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -2196,38 +2230,74 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "20.17.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz", "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-local": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.38.tgz", + "integrity": "sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -2238,7 +2308,6 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -3338,6 +3407,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4199,6 +4274,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6742,6 +6826,49 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6840,6 +6967,42 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -6854,6 +7017,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -7597,6 +7766,53 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7670,6 +7886,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -9545,7 +9766,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "devOptional": true, "license": "MIT" }, "node_modules/universalify": { diff --git a/package.json b/package.json index 4c0a08d..ba0ec70 100644 --- a/package.json +++ b/package.json @@ -24,14 +24,20 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "*", + "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/typeorm": "^10.0.2", + "@types/passport-jwt": "^4.0.1", "bcrypt": "^5.1.1", "bcrypto": "^5.5.2", "class-validator": "^0.14.1", "dotenv": "^16.4.5", "mysql2": "^3.11.4", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "typeorm": "^0.3.20" @@ -44,6 +50,7 @@ "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", + "@types/passport-local": "^1.0.38", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", diff --git a/src/app.module.ts b/src/app.module.ts index 783b9ef..7b8b8d7 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,30 +5,29 @@ import { ConfigModule } from '@nestjs/config'; import { UsersModule } from './users/users.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthModule } from './auth/auth.module'; +import { ormConfig } from 'orm.config'; +import { KakaoMapModule } from './kakaomap/map.module'; @Module({ - imports: [ - ConfigModule.forRoot({ - envFilePath: [ - (process.env.NODE_ENV === 'production') ? '.production.env' - : (process.env.NODE_ENV === 'stage') ? '.stage.env' : '.development.env' - ], - isGlobal: true, - }), - UsersModule, - TypeOrmModule.forRoot({ - type: 'mysql', - host: 'localhost', - port: 3306, - username: 'root', - password: '1234', - database: 'conn_test', - entities: [__dirname + `/**/*.entity{.ts,.js}`], - synchronize: false + imports: [ + ConfigModule.forRoot({ + envFilePath: [ + process.env.NODE_ENV === 'production' + ? '.production.env' + : process.env.NODE_ENV === 'stage' + ? '.stage.env' + : '.development.env' + ], + isGlobal: true + }), + TypeOrmModule.forRootAsync({ + useFactory: ormConfig }), - AuthModule + UsersModule, + AuthModule, + KakaoMapModule ], controllers: [AppController], - providers: [AppService, ], + providers: [AppService] }) -export class AppModule {} +export class AppModule { } diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index f7f8dcd..d9115ba 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,7 +1,14 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Post, UseGuards, Request } from '@nestjs/common'; import { AuthService } from './auth.service'; +import { LocalAuthGuard } from './security/passport.jwt'; -@Controller('auth') +@Controller() export class AuthController { constructor(private readonly authService: AuthService) {} + + @Post('/signin') + @UseGuards(LocalAuthGuard) + async singin(@Request() req){ + return this.authService.signin(req.user); + } } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 46e183a..a9963d2 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,9 +1,22 @@ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; +import { UsersModule } from 'src/users/users.module'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { JwtStrategy, JwtLocalStrategy } from './security/passport.jwt'; +import { jwtConstants } from 'jwt.config'; @Module({ + imports: [ + UsersModule, + JwtModule.register({ + secret: jwtConstants.secret, + signOptions: { expiresIn: jwtConstants.expiresIn } + }), + PassportModule + ], controllers: [AuthController], - providers: [AuthService], + providers: [AuthService, JwtStrategy, JwtLocalStrategy], }) -export class AuthModule {} +export class AuthModule { } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index a41c649..a62345e 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,4 +1,60 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import * as bcrypt from 'bcrypt'; +import { UserEntity } from 'src/users/entities/user.entity'; +import { UsersService } from 'src/users/users.service'; +import { JwtService } from '@nestjs/jwt'; +import { JwtTokenDto } from './dto/token.dto'; +import { jwtConstants } from 'jwt.config'; @Injectable() -export class AuthService {} +export class AuthService { + constructor(private readonly userService: UsersService, + private readonly jwtService: JwtService) { } + + async validateUser(userId: string, password: string): Promise { + const user = await this.userService.findbyId(userId); + if (!user) { + throw new UnauthorizedException('아이디 혹은 비밀번호를 확인하세요.'); + } + + const isPasswordValid = bcrypt.compareSync(password, user.password); + if (!isPasswordValid) { + throw new UnauthorizedException('아이디 혹은 비밀번호를 확인히세요.'); + } else { + delete user.password + } + + return user; + } + + async signin(user: UserEntity) { + return this.createToken(user); + } + + async createToken(user: UserEntity) { + const tokenUserNo = user.userNo; + const tokenDto = new JwtTokenDto(); + tokenDto.accessToken = await this.createAccessToken(user); + tokenDto.refreshToken = await this.createRefreshToken(tokenUserNo); + + return tokenDto; + } + + async createAccessToken(user: UserEntity): Promise { + const payload = { userNo: user.userNo, userId: user.userId } + return this.jwtService.sign({ payload }, + { + secret: jwtConstants.secret, + expiresIn: jwtConstants.expiresIn + }) + } + + async createRefreshToken(userNo: number): Promise { + return this.jwtService.sign({ userNo }, + { + secret: jwtConstants.secret, + expiresIn: jwtConstants.expiresInRefresh + }) + } + +} diff --git a/src/auth/dto/auth.dto.ts b/src/auth/dto/auth.dto.ts index c9144b1..91c6150 100644 --- a/src/auth/dto/auth.dto.ts +++ b/src/auth/dto/auth.dto.ts @@ -1,22 +1,22 @@ -import { IsEmpty, IsString, IsEmail, Length, IsAlphanumeric, Matches} from "class-validator"; +import {IsString, IsEmpty, IsEmail, Length, IsAlphanumeric, Matches} from "class-validator"; export namespace AuthDTO{ export class SignUp{ //TODO : guard랑 JWT토큰 적용 - @IsEmpty() - @IsAlphanumeric() + @IsEmpty({message: '사용자 ID는 공백일 수 없습니다.'}) + @IsAlphanumeric("en-US", {message:'사용자 ID는 영어와 숫자만 가능합니다.'}) userId: string; - @IsEmpty() - @IsString() - @Length(2,10) + @IsEmpty({message: '사용자 이름은 공백일 수 없습니다.'}) + @IsString({message: '사용자 이름은 문자만 가능합니다.'}) + @Length(2,10,{message:'사용자 이름은 최소 2글자에서 10글자 까지 가능합니다.'}) userName: string; - @IsEmpty() + @IsEmpty({message: '사용자 email은 공백일 수 없습니다.'}) @IsEmail() email:string; - @IsEmpty() - @IsString() + @IsEmpty({message: '사용자 비밀번호는 공백일 수 없습니다.'}) + @IsString({message: '사용자 비밀번호는 문자만 가능합니다.'}) @Matches(/^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*?_]).{8,20}$/, { message: '패스워드는 8~20자리이며 최소 하나 이상의 영문자, 최소 하나 이상의 숫자, 최소 하나 이상의 특수문자를 입력해야 합니다.', diff --git a/src/auth/dto/token.dto.ts b/src/auth/dto/token.dto.ts new file mode 100644 index 0000000..cff9fdd --- /dev/null +++ b/src/auth/dto/token.dto.ts @@ -0,0 +1,4 @@ +export class JwtTokenDto { + accessToken; + refreshToken; +} \ No newline at end of file diff --git a/src/auth/security/passport.jwt.ts b/src/auth/security/passport.jwt.ts new file mode 100644 index 0000000..5b330af --- /dev/null +++ b/src/auth/security/passport.jwt.ts @@ -0,0 +1,50 @@ +import { Injectable, UnauthorizedException } from "@nestjs/common"; +import { PassportStrategy } from "@nestjs/passport"; +import { ExtractJwt, Strategy } from "passport-jwt"; +import { Strategy as LocalStrategy } from "passport-local"; +import { AuthGuard } from "@nestjs/passport"; + +import { AuthService } from "../auth.service"; +import { jwtConstants } from "jwt.config"; + +@Injectable() +export class JwtLocalStrategy extends PassportStrategy(LocalStrategy, 'local') { + constructor(private readonly authService: AuthService) { + super({ + usernameField: 'userId', + passwordField: 'password' + }) + } + + async validate(userId: string, password: string): Promise { + const user = await this.authService.validateUser(userId, password); + if (!user) { + return new UnauthorizedException({ message: '회원 정보가 존재하지 않습니다.' }); + } + return user; + + } +} + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: jwtConstants.secret, + ignoreException: false, + algorithms: ['HS256'], + }) + } + + async validate(payload: any) { + return { ...payload }; + } +} + + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { } + +@Injectable() +export class LocalAuthGuard extends AuthGuard('local') { } \ No newline at end of file diff --git a/src/kakaomap/map.controller.spec.ts b/src/kakaomap/map.controller.spec.ts new file mode 100644 index 0000000..61b9582 --- /dev/null +++ b/src/kakaomap/map.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { KakaoMapController } from './map.controller'; + +describe('MapController', () => { + let controller: KakaoMapController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [KakaoMapController] + }).compile(); + + controller = module.get(KakaoMapController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/kakaomap/map.controller.ts b/src/kakaomap/map.controller.ts new file mode 100644 index 0000000..4964f35 --- /dev/null +++ b/src/kakaomap/map.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { KakaoMapService } from './map.service'; + +@Controller('map') +export class KakaoMapController { + constructor(private readonly kakaoMapService: KakaoMapService) {} + + @Get('/search/:inputQuery') + async searchMap(@Param('inputQuery') inputQuery: string) { + return this.kakaoMapService.searchAddress(inputQuery); + } +} diff --git a/src/kakaomap/map.module.ts b/src/kakaomap/map.module.ts new file mode 100644 index 0000000..a090f3d --- /dev/null +++ b/src/kakaomap/map.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { KakaoMapController } from './map.controller'; +import { KakaoMapService } from './map.service'; + +@Module({ + controllers: [KakaoMapController], + providers: [KakaoMapService] +}) +export class KakaoMapModule {} diff --git a/src/kakaomap/map.service.spec.ts b/src/kakaomap/map.service.spec.ts new file mode 100644 index 0000000..1c369e9 --- /dev/null +++ b/src/kakaomap/map.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { KakaoMapService } from './map.service'; + +describe('MapService', () => { + let service: KakaoMapService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [KakaoMapService] + }).compile(); + + service = module.get(KakaoMapService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/kakaomap/map.service.ts b/src/kakaomap/map.service.ts new file mode 100644 index 0000000..4c7a28d --- /dev/null +++ b/src/kakaomap/map.service.ts @@ -0,0 +1,20 @@ +import { Injectable, Inject } from '@nestjs/common'; +import axios from 'axios'; + +@Injectable() +export class KakaoMapService { + constructor() {} + + async searchAddress(inputQuery: string) { + const url = `https://dapi.kakao.com/v2/local/search/keyword`; + const response = await axios.get(url, { + headers: { + Authorization: `KakaoAK ` + process.env.REST_API_KEY + }, + params: { + query: inputQuery + } + }); + return response.data.documents; + } +} diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 4e44d4f..bc783e6 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,21 +1,24 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, ConflictException } from '@nestjs/common'; +import { + Controller, Get, Post, Body, Patch, Param, Delete, + ConflictException, UseGuards, Req, ParseIntPipe +} from '@nestjs/common'; import { UsersService } from './users.service'; import { AuthDTO } from 'src/auth/dto/auth.dto'; -import { userDto } from './dto/user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; +import { JwtAuthGuard } from 'src/auth/security/passport.jwt'; @Controller('users') export class UsersController { - constructor(private readonly usersService: UsersService) {} + constructor(private readonly usersService: UsersService) { } //TODO //가입, 로그인, 탈퇴, 수정, 아이디 패스워드 찾기, 이메일 인증? @Post('/signup') - async signup (@Body() authDto: AuthDTO.SignUp) { - const {userId, userName, email, password} = authDto; - + async signup(@Body() authDto: AuthDTO.SignUp) { + const { userId, userName, email, password } = authDto; + const hasUserId = await this.usersService.findbyId(userId); - if(hasUserId){ + if (hasUserId) { throw new ConflictException('이미 사용중인 아이디 입니다.'); } @@ -24,23 +27,32 @@ export class UsersController { return '회원가입성공'; } + @UseGuards(JwtAuthGuard) + @Get('/') + async getProfile(@Req() req: any) { + const user = req.user; + return user; + } + + @Get() findAll() { return this.usersService.findAll(); } - @Get(':id') - findOne(@Param('id') id: string) { - return this.usersService.findOne(+id); + @Get('/userNo') + findOne(@Param('userNo', ParseIntPipe) userNo: number) { + return this.usersService.findOne(userNo); } - @Patch(':id') - update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { - return this.usersService.update(+id, updateUserDto); - } + //TODOs + // @Patch('/userNo') + // update(@Param('userNo', ParseIntPipe) userNo: number, @Body() updateUserDto: UpdateUserDto) { + // return this.usersService.update(userNo, updateUserDto); + // } - @Delete(':id') - remove(@Param('id') id: string) { - return this.usersService.remove(+id); - } + // @Delete('/id') + // remove(@Param('id') id: string) { + // return this.usersService.remove(+id); + // } } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 781863f..cd6e447 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -12,15 +12,15 @@ export class UsersService { constructor( @InjectRepository(UserEntity) private userRepository: Repository, - ){} - + ) { } + async createUser(authDTO: AuthDTO.SignUp) { const userEntity = await this.userRepository.create(authDTO); - + return await this.userRepository.save(userEntity); } - async findbyId(userId: string){ + async findbyId(userId: string) { return await this.userRepository.findOne({ where: { userId, @@ -29,18 +29,22 @@ export class UsersService { } findAll() { - return `This action returns all users`; + throw new Error('Not implemented yet') } - findOne(id: number) { - return `This action returns a #${id} user`; - } - - update(id: number, updateUserDto: UpdateUserDto) { - return `This action updates a #${id} user`; - } - - remove(id: number) { - return `This action removes a #${id} user`; + findOne(userNo: number) { + return this.userRepository.findOne({ + where: { + userNo, + } + }) } + //TODOs + // update(id: number, updateUserDto: UpdateUserDto) { + // return `This action updates a #${id} user`; + // } + + // remove(id: number) { + // return `This action removes a #${id} user`; + // } }