diff --git a/config.example.json b/config.example.json index 5ab3c3b..fb98a66 100644 --- a/config.example.json +++ b/config.example.json @@ -2,5 +2,12 @@ "express":{ "port": 3000, "jwtSecret": "" + }, + "pgsql":{ + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "", + "database": "postgres" } } \ No newline at end of file diff --git a/package.json b/package.json index 25a890f..07935ca 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,13 @@ "@tsconfig/node22": "^22.0.2", "@types/express": "^5.0.5", "@types/jsonwebtoken": "^9.0.10", + "@types/pg": "^8.15.6", "typescript": "^5.9.3" }, "dependencies": { "express": "^5.1.0", "jsonwebtoken": "^9.0.2", + "pg": "^8.16.3", "socket.io": "^4.8.1" } } diff --git a/src/config.ts b/src/config.ts index 438651c..b58391b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,12 +5,26 @@ export interface Iconfig { port: number; jwtSecret: string; } + pgsql: { + host: string; + port: number; + user: string; + password: string; + database: string; + } } const defaultConfig = { "express":{ "port": 3000, "jwtSecret": "JwtTianpaoMasterEasyTier000" + }, + "pgsql":{ + "host": "localhost", + "port": 5432, + "user": "root", + "password": "root", + "database": "easytier" } } diff --git a/src/middleware/Auth.ts b/src/middleware/Auth.ts index 81b87d2..340a2b6 100644 --- a/src/middleware/Auth.ts +++ b/src/middleware/Auth.ts @@ -1,16 +1,89 @@ -import { Request,Response,NextFunction } from "express"; +import type{ Request, Response, NextFunction } from "express"; +import crypto from "node:crypto"; +import sql from "../utils/pgsql.js"; +import config from "../config.js"; +import { Jwt } from "../utils/jwt.js"; +import { type JwtPayload } from "jsonwebtoken"; + +const clusters:string[] = [] export class AuthMiddleware { + static Challenge(req: Request, res: Response, next: NextFunction) { + if (!req.headers["user-agent"]?.includes("OpenEasyTier")) { + res.status(403).send() + return; + } + const id = req.query.clusterId; + if (!id) { + res.status(401).json({ + message: "where is your clusterId?", + }); + return; + } - static Challenge(req: Request, res: Response, next: NextFunction) { - const id = req.query.clusterId - if (!id){ - res.status(401).json({ - message: "where is your clusterId?" - }) + if(clusters.includes(id.toString())){ + next(); //缓存命中直接进入下一步 + return; + } + + sql.query(`SELECT "cluster_id" FROM cluster WHERE "cluster_id" = $1`,[id]).then((result) => { + if (result.rows.length == 0) { + res.status(401).json({ + message: "clusterId not found", + }); + return; + }else{ + clusters.push(id.toString()) + } + + next(); //Latest + return; + }); + } + + static Token(req: Request, res: Response, next: NextFunction) { + if (!req.headers["user-agent"]?.includes("OpenEasyTier")) { + res.status(403).send(); + return; + } + const body = req.body; + if ( + !body.clusterId && + ((!body.challenge && !body.signature) || !body.token) + ) { + res.status(401).json({ + message: "parameter miss", + }); + } + sql.query(`SELECT "cluster_id" FROM cluster WHERE "cluster_id" = $1`,[body.clusterId]).then((result) => { + if (result.rows.length == 0) { + res.status(401).json({ + message: "clusterId not found", + }); + return; + } + + if (body.signature) { //校验签名(如果有) + const sign = crypto.createHmac("sha256", config.express.jwtSecret).update(body.challenge).digest("hex").toLowerCase(); + if(body.signature != sign){ + res.status(403).json({ + message: "signature not match", + }); return; } - - next() //Latest } -} \ No newline at end of file + + if (body.token) { //校验token(如果有) + const jwt_token = Jwt.verify(body.token.replace("Bearer ","")) as JwtPayload as { clusterId: string }; + if(jwt_token.clusterId != body.clusterId){ + res.status(403).json({ + message: "token not match", + }); + return; + } + } + + next(); //next function~ + }); + } +} diff --git a/src/router/auth.ts b/src/router/auth.ts index 22dae0f..12b9006 100644 --- a/src/router/auth.ts +++ b/src/router/auth.ts @@ -1,10 +1,24 @@ import { Router } from "express"; import { AuthMiddleware } from "../middleware/Auth.js"; +import { Jwt } from "../utils/jwt.js"; const router = Router(); -router.post('/challenge',AuthMiddleware.Challenge,(req,res)=>{ - -}) +router.get("/challenge", AuthMiddleware.Challenge, (req, res) => { + const clusterId = req.query.clusterId; + const challenge = Jwt.sign({ clusterId }); + res.json({ + challenge:`Bearer ${challenge}`, + }); +}); -export default router; \ No newline at end of file +router.post("/token", AuthMiddleware.Token, (req, res) => { + const body = req.body; + const token = `Bearer ${Jwt.sign({ clusterId: body.clusterId })}`; + res.json({ + token, + ttl: 1000 * 60 * 60 * 24, // TTL:24小时 + }); +}); + +export default router; diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts index 59ccead..a188708 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -1,15 +1,18 @@ import jsonwebtoken from 'jsonwebtoken'; import config from '../config.js'; export class Jwt { - static sign(payload: object){ + static sign(payload: object,expiresIn: number = 60 * 60 * 24){ return jsonwebtoken.sign(payload,config.express.jwtSecret,{ - expiresIn: 60 * 60 * 24, // 24 hours - algorithm: "HS256" + expiresIn: expiresIn, // 24 hours + algorithm: "HS256", + issuer: "OpenEasyTierMaster" }) } static verify(token: string) { - return jsonwebtoken.verify(token,config.express.jwtSecret) + return jsonwebtoken.verify(token,config.express.jwtSecret,{ + issuer: "OpenEasyTierMaster" + }) } } \ No newline at end of file diff --git a/src/utils/pgsql.ts b/src/utils/pgsql.ts new file mode 100644 index 0000000..b13f592 --- /dev/null +++ b/src/utils/pgsql.ts @@ -0,0 +1,6 @@ +import { Pool } from "pg"; +import config from "../config.js"; + +const pool = new Pool(config.pgsql); + +export default pool; \ No newline at end of file