diff --git a/src/controllers/apikey-controller.ts b/src/controllers/apikey-controller.ts new file mode 100644 index 0000000..2bfe67a --- /dev/null +++ b/src/controllers/apikey-controller.ts @@ -0,0 +1,49 @@ +import { Request, Response } from 'express'; +import { successResponse, errorResponse } from '../utils/response'; +import UserRepository from '../repositories/user-repository'; +import { generateApiKey } from '../utils/auth'; +import Users from '../models/user'; +import { InferAttributes } from 'sequelize'; + +type UserType = InferAttributes; + +interface AuthRequest extends Request { + user?: UserType; +} + +export const getApiKey = async (req: AuthRequest, res: Response) => { + try { + if (!req.user) { + return errorResponse(res, 'Unauthorized', 401); + } + + return successResponse( + res, + { apikey: req.user.apikey }, + 'User details retrieved', + ); + } catch (error) { + console.error(error); + return errorResponse(res, 'Unauthorized', 401); + } +}; + +export const regenerateApiKey = async (req: AuthRequest, res: Response) => { + try { + if (!req.user) { + return errorResponse(res, 'Unauthorized', 401); + } + + const newApiKey = await generateApiKey(); + await UserRepository.update({ id: req.user.id }, { apikey: newApiKey }); + + return successResponse( + res, + { apikey: newApiKey }, + 'User details retrieved', + ); + } catch (error) { + console.error(error); + return errorResponse(res, 'Unauthorized', 401); + } +}; diff --git a/src/controllers/query-controller.ts b/src/controllers/query-controller.ts new file mode 100644 index 0000000..11892c0 --- /dev/null +++ b/src/controllers/query-controller.ts @@ -0,0 +1,29 @@ +import { Request, Response } from 'express'; +import { sequelize } from '../config/database'; +import { QueryTypes } from 'sequelize'; +import { errorResponse, successResponse } from '../utils/response'; + +export const executeQuery = async (req: Request, res: Response) => { + try { + const { query } = req.body; + + if (!query || typeof query !== 'string') { + return errorResponse(res, 'Query is required and must be a string', 400); + } + + const forbiddenPatterns = [ + /DROP\s+TABLE/i, + /ALTER\s+/i, + /DELETE\s+FROM\s+[^\s]+(\s*;|$)/i, + ]; + if (forbiddenPatterns.some((pattern) => pattern.test(query))) { + return errorResponse(res, 'Query contains forbidden operations', 403); + } + + const result = await sequelize.query(query, { type: QueryTypes.RAW }); + return successResponse(res, result, 'Query executed successfully'); + } catch (error) { + console.error(error); + return errorResponse(res, 'Failed to execute query', 500); + } +}; diff --git a/src/controllers/schema-controller.ts b/src/controllers/schema-controller.ts index 704fe0d..695aade 100644 --- a/src/controllers/schema-controller.ts +++ b/src/controllers/schema-controller.ts @@ -1,14 +1,15 @@ import { Request, Response } from 'express'; import SchemaRepository from '../repositories/schema-repository'; +import { errorResponse, successResponse } from '../utils/response'; export const schema = async (req: Request, res: Response) => { try { const tables = await SchemaRepository.getSchemas(); - return res.json({ success: true, tables }); + return successResponse(res, tables); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - res.status(500).json({ error: errorMessage }); + return errorResponse(res, errorMessage, 500); } }; diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index 6dced80..de5ab69 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -5,6 +5,14 @@ import { ENV } from '../config/env'; import { hashPassword, generateApiKey } from '../utils/auth'; import { successResponse, errorResponse } from '../utils/response'; import UserRepository from '../repositories/user-repository'; +import Users from '../models/user'; +import { InferAttributes } from 'sequelize'; + +type UserType = InferAttributes; + +interface AuthRequest extends Request { + user?: UserType; +} export const registerUser = async (req: Request, res: Response) => { try { @@ -71,21 +79,9 @@ export const loginUser = async (req: Request, res: Response) => { } }; -export const getUserDetails = async (req: Request, res: Response) => { +export const getUserDetails = async (req: AuthRequest, res: Response) => { try { - const token = req.headers.authorization?.split(' ')[1]; - if (!token) { - return errorResponse(res, 'Unauthorized', 401); - } - - const decoded: any = jwt.verify(token, ENV.JWT_SECRET); - const user = await UserRepository.findOne({ id: decoded.userId }); - - if (!user) { - return errorResponse(res, 'Unauthorized', 401); - } - - return successResponse(res, user, 'User details retrieved'); + return successResponse(res, req.user, 'User details retrieved'); } catch (error) { console.error(error); return errorResponse(res, 'Unauthorized', 401); diff --git a/src/index.ts b/src/index.ts index 395af3c..dbcc677 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,8 @@ import ddlRoutes from './routes/ddl-routes'; import { sequelize } from './config/database'; import schemaRoutes from './routes/schema-routes'; import dmlRoutes from './routes/dml-routes'; +import queryRoutes from './routes/query-routes'; +import apikeyRoutes from './routes/apikey-routes'; sequelize .sync({ alter: true }) @@ -30,6 +32,8 @@ apiRouter.use('/users', userRoutes); apiRouter.use('/migrate', ddlRoutes); apiRouter.use('/schemas', schemaRoutes); apiRouter.use('/execute', dmlRoutes); +apiRouter.use('/query', queryRoutes); +apiRouter.use('/apikey', apikeyRoutes); app.use('/api', apiRouter); diff --git a/src/middlewares/apikey-middleware.ts b/src/middlewares/apikey-middleware.ts new file mode 100644 index 0000000..9e7129f --- /dev/null +++ b/src/middlewares/apikey-middleware.ts @@ -0,0 +1,31 @@ +import { Request, Response, NextFunction } from 'express'; +import { errorResponse } from '../utils/response'; +import UserRepository from '../repositories/user-repository'; + +export const apiKeyMiddleware = async ( + req: Request, + res: Response, + next: NextFunction, +) => { + try { + const apiKey = req.headers['x-api-key'] as string; + + if (!apiKey) { + return errorResponse(res, 'Unauthorized', 401); + } + + const user = await UserRepository.findOne( + { apikey: apiKey }, + { attributes: { exclude: ['password'] } }, + ); + if (!user) { + return errorResponse(res, 'Unauthorized', 401); + } + + (req as any).user = user; + next(); + } catch (error) { + console.error('Error:', error); + return errorResponse(res, 'Unauthorized', 401); + } +}; diff --git a/src/middlewares/auth-middleware.ts b/src/middlewares/auth-middleware.ts index 29c9cd3..2ca23e4 100644 --- a/src/middlewares/auth-middleware.ts +++ b/src/middlewares/auth-middleware.ts @@ -2,9 +2,18 @@ import { Request, Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import { errorResponse } from '../utils/response'; import { ENV } from '../config/env'; +import UserRepository from '../repositories/user-repository'; +import Users from '../models/user'; +import { InferAttributes } from 'sequelize'; -export const authMiddleware = ( - req: Request, +type UserType = InferAttributes; + +interface AuthRequest extends Request { + user?: UserType; +} + +export const authMiddleware = async ( + req: AuthRequest, res: Response, next: NextFunction, ) => { @@ -14,8 +23,17 @@ export const authMiddleware = ( return errorResponse(res, 'Unauthorized', 401); } - const decoded = jwt.verify(token, ENV.JWT_SECRET); - (req as any).user = decoded; + const decoded: any = jwt.verify(token, ENV.JWT_SECRET); + const user = await UserRepository.findOne( + { id: decoded.userId }, + { attributes: { exclude: ['password'] } }, + ); + + if (!user) { + return errorResponse(res, 'Unauthorized', 401); + } + + req.user = user; next(); } catch (error) { console.error('Error:', error); diff --git a/src/repositories/user-repository.ts b/src/repositories/user-repository.ts index 108aad0..131d993 100644 --- a/src/repositories/user-repository.ts +++ b/src/repositories/user-repository.ts @@ -1,9 +1,9 @@ import Users from '../models/user'; -import { WhereOptions } from 'sequelize'; +import { FindOptions, WhereOptions } from 'sequelize'; class UserRepository { - static async findOne(condition: WhereOptions) { - return await Users.findOne({ where: condition }); + static async findOne(condition: WhereOptions, options: FindOptions = {}) { + return await Users.findOne({ where: condition, ...options }); } static async insert(data: { @@ -14,6 +14,10 @@ class UserRepository { }) { return await Users.create(data); } + + static async update(condition: WhereOptions, data: Partial) { + return await Users.update(data, { where: condition }); + } } export default UserRepository; diff --git a/src/routes/apikey-routes.ts b/src/routes/apikey-routes.ts new file mode 100644 index 0000000..ca1fd27 --- /dev/null +++ b/src/routes/apikey-routes.ts @@ -0,0 +1,10 @@ +import { Router } from 'express'; +import { authMiddleware } from '../middlewares/auth-middleware'; +import { getApiKey, regenerateApiKey } from '../controllers/apikey-controller'; + +const router = Router(); + +router.get('/', authMiddleware, getApiKey); +router.put('/', authMiddleware, regenerateApiKey); + +export default router; diff --git a/src/routes/auth-routes.ts b/src/routes/auth-routes.ts index 5738137..91556f5 100644 --- a/src/routes/auth-routes.ts +++ b/src/routes/auth-routes.ts @@ -1,5 +1,5 @@ import { Router } from 'express'; -const { registerUser, loginUser } = require('../controllers/user-controller'); +import { registerUser, loginUser } from '../controllers/user-controller'; const router = Router(); diff --git a/src/routes/ddl-routes.ts b/src/routes/ddl-routes.ts index 2a8b552..aebf72c 100644 --- a/src/routes/ddl-routes.ts +++ b/src/routes/ddl-routes.ts @@ -1,8 +1,9 @@ import { Router } from 'express'; -const { migrate } = require('../controllers/ddl-controller'); +import { apiKeyMiddleware } from '../middlewares/apikey-middleware'; +import { migrate } from '../controllers/ddl-controller'; const router = Router(); -router.post('/', migrate); +router.post('/', apiKeyMiddleware, migrate); export default router; diff --git a/src/routes/dml-routes.ts b/src/routes/dml-routes.ts index 23769a4..c8eeaca 100644 --- a/src/routes/dml-routes.ts +++ b/src/routes/dml-routes.ts @@ -1,8 +1,9 @@ import { Router } from 'express'; -const { execute } = require('../controllers/dml-controller'); +import { apiKeyMiddleware } from '../middlewares/apikey-middleware'; +import { execute } from '../controllers/dml-controller'; const router = Router(); -router.post('/', execute); +router.post('/', apiKeyMiddleware, execute); export default router; diff --git a/src/routes/query-routes.ts b/src/routes/query-routes.ts new file mode 100644 index 0000000..d3d36da --- /dev/null +++ b/src/routes/query-routes.ts @@ -0,0 +1,9 @@ +import { Router } from 'express'; +import { apiKeyMiddleware } from '../middlewares/apikey-middleware'; +import { executeQuery } from '../controllers/query-controller'; + +const router = Router(); + +router.post('/', apiKeyMiddleware, executeQuery); + +export default router; diff --git a/src/routes/schema-routes.ts b/src/routes/schema-routes.ts index 7decec3..5f4182a 100644 --- a/src/routes/schema-routes.ts +++ b/src/routes/schema-routes.ts @@ -1,8 +1,9 @@ import { Router } from 'express'; -const { schema } = require('../controllers/schema-controller'); +import { apiKeyMiddleware } from '../middlewares/apikey-middleware'; +import { schema } from '../controllers/schema-controller'; const router = Router(); -router.get('/', schema); +router.get('/', apiKeyMiddleware, schema); export default router; diff --git a/src/routes/user-routes.ts b/src/routes/user-routes.ts index 786aa6c..dfb753d 100644 --- a/src/routes/user-routes.ts +++ b/src/routes/user-routes.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; -const { getUserDetails } = require('../controllers/user-controller'); -const { authMiddleware } = require('../middlewares/auth-middleware'); +import { getUserDetails } from '../controllers/user-controller'; +import { authMiddleware } from '../middlewares/auth-middleware'; const router = Router();