201 lines
2.4 KiB
JavaScript
201 lines
2.4 KiB
JavaScript
// app/utils/refreshSession.util.js
|
|
|
|
const crypto = require("crypto");
|
|
|
|
const {
|
|
generateRefreshToken,
|
|
verifyRefreshToken,
|
|
} = require("./jwt.util");
|
|
|
|
const refreshSessions = new Map();
|
|
|
|
|
|
// Default = 7 days
|
|
const REFRESH_SESSION_TTL =
|
|
7 * 24 * 60 * 60 * 1000;
|
|
|
|
|
|
const hashRefreshToken = (token) => {
|
|
return crypto
|
|
.createHash("sha256")
|
|
.update(token)
|
|
.digest("hex");
|
|
};
|
|
|
|
const createRefreshSession = (userId) => {
|
|
|
|
// Unique session ID
|
|
const sessionId =
|
|
crypto.randomUUID();
|
|
|
|
|
|
// Create raw Refresh JWT
|
|
const refreshToken =
|
|
generateRefreshToken({
|
|
sub: userId,
|
|
sid: sessionId,
|
|
});
|
|
|
|
|
|
// Hash raw refresh token
|
|
const tokenHash =
|
|
hashRefreshToken(
|
|
refreshToken
|
|
);
|
|
|
|
|
|
// Expiration time
|
|
const expiresAt =
|
|
Date.now() +
|
|
REFRESH_SESSION_TTL;
|
|
|
|
|
|
// Save only HASH in RAM
|
|
refreshSessions.set(
|
|
sessionId,
|
|
{
|
|
userId,
|
|
tokenHash,
|
|
expiresAt,
|
|
}
|
|
);
|
|
|
|
|
|
return {
|
|
refreshToken,
|
|
sessionId,
|
|
};
|
|
};
|
|
|
|
const validateRefreshSession = (
|
|
refreshToken
|
|
) => {
|
|
|
|
if (!refreshToken) {
|
|
return null;
|
|
}
|
|
|
|
|
|
let decoded;
|
|
|
|
try {
|
|
|
|
decoded =
|
|
verifyRefreshToken(
|
|
refreshToken
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
const sessionId =
|
|
decoded.sid;
|
|
|
|
const userId =
|
|
decoded.sub;
|
|
|
|
|
|
if (!sessionId || !userId) {
|
|
return null;
|
|
}
|
|
|
|
|
|
// Get session from RAM
|
|
const session =
|
|
refreshSessions.get(
|
|
sessionId
|
|
);
|
|
|
|
|
|
if (!session) {
|
|
return null;
|
|
}
|
|
|
|
|
|
// Check session expiration
|
|
if (
|
|
Date.now() >
|
|
session.expiresAt
|
|
) {
|
|
|
|
refreshSessions.delete(
|
|
sessionId
|
|
);
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
// Hash received refresh token
|
|
const receivedHash =
|
|
hashRefreshToken(
|
|
refreshToken
|
|
);
|
|
|
|
|
|
// Compare stored hash
|
|
if (
|
|
receivedHash !==
|
|
session.tokenHash
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
|
|
// Extra user check
|
|
if (
|
|
session.userId !==
|
|
userId
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
|
|
return {
|
|
userId,
|
|
sessionId,
|
|
};
|
|
};
|
|
|
|
const deleteRefreshSession = (
|
|
sessionId
|
|
) => {
|
|
|
|
refreshSessions.delete(
|
|
sessionId
|
|
);
|
|
};
|
|
|
|
const deleteAllUserSessions = (
|
|
userId
|
|
) => {
|
|
|
|
for (
|
|
const [sessionId, session]
|
|
of refreshSessions.entries()
|
|
) {
|
|
|
|
if (
|
|
session.userId === userId
|
|
) {
|
|
|
|
refreshSessions.delete(
|
|
sessionId
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
|
|
module.exports = {
|
|
createRefreshSession,
|
|
validateRefreshSession,
|
|
deleteRefreshSession,
|
|
deleteAllUserSessions,
|
|
}; |