diff --git a/.eslintrc.js b/.eslintrc.js index 5e53d47..e096fcc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,11 +1,7 @@ export default { parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint', 'prettier'], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier', - ], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], env: { node: true, es2022: true, diff --git a/package-lock.json b/package-lock.json index 2a1717a..9246f79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcryptjs": "^3.0.2", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", + "express-session": "^1.18.1", "mysql2": "^3.12.0", "sequelize": "^6.37.5", "uuid": "^11.1.0" @@ -22,6 +24,7 @@ "@types/cookie-parser": "^1.4.8", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", + "@types/express-session": "^1.18.1", "@types/node": "^22.13.5", "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.1", @@ -454,6 +457,15 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-S6TkD/lljxDlQ2u/4A70luD8/ZxZcrU5pQwI1rVXCiaVIywoFgbA+PIUNDjPhQpPdK0dGleLtYc/y7XWBfclBg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -895,6 +907,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -1506,6 +1526,37 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2220,6 +2271,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2418,6 +2477,14 @@ } ] }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2970,6 +3037,17 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", diff --git a/package.json b/package.json index 15816bb..fd6d202 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/cookie-parser": "^1.4.8", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", + "@types/express-session": "^1.18.1", "@types/node": "^22.13.5", "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.1", @@ -28,6 +29,7 @@ "typescript-eslint": "^8.25.0" }, "dependencies": { + "bcryptjs": "^3.0.2", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.7", diff --git a/src/controllers/dashboardController.ts b/src/controllers/dashboardController.ts index 9801bf2..b5537a0 100644 --- a/src/controllers/dashboardController.ts +++ b/src/controllers/dashboardController.ts @@ -1,14 +1,16 @@ import { NextFunction, Request, Response } from 'express'; +import { dashboardClientService } from '../services/dashboardClientService'; import { userActionService } from '../services/userActionService'; import { userConnectionService } from '../services/userConnectionService'; import { userDeviceService } from '../services/userDeviceService'; import { userInfoService } from '../services/userInfoService'; import { userPageInfoService } from '../services/userPageInfoService'; +import { AuthenticatedRequest } from '../types/sessionType'; export const dashboardController = { - getOnlineUsersCount: async (req: Request, res: Response, next: NextFunction) => { + getOnlineUsersCount: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const onlineUsersCount = await userConnectionService.getOnlineUsersCount(domain); res.status(200).json({ onlineUsersCount }); } catch (err) { @@ -16,9 +18,13 @@ export const dashboardController = { } }, - getPerPageAverageScrollDepth: async (req: Request, res: Response, next: NextFunction) => { + getPerPageAverageScrollDepth: async ( + req: AuthenticatedRequest, + res: Response, + next: NextFunction + ) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const perPageAverageScrollDepth = await userActionService.getPerPageAverageScrollDepth(domain); res.status(200).json(perPageAverageScrollDepth); @@ -27,9 +33,9 @@ export const dashboardController = { } }, - getPerPageBounceRate: async (req: Request, res: Response, next: NextFunction) => { + getPerPageBounceRate: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const bounceRate = await userActionService.getPerPageBounceRate(domain); res.status(200).json(bounceRate); } catch (err) { @@ -37,9 +43,9 @@ export const dashboardController = { } }, - getBrowserStats: async (req: Request, res: Response, next: NextFunction) => { + getBrowserStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const userBrowserStats = await userDeviceService.getBrowserStats(domain); res.status(200).json(userBrowserStats); } catch (err) { @@ -47,9 +53,9 @@ export const dashboardController = { } }, - getOsStats: async (req: Request, res: Response, next: NextFunction) => { + getOsStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const userOsStats = await userDeviceService.getOsStats(domain); res.status(200).json(userOsStats); } catch (err) { @@ -57,9 +63,9 @@ export const dashboardController = { } }, - getDeviceStats: async (req: Request, res: Response, next: NextFunction) => { + getDeviceStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const userDeviceStats = await userDeviceService.getDeviceStats(domain); res.status(200).json(userDeviceStats); } catch (err) { @@ -67,9 +73,9 @@ export const dashboardController = { } }, - getResolutionStats: async (req: Request, res: Response, next: NextFunction) => { + getResolutionStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const userResolutionStats = await userDeviceService.getResolutionStats(domain); res.status(200).json(userResolutionStats); } catch (err) { @@ -77,9 +83,9 @@ export const dashboardController = { } }, - getLanguageStats: async (req: Request, res: Response, next: NextFunction) => { + getLanguageStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const languageStats = await userInfoService.getLanguageStats(domain); res.status(200).json(languageStats); } catch (err) { @@ -87,9 +93,9 @@ export const dashboardController = { } }, - getCountryStats: async (req: Request, res: Response, next: NextFunction) => { + getCountryStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const countryStats = await userInfoService.getCountryStats(domain); res.status(200).json(countryStats); } catch (err) { @@ -97,9 +103,9 @@ export const dashboardController = { } }, - getVisitedUsersRate: async (req: Request, res: Response, next: NextFunction) => { + getVisitedUsersRate: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const getVisitedUsersRate = await userInfoService.getVisitedUsersRate(domain); res.status(200).json(getVisitedUsersRate); } catch (err) { @@ -107,9 +113,9 @@ export const dashboardController = { } }, - getReferrerStats: async (req: Request, res: Response, next: NextFunction) => { + getReferrerStats: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const referrerStatus = await userPageInfoService.getReferrerStats(domain); res.status(200).json(referrerStatus); } catch (err) { @@ -117,9 +123,9 @@ export const dashboardController = { } }, - getAveragePageLoadTime: async (req: Request, res: Response, next: NextFunction) => { + getAveragePageLoadTime: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const avgLoadTime = await userPageInfoService.getAveragePageLoadTime(domain); res.status(200).json(avgLoadTime); } catch (err) { @@ -127,9 +133,9 @@ export const dashboardController = { } }, - getPageViewCount: async (req: Request, res: Response, next: NextFunction) => { + getPageViewCount: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: '시작 날짜와 종료날짜 올바르게 입력하세요' }); @@ -146,9 +152,9 @@ export const dashboardController = { } }, - getVisitorsByPeriod: async (req: Request, res: Response, next: NextFunction) => { + getVisitorsByPeriod: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const { startDate, endDate } = req.query; if (typeof startDate !== 'string' || typeof endDate !== 'string') { res.status(400).json({ message: 'startDate와 endDate는 필수입니다.' }); @@ -165,13 +171,45 @@ export const dashboardController = { } }, - getTotalVisitors: async (req: Request, res: Response, next: NextFunction) => { + getTotalVisitors: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { try { - const { domain } = req.params; + const domain = req.session.client.domain; const totalVisitorsData = await userPageInfoService.getTotalVisitors(domain); res.status(200).json(totalVisitorsData); } catch (err) { next(err); } }, + + enrollClient: async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, password, domain } = req.body; + const apiKey = await dashboardClientService.enrollClient(email, password, domain); + res.status(201).json({ message: '회원가입 성공', apiKey }); + } catch (err) { + next(err); + } + }, + + loginClient: async (req: Request, res: Response, next: NextFunction) => { + try { + const { email, password } = req.body; + const domain = await dashboardClientService.loginClient(email, password); + req.session.client = { email, domain }; + res.status(200).json({ message: '로그인 성공' }); + } catch (err) { + next(err); + } + }, + + logoutClient: (req: Request, res: Response, next: NextFunction) => { + if (!req.session.client) { + next(new Error('이미 로그아웃')); + return; + } + req.session.destroy(() => { + res.clearCookie('connect.sid'); + res.json({ message: '로그아웃 성공' }); + }); + }, }; diff --git a/src/middleware/authenticateAPIKey.ts b/src/middleware/authenticateAPIKey.ts index c6bb062..d08ba51 100644 --- a/src/middleware/authenticateAPIKey.ts +++ b/src/middleware/authenticateAPIKey.ts @@ -1,5 +1,5 @@ import { NextFunction, Request, RequestHandler, Response } from 'express'; -import { getAPIKeyFromDB } from '../services/apiKeyService'; +import { getClientDomain } from '../services/apiKeyService'; export const authenticateAPIKey: RequestHandler = async ( req: Request, @@ -13,11 +13,7 @@ export const authenticateAPIKey: RequestHandler = async ( } const apiKey = authHeader.split(' ')[1]; try { - const domain = await getAPIKeyFromDB(apiKey); - if (!domain) { - res.status(403).json({ error: 'Forbidden: Invalid API Key' }); - return; - } + const { domain } = await getClientDomain(apiKey); res.locals.domain = domain; next(); } catch (err) { diff --git a/src/middleware/ensureLogin.ts b/src/middleware/ensureLogin.ts new file mode 100644 index 0000000..21c2fdc --- /dev/null +++ b/src/middleware/ensureLogin.ts @@ -0,0 +1,8 @@ +import { NextFunction, Request, Response } from 'express'; + +export const ensureLogin = (req: Request, res: Response, next: NextFunction) => { + if (!req.session.client) { + next(new Error('로그인 필요')); + } + next(); +}; diff --git a/src/middleware/errorHandle.ts b/src/middleware/errorHandle.ts index e4826ee..c9ff810 100644 --- a/src/middleware/errorHandle.ts +++ b/src/middleware/errorHandle.ts @@ -5,5 +5,13 @@ export const errorHandle = (err: Error, req: Request, res: Response, next: NextF if (res.headersSent) { return next(err); } + if ( + err.message === 'apiKey 인증실패' || + err.message === '로그인 필요' || + err.message === '이미 로그아웃' + ) { + res.status(401).json({ message: err.message }); + } + res.status(500).json({ message: '서버 내부 오류', err: err.message }); }; diff --git a/src/middleware/session.ts b/src/middleware/session.ts new file mode 100644 index 0000000..e2c7677 --- /dev/null +++ b/src/middleware/session.ts @@ -0,0 +1,10 @@ +import session from 'express-session'; + +export function createSession() { + return session({ + secret: process.env.SESSION_SECRET as string, + resave: false, + saveUninitialized: false, + cookie: { secure: false, httpOnly: true }, + }); +} diff --git a/src/models/apikeyModel.ts b/src/models/apikeyModel.ts deleted file mode 100644 index 1f5e73d..0000000 --- a/src/models/apikeyModel.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { DataTypes, Model } from 'sequelize'; -import sequelize from '../config/db'; - -class APIKeyModel extends Model {} - -APIKeyModel.init( - { - id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, - api_key: { type: DataTypes.STRING, allowNull: false, unique: true }, - domain: { type: DataTypes.STRING, allowNull: false }, - created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - }, - { - sequelize, - modelName: 'apiKeys', - tableName: 'apiKeys', - timestamps: true, - } -); -export const apiKeyModel = APIKeyModel; diff --git a/src/models/dashboardClientModel.ts b/src/models/dashboardClientModel.ts new file mode 100644 index 0000000..6dd8206 --- /dev/null +++ b/src/models/dashboardClientModel.ts @@ -0,0 +1,22 @@ +import { DataTypes, Model } from 'sequelize'; +import sequelize from '../config/db'; + +class DashboardClient extends Model {} + +DashboardClient.init( + { + id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, + email: { type: DataTypes.STRING, allowNull: false, primaryKey: true, unique: true }, + hashedPassword: { type: DataTypes.STRING, allowNull: false }, + domain: { type: DataTypes.STRING, allowNull: false, unique: true }, + apiKey: { type: DataTypes.STRING, allowNull: false }, + }, + { + sequelize, + modelName: 'dashboardClient', + tableName: 'dashboardClients', + timestamps: true, + } +); + +export const DashboardClientModel = DashboardClient; diff --git a/src/routes/dashBoardRoutes.ts b/src/routes/dashBoardRoutes.ts index 543d67b..b4b987a 100644 --- a/src/routes/dashBoardRoutes.ts +++ b/src/routes/dashBoardRoutes.ts @@ -1,41 +1,82 @@ -import express from 'express'; +import express, { RequestHandler } from 'express'; import { dashboardController } from '../controllers/dashboardController'; - +import { ensureLogin } from '../middleware/ensureLogin'; export const dashboardRouter = express.Router(); -dashboardRouter.get('/userConnection/onlineUsersCount', dashboardController.getOnlineUsersCount); -dashboardRouter.get('/userDevice/browsersStats', dashboardController.getBrowserStats); -dashboardRouter.get('/userDevice/osStats', dashboardController.getOsStats); -dashboardRouter.get('/userDevice/deviceStats', dashboardController.getDeviceStats); -dashboardRouter.get('/userDevice/resolutionStats', dashboardController.getResolutionStats); dashboardRouter.get( - '/domains/:domain/userInfo/languageStats', - dashboardController.getLanguageStats + '/dashboard/onlineUsersCount', + ensureLogin, + dashboardController.getOnlineUsersCount as RequestHandler +); +dashboardRouter.get( + '/dashboard/browsersStats', + ensureLogin, + dashboardController.getBrowserStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/osStats', + ensureLogin, + dashboardController.getOsStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/deviceStats', + ensureLogin, + dashboardController.getDeviceStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/resolutionStats', + ensureLogin, + dashboardController.getResolutionStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/languageStats', + ensureLogin, + dashboardController.getLanguageStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/countryStats', + ensureLogin, + dashboardController.getCountryStats as RequestHandler +); +dashboardRouter.get( + '/dashboard/visitedRate', + ensureLogin, + dashboardController.getVisitedUsersRate as RequestHandler +); +dashboardRouter.get( + '/dashboard/referrer', + ensureLogin, + dashboardController.getReferrerStats as RequestHandler ); -dashboardRouter.get('/domains/:domain/userInfo/countryStats', dashboardController.getCountryStats); dashboardRouter.get( - '/domains/:domain/userInfo/visitedRate', - dashboardController.getVisitedUsersRate + '/dashboard/loadTime', + ensureLogin, + dashboardController.getAveragePageLoadTime as RequestHandler ); -dashboardRouter.get('/domains/:domain/pageInfo/referrer', dashboardController.getReferrerStats); dashboardRouter.get( - '/domains/:domain/pageInfo/loadTime', - dashboardController.getAveragePageLoadTime + '/dashboard/visitorsPageByPeriodCount', + ensureLogin, + dashboardController.getPageViewCount as RequestHandler ); dashboardRouter.get( - '/domains/:domain/pageInfo/visitorsPageByPeriodCount', - dashboardController.getPageViewCount + '/dashboard/perPageAverageScrollDepth', + dashboardController.getPerPageAverageScrollDepth as RequestHandler ); dashboardRouter.get( - '/userAction/perPageAverageScrollDepth', - dashboardController.getPerPageAverageScrollDepth + '/dashboard/bounceRate', + ensureLogin, + dashboardController.getPerPageBounceRate as RequestHandler ); -dashboardRouter.get('/userAction/bounceRate', dashboardController.getPerPageBounceRate); dashboardRouter.get( - `/domains/:domain/pageInfo/visitorsByPeriodCount`, - dashboardController.getVisitorsByPeriod + '/dashboard/visitorsByPeriodCount', + ensureLogin, + dashboardController.getVisitorsByPeriod as RequestHandler ); dashboardRouter.get( - `/domains/:domain/pageInfo/totalVisitorsCount`, - dashboardController.getTotalVisitors + '/dashboard/totalVisitorsCount', + ensureLogin, + dashboardController.getTotalVisitors as RequestHandler ); +dashboardRouter.post('/dashboard/enrollClient', dashboardController.enrollClient); +dashboardRouter.post('/dashboard/loginClient', dashboardController.loginClient); +dashboardRouter.post('/dashboard/logoutClient', dashboardController.logoutClient); diff --git a/src/server.ts b/src/server.ts index 76baa01..53d2db4 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,13 +4,14 @@ import express from 'express'; import { UUIDV4 } from 'sequelize'; import { authenticateAPIKey } from './middleware/authenticateAPIKey'; import { errorHandle } from './middleware/errorHandle'; +import { createSession } from './middleware/session'; import { dashboardRouter } from './routes/dashboardRoutes'; import { trackerSdkRouter } from './routes/trackerSdkRoutes'; - const app = express(); const port = 3000; app.use(authenticateAPIKey); +app.use(createSession()); app.use( cors({ origin: 'http://client-tracker-sdk', diff --git a/src/services/apiKeyService.ts b/src/services/apiKeyService.ts index 058bb64..e163bd2 100644 --- a/src/services/apiKeyService.ts +++ b/src/services/apiKeyService.ts @@ -1,10 +1,14 @@ -import { apiKeyModel } from '../models/apiKeyModel'; +import { DashboardClientModel } from '../models/dashboardClientModel'; -export async function getAPIKeyFromDB(apiKey: string) { - const apiKeyData = await apiKeyModel.findOne({ +export async function getClientDomain(apiKey: string) { + const clientDomain = await DashboardClientModel.findOne({ where: { apiKey }, attributes: ['domain'], }); + if (!clientDomain) { + throw new Error('apiKey 인증에러'); + } + const clientDomainForSdk: { domain: string } = clientDomain.get({ plain: true }); - return apiKeyData ? apiKeyData.toJSON() : null; + return clientDomainForSdk; } diff --git a/src/services/dashboardClientService.ts b/src/services/dashboardClientService.ts new file mode 100644 index 0000000..81ae97e --- /dev/null +++ b/src/services/dashboardClientService.ts @@ -0,0 +1,32 @@ +import bcrypt from 'bcryptjs'; +import * as crypto from 'crypto'; +import { DashboardClientModel } from '../models/dashboardClientModel'; +export const dashboardClientService = { + enrollClient: async (email: string, password: string, domain: string) => { + const apiKey = crypto.randomBytes(32).toString('hex'); + const hashedPassword = await bcrypt.hash(password, 10); + await DashboardClientModel.create({ + email, + hashedPassword, + domain, + apiKey, + }); + return apiKey; + }, + + loginClient: async (email: string, password: string) => { + const client = await DashboardClientModel.findOne({ + where: { email }, + attributes: ['hashedPassword', 'domain'], + }); + if (!client) { + throw new Error('로그인 에러'); + } + const clientData: { hashedPassword: string; domain: string } = client.get({ plain: true }); + const isValidPassword = await bcrypt.compare(password, clientData.hashedPassword); + if (!isValidPassword) { + throw new Error('로그인 에러'); + } + return clientData.domain; + }, +}; diff --git a/src/types/sessionDataType.d.ts b/src/types/sessionDataType.d.ts new file mode 100644 index 0000000..4895ffc --- /dev/null +++ b/src/types/sessionDataType.d.ts @@ -0,0 +1,10 @@ +import 'express-session'; + +declare module 'express-session' { + interface SessionData { + client?: { + email: string; + domain: string; + }; + } +} diff --git a/src/types/sessionType.d.ts b/src/types/sessionType.d.ts new file mode 100644 index 0000000..fb324f9 --- /dev/null +++ b/src/types/sessionType.d.ts @@ -0,0 +1,13 @@ +import { Request } from 'express'; +import { Session } from 'express-session'; +import { ParsedQs } from 'qs'; + +export interface AuthenticatedRequest + extends Request { + session: Session & { + client: { + email: string; + domain: string; + }; + }; +}