@topgroup/diginext
Version:
A BUILD SERVER & CLI to deploy apps to any Kubernetes clusters.
231 lines (230 loc) • 10.7 kB
JavaScript
;
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);