sanket3280/code-execution
0
1const jwt = require('jsonwebtoken');2const { config } = require('../config/env.config');3 4/**5 * Challenge Token Management Utility6 * Handles generation and verification of challenge-specific JWT tokens7 * with 24-hour expiry and automatic rotation support8 */9 10/**11 * Generate a participant token for challenge access12 * @param {String} userId - User's MongoDB ObjectId13 * @param {String} challengeId - Challenge's MongoDB ObjectId14 * @param {Object} options - Additional options15 * @returns {String} JWT token16 */17function generateParticipantToken(userId, challengeId, options = {}) {18 const payload = {19 type: 'participant',20 userId: userId.toString(),21 challengeId: challengeId.toString(),22 role: options.isCreator ? 'creator' : 'participant',23 permissions: {24 canSolveProblems: true,25 canViewProblems: true,26 canSpectate: options.hasSpectatorAccess || false27 },28 iat: Math.floor(Date.now() / 1000)29 };30 31 // Token expires in 24 hours32 const token = jwt.sign(payload, config.jwt.secret, {33 expiresIn: '24h'34 });35 36 return token;37}38 39/**40 * Generate a spectator token for viewing participant code41 * @param {String} userId - User's MongoDB ObjectId42 * @param {String} challengeId - Challenge's MongoDB ObjectId43 * @param {Object} permissions - Spectator permissions44 * @returns {String} JWT token45 */46function generateSpectatorToken(userId, challengeId, permissions = {}) {47 const payload = {48 type: 'spectator',49 userId: userId.toString(),50 challengeId: challengeId.toString(),51 role: permissions.isCreator ? 'creator' : 'spectator',52 permissions: {53 canViewAllParticipants: permissions.canViewAllParticipants !== false,54 canViewCodeHistory: permissions.canViewCodeHistory || false,55 canFlagSuspicious: permissions.canFlagSuspicious || false,56 canViewSolutions: false, // Spectators cannot view solutions57 canViewJudgeOutput: false, // Spectators cannot view judge output58 canViewErrors: false // Spectators cannot view error details59 },60 iat: Math.floor(Date.now() / 1000)61 };62 63 // Token expires in 24 hours64 const token = jwt.sign(payload, config.jwt.secret, {65 expiresIn: '24h'66 });67 68 return token;69}70 71/**72 * Verify and decode a challenge token73 * @param {String} token - JWT token to verify74 * @returns {Object} Decoded token payload75 * @throws {Error} If token is invalid or expired76 */77function verifyToken(token) {78 try {79 const decoded = jwt.verify(token, config.jwt.secret);80 return decoded;81 } catch (error) {82 if (error.name === 'TokenExpiredError') {83 const err = new Error('Token has expired');84 err.code = 'TOKEN_EXPIRED';85 throw err;86 }87 if (error.name === 'JsonWebTokenError') {88 const err = new Error('Invalid token');89 err.code = 'TOKEN_INVALID';90 throw err;91 }92 throw error;93 }94}95 96/**97 * Check if token is near expiry (< 24 hours remaining)98 * @param {String} token - JWT token to check99 * @returns {Boolean} True if token expires in less than 24 hours100 */101function isTokenNearExpiry(token) {102 try {103 const decoded = jwt.decode(token);104 if (!decoded || !decoded.exp) return true;105 106 const now = Math.floor(Date.now() / 1000);107 const timeUntilExpiry = decoded.exp - now;108 109 // Return true if less than 24 hours (86400 seconds) remaining110 return timeUntilExpiry < 86400;111 } catch (error) {112 return true;113 }114}115 116/**117 * Decode token without verification (for inspection)118 * @param {String} token - JWT token to decode119 * @returns {Object|null} Decoded token payload or null120 */121function decodeToken(token) {122 try {123 return jwt.decode(token);124 } catch (error) {125 return null;126 }127}128 129/**130 * Add token to revocation list in Redis131 * @param {String} token - JWT token to revoke132 * @param {Number} ttl - Time to live in seconds (default 24 hours)133 */134async function revokeToken(token) {135 try {136 const { client: redisClient } = require('./redis');137 if (!redisClient || !redisClient.isOpen) {138 console.warn('Redis not available for token revocation');139 return false;140 }141 142 // Store revoked token with 24 hour TTL143 await redisClient.setEx(`revoked:${token}`, 86400, 'true');144 return true;145 } catch (error) {146 console.error('Error revoking token:', error);147 return false;148 }149}150 151/**152 * Check if token is in revocation list153 * @param {String} token - JWT token to check154 * @returns {Boolean} True if token is revoked155 */156async function isTokenRevoked(token) {157 try {158 const { client: redisClient } = require('./redis');159 if (!redisClient || !redisClient.isOpen) {160 return false; // If Redis is down, don't block valid tokens161 }162 163 const isRevoked = await redisClient.get(`revoked:${token}`);164 return !!isRevoked;165 } catch (error) {166 console.error('Error checking token revocation:', error);167 return false;168 }169}170 171module.exports = {172 generateParticipantToken,173 generateSpectatorToken,174 verifyToken,175 isTokenNearExpiry,176 decodeToken,177 revokeToken,178 isTokenRevoked179};180 