Browse Source

first commit

master
Jeff2hu 1 day ago
commit
48eb53cf42

+ 2
- 0
.env View File

@@ -0,0 +1,2 @@
PORT=9000
MONGO_URI=mongodb://localhost:27017/CompanyAuth

+ 6
- 0
.gitignore View File

@@ -0,0 +1,6 @@
/node_modules
/dist
/build
/coverage
/logs
/temp

+ 2190
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 35
- 0
package.json View File

@@ -0,0 +1,35 @@
{
"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"
}
}

+ 21
- 0
src/controllers/company.controller.ts View File

@@ -0,0 +1,21 @@
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);
}
};

+ 45
- 0
src/controllers/user.controller.ts View File

@@ -0,0 +1,45 @@
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);
}
};

+ 30
- 0
src/index.ts View File

@@ -0,0 +1,30 @@
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}`);
});

+ 18
- 0
src/lib/database.ts View File

@@ -0,0 +1,18 @@
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;

+ 19
- 0
src/middleware/error.ts View File

@@ -0,0 +1,19 @@
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");
}
};

+ 28
- 0
src/models/company.modal.ts View File

@@ -0,0 +1,28 @@
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;

+ 39
- 0
src/models/user.modal.ts View File

@@ -0,0 +1,39 @@
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;

+ 8
- 0
src/routes/company.ts View File

@@ -0,0 +1,8 @@
import { Router } from "express";
import { createCompanyController } from "../controllers/company.controller";
const router = Router();
router.post("/create", createCompanyController);
export default router;

+ 12
- 0
src/routes/user.ts View File

@@ -0,0 +1,12 @@
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;

+ 46
- 0
src/services/company.service.ts View File

@@ -0,0 +1,46 @@
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)}`
);
}
};

+ 25
- 0
src/services/jwtToken.ts View File

@@ -0,0 +1,25 @@
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");
}
};

+ 56
- 0
src/services/user.service.ts View File

@@ -0,0 +1,56 @@
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");
}
};

+ 5
- 0
src/type/api.ts View File

@@ -0,0 +1,5 @@
import { Request } from "express";
export interface TypedRequestBody<T> extends Request {
body: T;
}

+ 10
- 0
src/utils/checkBodyAndThrowError.ts View File

@@ -0,0 +1,10 @@
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`);
});
};

+ 52
- 0
src/utils/response.ts View File

@@ -0,0 +1,52 @@
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;
}
};

+ 14
- 0
tsconfig.json View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

Loading…
Cancel
Save