2022-06-23 21:22:51 +02:00
|
|
|
const redis = require("../redis/init")
|
2022-01-25 23:54:50 +01:00
|
|
|
const { v4: uuidv4 } = require("uuid")
|
2022-08-04 17:06:59 +02:00
|
|
|
const { logWarn } = require("../logging")
|
2022-08-04 20:03:50 +02:00
|
|
|
const env = require("../environment")
|
2021-07-06 19:10:04 +02:00
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
interface Session {
|
|
|
|
key: string
|
|
|
|
userId: string
|
|
|
|
sessionId: string
|
|
|
|
lastAccessedAt: string
|
|
|
|
createdAt: string
|
|
|
|
csrfToken?: string
|
|
|
|
value: string
|
|
|
|
}
|
|
|
|
|
|
|
|
type SessionKey = { key: string }[]
|
|
|
|
|
2021-12-03 12:17:48 +01:00
|
|
|
// a week in seconds
|
|
|
|
const EXPIRY_SECONDS = 86400 * 7
|
2021-07-06 19:10:04 +02:00
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
function makeSessionID(userId: string, sessionId: string) {
|
|
|
|
return `${userId}/${sessionId}`
|
2021-07-06 19:10:04 +02:00
|
|
|
}
|
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
export async function getSessionsForUser(userId: string) {
|
|
|
|
const client = await redis.getSessionClient()
|
|
|
|
const sessions = await client.scan(userId)
|
|
|
|
return sessions.map((session: Session) => session.value)
|
2021-07-06 19:10:04 +02:00
|
|
|
}
|
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
export async function invalidateSessions(
|
|
|
|
userId: string,
|
|
|
|
opts: { sessionIds?: string[]; reason?: string } = {}
|
|
|
|
) {
|
2022-05-24 23:57:32 +02:00
|
|
|
try {
|
2022-08-05 18:13:03 +02:00
|
|
|
const reason = opts?.reason || "unknown"
|
|
|
|
let sessionIds: string[] = opts.sessionIds || []
|
|
|
|
let sessions: SessionKey
|
2022-05-24 23:57:32 +02:00
|
|
|
|
|
|
|
// If no sessionIds, get all the sessions for the user
|
|
|
|
if (!sessionIds) {
|
|
|
|
sessions = await getSessionsForUser(userId)
|
|
|
|
sessions.forEach(
|
2022-08-05 18:13:03 +02:00
|
|
|
(session: any) =>
|
2022-05-24 23:57:32 +02:00
|
|
|
(session.key = makeSessionID(session.userId, session.sessionId))
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// use the passed array of sessionIds
|
2022-08-05 18:13:03 +02:00
|
|
|
sessionIds = Array.isArray(sessionIds) ? sessionIds : [sessionIds]
|
|
|
|
sessions = sessionIds.map((sessionId: string) => ({
|
2022-05-24 23:57:32 +02:00
|
|
|
key: makeSessionID(userId, sessionId),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2022-08-04 20:03:50 +02:00
|
|
|
if (sessions && sessions.length > 0) {
|
|
|
|
const client = await redis.getSessionClient()
|
|
|
|
const promises = []
|
|
|
|
for (let session of sessions) {
|
|
|
|
promises.push(client.delete(session.key))
|
|
|
|
}
|
|
|
|
if (!env.isTest()) {
|
|
|
|
logWarn(
|
2022-08-05 18:13:03 +02:00
|
|
|
`Invalidating sessions for ${userId} (reason: ${reason}) - ${sessions
|
2022-08-04 20:03:50 +02:00
|
|
|
.map(session => session.key)
|
|
|
|
.join(", ")}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
await Promise.all(promises)
|
2022-05-24 23:57:32 +02:00
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error(`Error invalidating sessions: ${err}`)
|
2021-07-06 19:10:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
export async function createASession(userId: string, session: Session) {
|
2022-04-07 13:32:00 +02:00
|
|
|
// invalidate all other sessions
|
2022-08-05 18:13:03 +02:00
|
|
|
await invalidateSessions(userId, { reason: "creation" })
|
2022-04-07 13:32:00 +02:00
|
|
|
|
|
|
|
const client = await redis.getSessionClient()
|
|
|
|
const sessionId = session.sessionId
|
|
|
|
if (!session.csrfToken) {
|
|
|
|
session.csrfToken = uuidv4()
|
|
|
|
}
|
|
|
|
session = {
|
2022-08-05 18:13:03 +02:00
|
|
|
...session,
|
2022-04-07 13:32:00 +02:00
|
|
|
createdAt: new Date().toISOString(),
|
|
|
|
lastAccessedAt: new Date().toISOString(),
|
|
|
|
userId,
|
|
|
|
}
|
|
|
|
await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS)
|
|
|
|
}
|
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
export async function updateSessionTTL(session: Session) {
|
2021-07-06 19:10:04 +02:00
|
|
|
const client = await redis.getSessionClient()
|
2021-07-08 00:29:19 +02:00
|
|
|
const key = makeSessionID(session.userId, session.sessionId)
|
2021-07-08 00:30:14 +02:00
|
|
|
session.lastAccessedAt = new Date().toISOString()
|
2021-07-08 00:29:19 +02:00
|
|
|
await client.store(key, session, EXPIRY_SECONDS)
|
2021-07-06 19:10:04 +02:00
|
|
|
}
|
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
export async function endSession(userId: string, sessionId: string) {
|
2021-07-06 19:10:04 +02:00
|
|
|
const client = await redis.getSessionClient()
|
|
|
|
await client.delete(makeSessionID(userId, sessionId))
|
|
|
|
}
|
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
export async function getSession(userId: string, sessionId: string) {
|
2021-07-06 19:10:04 +02:00
|
|
|
try {
|
|
|
|
const client = await redis.getSessionClient()
|
|
|
|
return client.get(makeSessionID(userId, sessionId))
|
|
|
|
} catch (err) {
|
|
|
|
// if can't get session don't error, just don't return anything
|
2022-05-25 14:02:15 +02:00
|
|
|
console.error(err)
|
2021-07-06 19:10:04 +02:00
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 18:13:03 +02:00
|
|
|
export async function getAllSessions() {
|
2021-07-06 19:10:04 +02:00
|
|
|
const client = await redis.getSessionClient()
|
2021-07-08 00:29:19 +02:00
|
|
|
const sessions = await client.scan()
|
2022-08-05 18:13:03 +02:00
|
|
|
return sessions.map((session: Session) => session.value)
|
2021-07-06 19:10:04 +02:00
|
|
|
}
|