UNPKG

@topgroup/diginext

Version:

A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.

231 lines (230 loc) 10.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.jwtStrategy = exports.extractAccessTokenInfo = exports.generateJWT = exports.generateRefreshToken = exports.verifyRefreshToken = void 0; // import passport from "passport"; const dayjs_1 = __importDefault(require("dayjs")); const relativeTime_1 = __importDefault(require("dayjs/plugin/relativeTime")); const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const passport_jwt_1 = require("passport-jwt"); const zod_1 = require("zod"); const app_config_1 = require("../../app.config"); dayjs_1.default.extend(relativeTime_1.default); // Zod schema for token validation const TokenSchema = zod_1.z.object({ id: zod_1.z.string().min(1, "User ID is required"), workspaceId: zod_1.z.string().optional(), exp: zod_1.z.number().optional(), }); // Supported algorithms const SUPPORTED_ALGORITHMS = ["HS256", "HS512"]; var cookieExtractor = function (req) { var token = null; if (req && req.cookies) { token = req.cookies["x-auth-cookie"]; } return token; }; const verifyRefreshToken = async (refreshToken) => { try { // console.log("passports > verifyRefreshToken > Verifying token :>>", refreshToken); // Check if token exists if (!refreshToken) { return { error: true, message: "Refresh token is required", }; } // Verify token's secret and decode const decoded = jsonwebtoken_1.default.verify(refreshToken, process.env.JWT_REFRESH_SECRET || "your_refresh_secret"); // Validate decoded token structure const validatedToken = TokenSchema.parse({ id: decoded.id, workspaceId: decoded.workspaceId, exp: decoded.exp, }); // Check token expiration const currentTime = Math.floor(Date.now() / 1000); const isExpired = decoded.exp ? decoded.exp < currentTime : true; if (isExpired) { return { error: true, message: "Refresh token has expired", }; } return { error: false, tokenDetails: { id: validatedToken.id, workspaceId: validatedToken.workspaceId, isExpired: false, }, }; } catch (error) { console.error("passports > verifyRefreshToken > Error :>>", error); // Specific error handling if (error instanceof jsonwebtoken_1.default.TokenExpiredError) { return { error: true, message: "Refresh token has expired", }; } if (error instanceof jsonwebtoken_1.default.JsonWebTokenError) { return { error: true, message: "Invalid refresh token signature", }; } if (error instanceof zod_1.z.ZodError) { return { error: true, message: "Invalid token structure", }; } return { error: true, message: "Invalid refresh token", }; } }; exports.verifyRefreshToken = verifyRefreshToken; // Companion function for generating refresh tokens const generateRefreshToken = (userId, options = {}) => { const { workspaceId, expiresIn = "7d" } = options; return jsonwebtoken_1.default.sign({ id: userId, workspaceId, }, process.env.JWT_REFRESH_SECRET || "your_refresh_secret", { expiresIn, }); }; exports.generateRefreshToken = generateRefreshToken; const generateJWT = async (userId, options = {}) => { const { expiresIn = "2d", workspaceId } = options; const accessToken = jsonwebtoken_1.default.sign({ id: userId, workspaceId, }, process.env.JWT_SECRET || "your_secret", { expiresIn, }); const refreshToken = (0, exports.generateRefreshToken)(userId, { workspaceId }); return { accessToken, refreshToken, }; }; exports.generateJWT = generateJWT; async function extractAccessTokenInfo(tokens, payload) { let { access_token, refresh_token } = tokens; const { exp, workspaceId } = payload; let expiredDate = (0, dayjs_1.default)(new Date(exp * 1000)); let expiredTimestamp = (0, dayjs_1.default)(new Date(exp * 1000)).diff((0, dayjs_1.default)()); let isExpired = expiredTimestamp <= 0; let expToNow = (0, dayjs_1.default)(new Date(exp * 1000)).fromNow(); // console.log("extractAccessTokenInfo() > Expired date >", expiredTimestamp, ">>:", expiredDate.format("YYYY-MM-DD HH:mm:ss")); // console.log(`extractAccessTokenInfo() > Is token expired >>:`, isExpired, `(will expire ${expToNow})`); if (refresh_token) { // If token is < 4 hours to expire, refresh it: const accessTokenExpHourLeft = expiredTimestamp / 60 / 60 / 1000; const { error: isInvalidRefreshToken, tokenDetails: refreshTokenDetails } = await (0, exports.verifyRefreshToken)(refresh_token); // console.log("extractAccessTokenInfo() > accessTokenExpHourLeft :>> ", accessTokenExpHourLeft); // console.log("extractAccessTokenInfo() > refreshTokenDetails :>> ", refreshTokenDetails); // console.log("extractAccessTokenInfo() > isInvalidRefreshToken :>> ", isInvalidRefreshToken); if (isInvalidRefreshToken || refreshTokenDetails.isExpired) return { isExpired: true }; if (accessTokenExpHourLeft < 4) { const userId = payload.id; const { accessToken, refreshToken } = await (0, exports.generateJWT)(userId, { expiresIn: process.env.JWT_EXPIRE_TIME || "2d", workspaceId }); access_token = accessToken; refresh_token = refreshToken; isExpired = false; expiredDate = (0, dayjs_1.default)(new Date(payload.exp * 1000)); expiredTimestamp = (0, dayjs_1.default)(new Date(payload.exp * 1000)).diff((0, dayjs_1.default)()); expToNow = (0, dayjs_1.default)(new Date(payload.exp * 1000)).fromNow(); // console.log(`The token of ${userId} is about to expired ${expToNow} > Refreshed it!`); } } // assign "access_token" info to request: const token = { access_token, refresh_token, expiredTimestamp: expiredTimestamp, expiredDate: expiredDate.toDate(), expiredDateGTM7: expiredDate.format("YYYY-MM-DD HH:mm:ss"), }; return { token, isExpired }; } exports.extractAccessTokenInfo = extractAccessTokenInfo; exports.jwtStrategy = new passport_jwt_1.Strategy({ secretOrKey: app_config_1.Config.grab("JWT_SECRET", "123"), jwtFromRequest: passport_jwt_1.ExtractJwt.fromExtractors([ passport_jwt_1.ExtractJwt.fromAuthHeaderAsBearerToken(), passport_jwt_1.ExtractJwt.fromUrlQueryParameter("access_token"), cookieExtractor, ]), algorithms: SUPPORTED_ALGORITHMS, passReqToCallback: true, }, async function (req, payload, done) { var _a; // console.log(`[1] AUTHENTICATE: jwtStrategy > payload...`, payload); const { DB } = await Promise.resolve().then(() => __importStar(require("../../modules/api/DB"))); let access_token = req.query.access_token || req.cookies["x-auth-cookie"] || ((_a = req.headers.authorization) === null || _a === void 0 ? void 0 : _a.split(" ")[1]); let refresh_token = req.query.refresh_token; // console.log("jwtStrategy > access_token :>> ", access_token); // console.log("jwtStrategy > refresh_token :>> ", refresh_token); // console.log("jwtStrategy > payload :>> ", payload); // console.log(`[1] jwtStrategy > payload.id :>> `, payload.id); // 1. Extract token info const tokenInfo = await extractAccessTokenInfo({ access_token, refresh_token }, payload); // console.log("[DEBUG] jwtStrategy > tokenInfo :>> ", tokenInfo); // validating token... if (tokenInfo === null || tokenInfo === void 0 ? void 0 : tokenInfo.isExpired) return done(JSON.stringify({ status: 0, messages: ["Access token was expired."] }), null); if (!(tokenInfo === null || tokenInfo === void 0 ? void 0 : tokenInfo.token)) return done(JSON.stringify({ status: 0, messages: ["Missing access token."] }), null); // 2. Check if this access token is from a {User} or a {ServiceAccount} let user = await DB.findOne("user", { _id: payload.id }, { populate: ["roles", "workspaces", "activeWorkspace"], ignorable: true }); // console.log("jwtStrategy > user :>> ", user); if (user) { const isAccessTokenExisted = await DB.count("user", { _id: payload.id, "token.access_token": tokenInfo.token.access_token }); if (isAccessTokenExisted === 0) { user = await DB.updateOne("user", { _id: payload.id }, { token: tokenInfo.token }, { populate: ["roles", "workspaces", "activeWorkspace"], }); } // user.token.refresh_token = tokenInfo.token.refresh_token; return done(null, user); } // Maybe it's not a normal user, try looking for {ServiceAccount} user: let serviceAccount = await DB.findOne("service_account", { _id: payload.id }, { populate: ["roles", "workspaces", "activeWorkspace"], ignorable: true }); if (!serviceAccount) return done(JSON.stringify({ status: 0, messages: ["Invalid service account (probably deleted?)."] }), null); return done(null, serviceAccount); }); // passport.use(jwtStrategy);