Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/controllers/apikey-controller.ts
Original file line number Diff line number Diff line change
@@ -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<Users>;

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);
}
};
29 changes: 29 additions & 0 deletions src/controllers/query-controller.ts
Original file line number Diff line number Diff line change
@@ -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);
}
};
5 changes: 3 additions & 2 deletions src/controllers/schema-controller.ts
Original file line number Diff line number Diff line change
@@ -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);
}
};
24 changes: 10 additions & 14 deletions src/controllers/user-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Users>;

interface AuthRequest extends Request {
user?: UserType;
}

export const registerUser = async (req: Request, res: Response) => {
try {
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand All @@ -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);

Expand Down
31 changes: 31 additions & 0 deletions src/middlewares/apikey-middleware.ts
Original file line number Diff line number Diff line change
@@ -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);
}
};
26 changes: 22 additions & 4 deletions src/middlewares/auth-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Users>;

interface AuthRequest extends Request {
user?: UserType;
}

export const authMiddleware = async (
req: AuthRequest,
res: Response,
next: NextFunction,
) => {
Expand All @@ -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);
Expand Down
10 changes: 7 additions & 3 deletions src/repositories/user-repository.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -14,6 +14,10 @@ class UserRepository {
}) {
return await Users.create(data);
}

static async update(condition: WhereOptions, data: Partial<Users>) {
return await Users.update(data, { where: condition });
}
}

export default UserRepository;
10 changes: 10 additions & 0 deletions src/routes/apikey-routes.ts
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion src/routes/auth-routes.ts
Original file line number Diff line number Diff line change
@@ -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();

Expand Down
5 changes: 3 additions & 2 deletions src/routes/ddl-routes.ts
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 3 additions & 2 deletions src/routes/dml-routes.ts
Original file line number Diff line number Diff line change
@@ -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;
9 changes: 9 additions & 0 deletions src/routes/query-routes.ts
Original file line number Diff line number Diff line change
@@ -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;
5 changes: 3 additions & 2 deletions src/routes/schema-routes.ts
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 2 additions & 2 deletions src/routes/user-routes.ts
Original file line number Diff line number Diff line change
@@ -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();

Expand Down