PORT=9000 | |||||
MONGO_URI=mongodb://localhost:27017/CompanyAuth |
/node_modules | |||||
/dist | |||||
/build | |||||
/coverage | |||||
/logs | |||||
/temp |
{ | |||||
"name": "authserver", | |||||
"version": "1.0.0", | |||||
"description": "", | |||||
"main": "index.js", | |||||
"scripts": { | |||||
"start": "node dist/index.js", | |||||
"dev": "nodemon src/index.ts", | |||||
"build": "tsc", | |||||
"test": "echo \"Error: no test specified\" && exit 1" | |||||
}, | |||||
"keywords": [], | |||||
"author": "", | |||||
"license": "ISC", | |||||
"dependencies": { | |||||
"bcrypt": "^5.1.1", | |||||
"cors": "^2.8.5", | |||||
"dotenv": "^16.5.0", | |||||
"express": "^5.1.0", | |||||
"jsonwebtoken": "^9.0.2", | |||||
"mongoose": "^8.14.1", | |||||
"uuid": "^11.1.0" | |||||
}, | |||||
"devDependencies": { | |||||
"@types/bcrypt": "^5.0.2", | |||||
"@types/cors": "^2.8.18", | |||||
"@types/express": "^5.0.1", | |||||
"@types/jsonwebtoken": "^9.0.9", | |||||
"@types/mongoose": "^5.11.97", | |||||
"@types/node": "^22.15.16", | |||||
"nodemon": "^3.1.10", | |||||
"ts-node": "^10.9.2", | |||||
"typescript": "^5.8.3" | |||||
} | |||||
} |
import { NextFunction, Request, Response } from "express"; | |||||
import { CompanyType } from "../models/company.modal"; | |||||
import { createCompany } from "../services/company.service"; | |||||
import { successResponse } from "../utils/response"; | |||||
export const createCompanyController = async ( | |||||
req: Request, | |||||
res: Response, | |||||
next: NextFunction | |||||
) => { | |||||
try { | |||||
const { name } = req.body; | |||||
if (!name) throw new Error("400:Name is required"); | |||||
const company = await createCompany(name); | |||||
successResponse<CompanyType>(res, company); | |||||
} catch (error) { | |||||
next(error); | |||||
} | |||||
}; |
import { NextFunction, Request, Response } from "express"; | |||||
import { CreateJwtTokenRequest, UserType } from "../models/user.modal"; | |||||
import { createJWTToken, createUserToken } from "../services/user.service"; | |||||
import { TypedRequestBody } from "../type/api"; | |||||
import { checkBodyAndThrowError } from "../utils/checkBodyAndThrowError"; | |||||
import { successResponse } from "../utils/response"; | |||||
export const getUserTokenController = async ( | |||||
req: Request, | |||||
res: Response, | |||||
next: NextFunction | |||||
) => { | |||||
try { | |||||
const token = req.headers.authorization?.split(" ")[1]; | |||||
const { companyName } = req.params; | |||||
if (!token) throw new Error("401:Token is required"); | |||||
if (!companyName) throw new Error("400:companyName is required"); | |||||
const user = await createUserToken(token, companyName); | |||||
successResponse<UserType>(res, user); | |||||
} catch (error) { | |||||
next(error); | |||||
} | |||||
}; | |||||
export const createJwtTokenController = async ( | |||||
req: TypedRequestBody<CreateJwtTokenRequest>, | |||||
res: Response, | |||||
next: NextFunction | |||||
) => { | |||||
try { | |||||
checkBodyAndThrowError(req.body, [ | |||||
"name", | |||||
"amount", | |||||
"lobby", | |||||
"companyName", | |||||
]); | |||||
const token = await createJWTToken(req.body); | |||||
successResponse<string>(res, token); | |||||
} catch (error) { | |||||
next(error); | |||||
} | |||||
}; |
import cors from "cors"; | |||||
import dotenv from "dotenv"; | |||||
import express, { Express } from "express"; | |||||
import connectDB from "./lib/database"; | |||||
import { errorHandler } from "./middleware/error"; | |||||
import companyRoutes from "./routes/company"; | |||||
import userRoutes from "./routes/user"; | |||||
dotenv.config(); | |||||
const app: Express = express(); | |||||
const port = process.env.PORT || 3000; | |||||
connectDB(); | |||||
app.use(cors()); | |||||
app.use(express.json()); | |||||
app.use("/api/company", companyRoutes); | |||||
app.use("/api/user", userRoutes); | |||||
app.use(errorHandler); | |||||
app.get("/health", (req, res) => { | |||||
res.status(200).json({ status: "ok" }); | |||||
}); | |||||
app.listen(port, () => { | |||||
console.log(`⚡️ Server running at http://localhost:${port}`); | |||||
}); |
import dotenv from "dotenv"; | |||||
import mongoose from "mongoose"; | |||||
dotenv.config(); | |||||
const connectDB = async (): Promise<void> => { | |||||
try { | |||||
const mongoUri = | |||||
process.env.MONGO_URI || "mongodb://localhost:27017/companyAuth"; | |||||
await mongoose.connect(mongoUri); | |||||
console.log("✅ MongoDB Connected"); | |||||
} catch (error) { | |||||
console.error("❌ MongoDB Connection Error:", error); | |||||
process.exit(1); | |||||
} | |||||
}; | |||||
export default connectDB; |
import { NextFunction, Request, Response } from "express"; | |||||
import { errorResponse } from "../utils/response"; | |||||
export const errorHandler = ( | |||||
err: Error, | |||||
req: Request, | |||||
res: Response, | |||||
next: NextFunction | |||||
) => { | |||||
if (err instanceof Error) { | |||||
console.log(err); | |||||
const errorCode = err.message.split(":")[0]; | |||||
const errorMessage = err.message.split(":")[1]; | |||||
errorResponse(res, errorCode, errorMessage); | |||||
} else { | |||||
console.log(err); | |||||
errorResponse(res, "9999", "Internal server error"); | |||||
} | |||||
}; |
import mongoose from "mongoose"; | |||||
export interface CreateCompanyRequest { | |||||
name: string; | |||||
} | |||||
export interface CompanyOptions { | |||||
name?: string; | |||||
secretKey?: string; | |||||
} | |||||
export interface CompanyType extends mongoose.Document { | |||||
name: string; | |||||
secretKey: string; | |||||
createdAt: Date; | |||||
updatedAt: Date; | |||||
} | |||||
const companySchema = new mongoose.Schema({ | |||||
name: { type: String, required: true, unique: true }, | |||||
secretKey: { type: String, required: true, unique: true }, | |||||
createdAt: { type: Date, default: Date.now }, | |||||
updatedAt: { type: Date, default: Date.now }, | |||||
}); | |||||
const Company = mongoose.model("Company", companySchema); | |||||
export default Company; |
import mongoose from "mongoose"; | |||||
export interface UserPayloadRequest { | |||||
name: string; | |||||
amount: number; | |||||
lobby: string; | |||||
} | |||||
export interface UserPayload extends UserPayloadRequest { | |||||
iat: number; | |||||
exp: number; | |||||
} | |||||
export interface CreateJwtTokenRequest extends UserPayload { | |||||
companyName: string; | |||||
} | |||||
export interface UserType extends mongoose.Document { | |||||
name: string; | |||||
amount: number; | |||||
lobby: string; | |||||
token: string; | |||||
count: number; | |||||
} | |||||
const userSchema = new mongoose.Schema({ | |||||
name: { type: String, required: true }, | |||||
amount: { type: Number, required: true }, | |||||
lobby: { type: String, required: true }, | |||||
token: { type: String, required: true }, | |||||
count: { type: Number, default: 0 }, | |||||
record: { type: Array, default: [] }, | |||||
createdAt: { type: Date, default: Date.now }, | |||||
updatedAt: { type: Date, default: Date.now }, | |||||
}); | |||||
const User = mongoose.model("User", userSchema); | |||||
export default User; |
import { Router } from "express"; | |||||
import { createCompanyController } from "../controllers/company.controller"; | |||||
const router = Router(); | |||||
router.post("/create", createCompanyController); | |||||
export default router; |
import { Router } from "express"; | |||||
import { | |||||
createJwtTokenController, | |||||
getUserTokenController, | |||||
} from "../controllers/user.controller"; | |||||
const router = Router(); | |||||
router.post("/createJwtToken", createJwtTokenController); | |||||
router.get("/getUserToken/:companyName", getUserTokenController); | |||||
export default router; |
import { v4 as uuidv4 } from "uuid"; | |||||
import Company, { CompanyOptions } from "../models/company.modal"; | |||||
export const createCompany = async (name: string) => { | |||||
try { | |||||
const existingCompanyName = await getCompanyByOptions({ name }); | |||||
if (existingCompanyName) throw "409:Company already exists"; | |||||
const secretKey = uuidv4(); | |||||
const existingCompanySecretKey = await getCompanyByOptions({ secretKey }); | |||||
if (existingCompanySecretKey) throw "409:Company secret key already exists"; | |||||
const newCompany = await Company.create({ | |||||
name, | |||||
secretKey, | |||||
}); | |||||
return newCompany; | |||||
} catch (error) { | |||||
throw new Error((error as string) || "500:Failed to create company"); | |||||
} | |||||
}; | |||||
export const getCompanyById = async (id: string) => { | |||||
try { | |||||
const company = await Company.findById(id); | |||||
return company; | |||||
} catch (error) { | |||||
throw new Error( | |||||
((error as string) || "500:Failed to get company by") + `id: ${id}` | |||||
); | |||||
} | |||||
}; | |||||
export const getCompanyByOptions = async (options: CompanyOptions) => { | |||||
try { | |||||
const company = await Company.findOne(options); | |||||
return company; | |||||
} catch (error) { | |||||
throw new Error( | |||||
((error as string) || "500:Failed to get company by") + | |||||
`options: ${JSON.stringify(options)}` | |||||
); | |||||
} | |||||
}; |
import jwt from "jsonwebtoken"; | |||||
import { UserPayload, UserPayloadRequest } from "../models/user.modal"; | |||||
export const verifyToken = (token: string, secret: string) => { | |||||
try { | |||||
const decoded = jwt.verify(token, secret); | |||||
if (!decoded) throw "401:Invalid token"; | |||||
return decoded as UserPayload; | |||||
} catch (error) { | |||||
if (error instanceof jwt.TokenExpiredError) { | |||||
throw new Error("401:Token expired"); | |||||
} | |||||
throw new Error((error as string) || "500:Failed to verify token"); | |||||
} | |||||
}; | |||||
export const generateToken = (payload: UserPayloadRequest, secret: string) => { | |||||
try { | |||||
const token = jwt.sign(payload, secret, { expiresIn: "1h" }); | |||||
return token; | |||||
} catch (error) { | |||||
throw new Error((error as string) || "500:Failed to generate token"); | |||||
} | |||||
}; |
import { Request } from "express"; | |||||
import { v4 as uuidv4 } from "uuid"; | |||||
import User, { UserPayload } from "../models/user.modal"; | |||||
import { getCompanyByOptions } from "./company.service"; | |||||
import { generateToken, verifyToken } from "./jwtToken"; | |||||
export const createUserToken = async (token: string, companyName: string) => { | |||||
try { | |||||
const company = await getCompanyByOptions({ name: companyName }); | |||||
if (!company) throw "404:Company not found"; | |||||
const user = verifyToken(token, company.secretKey); | |||||
if (!user) throw "0001:User not found"; | |||||
const userModel = await createUserModel(user); | |||||
return userModel; | |||||
} catch (error) { | |||||
if (error instanceof Error) throw error.message; | |||||
throw new Error((error as string) || "500:Failed to create user token"); | |||||
} | |||||
}; | |||||
export const createJWTToken = async (body: Request["body"]) => { | |||||
try { | |||||
const { name, amount, lobby, companyName } = body; | |||||
const company = await getCompanyByOptions({ name: companyName }); | |||||
if (!company) throw "404:Company not found"; | |||||
const token = generateToken({ name, amount, lobby }, company.secretKey); | |||||
return token; | |||||
} catch (error) { | |||||
if (error instanceof Error) throw error.message; | |||||
throw new Error((error as string) || "500:Failed to create JWT token"); | |||||
} | |||||
}; | |||||
export const createUserModel = async (user: UserPayload) => { | |||||
try { | |||||
const { iat, exp, ...userPayload } = user; | |||||
const token = uuidv4(); | |||||
const newUser = { | |||||
...userPayload, | |||||
token: token, | |||||
count: 0, | |||||
}; | |||||
const userModel = await User.create(newUser); | |||||
return userModel; | |||||
} catch (error) { | |||||
throw new Error((error as string) || "500:Failed to create user model"); | |||||
} | |||||
}; |
import { Request } from "express"; | |||||
export interface TypedRequestBody<T> extends Request { | |||||
body: T; | |||||
} |
import { Request } from "express"; | |||||
export const checkBodyAndThrowError = ( | |||||
body: Request["body"], | |||||
requiredFields: string[] | |||||
) => { | |||||
requiredFields.forEach((field) => { | |||||
if (!body[field]) throw new Error(`400:${field} is required`); | |||||
}); | |||||
}; |
import { Response } from "express"; | |||||
export const successResponse = <T>(res: Response, data: T) => { | |||||
res.status(200).json({ | |||||
message: "Success", | |||||
data, | |||||
}); | |||||
}; | |||||
export const errorResponse = ( | |||||
res: Response, | |||||
errorCode: string, | |||||
message: string | |||||
) => { | |||||
switch (errorCode) { | |||||
case "400": | |||||
res.status(400).json({ | |||||
message, | |||||
}); | |||||
break; | |||||
case "401": | |||||
res.status(401).json({ | |||||
message, | |||||
}); | |||||
break; | |||||
case "403": | |||||
res.status(403).json({ | |||||
message, | |||||
}); | |||||
break; | |||||
case "404": | |||||
res.status(404).json({ | |||||
message, | |||||
}); | |||||
break; | |||||
case "409": | |||||
res.status(409).json({ | |||||
message, | |||||
}); | |||||
break; | |||||
case "9999": | |||||
res.status(500).json({ | |||||
message, | |||||
}); | |||||
break; | |||||
default: | |||||
res.status(500).json({ | |||||
message, | |||||
}); | |||||
break; | |||||
} | |||||
}; |
{ | |||||
"compilerOptions": { | |||||
"target": "es2016", | |||||
"module": "commonjs", | |||||
"outDir": "./dist", | |||||
"rootDir": "./src", | |||||
"strict": true, | |||||
"esModuleInterop": true, | |||||
"skipLibCheck": true, | |||||
"forceConsistentCasingInFileNames": true | |||||
}, | |||||
"include": ["src/**/*"], | |||||
"exclude": ["node_modules"] | |||||
} |