You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
4.9 KiB
201 lines
4.9 KiB
import assert from 'assert'; |
|
import expressSession, { |
|
SessionData, |
|
Store as BaseSessionStore, |
|
} from 'express-session'; |
|
|
|
import { DELETED } from './lib/consts'; |
|
|
|
import { getLocalHostUUID, query, timestamp, write } from './lib/accessModule'; |
|
import { getSessionSecret } from './lib/getSessionSecret'; |
|
import { stderr, stdout, stdoutVar, uuid } from './lib/shell'; |
|
|
|
const DEFAULT_COOKIE_ORIGINAL_MAX_AGE = 28800000; // 8 hours |
|
|
|
export class SessionStore extends BaseSessionStore { |
|
constructor(options = {}) { |
|
super(options); |
|
} |
|
|
|
public async destroy( |
|
sid: string, |
|
done?: ((err?: unknown) => void) | undefined, |
|
): Promise<void> { |
|
stdout(`Destroy session ${sid}`); |
|
|
|
try { |
|
const wcode = await write( |
|
`UPDATE sessions |
|
SET session_salt = '${DELETED}', modified_date = '${timestamp()}' |
|
WHERE session_uuid = '${sid}';`, |
|
); |
|
|
|
assert(wcode === 0, `Write exited with code ${wcode}`); |
|
} catch (error) { |
|
stderr( |
|
`Failed to complete DB write in destroy session ${sid}; CAUSE: ${error}`, |
|
); |
|
|
|
return done?.call(null, error); |
|
} |
|
|
|
return done?.call(null); |
|
} |
|
|
|
public async get( |
|
sid: string, |
|
done: (err: unknown, session?: SessionData | null | undefined) => void, |
|
): Promise<void> { |
|
stdout(`Get session ${sid}`); |
|
|
|
let rows: [ |
|
sessionUuid: string, |
|
userUuid: string, |
|
sessionModifiedDate: string, |
|
][]; |
|
|
|
try { |
|
rows = await query( |
|
`SELECT |
|
s.session_uuid, |
|
u.user_uuid, |
|
s.modified_date |
|
FROM sessions AS s |
|
JOIN users AS u |
|
ON s.session_user_uuid = u.user_uuid |
|
WHERE s.session_salt != '${DELETED}' |
|
AND s.session_uuid = '${sid}';`, |
|
); |
|
} catch (queryError) { |
|
return done(queryError); |
|
} |
|
|
|
if (!rows.length) { |
|
return done(null); |
|
} |
|
|
|
const { |
|
0: [, userUuid, sessionModifiedDate], |
|
} = rows; |
|
|
|
const cookieMaxAge = |
|
SessionStore.calculateCookieMaxAge(sessionModifiedDate); |
|
|
|
const data: SessionData = { |
|
cookie: { |
|
maxAge: cookieMaxAge, |
|
originalMaxAge: DEFAULT_COOKIE_ORIGINAL_MAX_AGE, |
|
}, |
|
passport: { user: userUuid }, |
|
}; |
|
|
|
return done(null, data); |
|
} |
|
|
|
public async set( |
|
sid: string, |
|
session: SessionData, |
|
done?: ((err?: unknown) => void) | undefined, |
|
): Promise<void> { |
|
stdoutVar({ session }, `Set session ${sid}`); |
|
|
|
const { passport: { user: userUuid } = {} } = session; |
|
|
|
try { |
|
assert.ok(userUuid, 'Missing user identifier'); |
|
|
|
const localHostUuid = getLocalHostUUID(); |
|
const modifiedDate = timestamp(); |
|
|
|
const wcode = await write( |
|
`INSERT INTO |
|
sessions ( |
|
session_uuid, |
|
session_host_uuid, |
|
session_user_uuid, |
|
session_salt, |
|
modified_date |
|
) |
|
VALUES |
|
( |
|
'${sid}', |
|
'${localHostUuid}', |
|
'${userUuid}', |
|
'', |
|
'${modifiedDate}' |
|
) |
|
ON CONFLICT (session_uuid) |
|
DO UPDATE SET modified_date = '${modifiedDate}';`, |
|
); |
|
|
|
assert(wcode === 0, `Write exited with code ${wcode}`); |
|
} catch (error) { |
|
stderr( |
|
`Failed to complete DB write in set session ${sid}; CAUSE: ${error}`, |
|
); |
|
|
|
return done?.call(null, error); |
|
} |
|
|
|
return done?.call(null); |
|
} |
|
|
|
public async touch( |
|
sid: string, |
|
session: SessionData, |
|
done?: ((err?: unknown) => void) | undefined, |
|
): Promise<void> { |
|
stdoutVar({ session }, `Touch session ${sid}`); |
|
|
|
try { |
|
const wcode = await write( |
|
`UPDATE sessions |
|
SET modified_date = '${timestamp()}' |
|
WHERE session_uuid = '${sid}';`, |
|
); |
|
|
|
assert(wcode === 0, `Write exited with code ${wcode}`); |
|
} catch (error) { |
|
stderr( |
|
`Failed to complete DB write in touch session ${sid}; CAUSE: ${error}`, |
|
); |
|
|
|
return done?.call(null, error); |
|
} |
|
|
|
return done?.call(null); |
|
} |
|
|
|
public static calculateCookieMaxAge( |
|
sessionModifiedDate: string, |
|
cookieOriginalMaxAge: number = DEFAULT_COOKIE_ORIGINAL_MAX_AGE, |
|
) { |
|
const sessionModifiedEpoch = Date.parse(sessionModifiedDate); |
|
const sessionDeadlineEpoch = sessionModifiedEpoch + cookieOriginalMaxAge; |
|
const cookieMaxAge = sessionDeadlineEpoch - Date.now(); |
|
|
|
stdoutVar({ sessionModifiedDate, sessionDeadlineEpoch, cookieMaxAge }); |
|
|
|
return cookieMaxAge; |
|
} |
|
} |
|
|
|
export default (async () => |
|
expressSession({ |
|
cookie: { |
|
httpOnly: true, |
|
maxAge: DEFAULT_COOKIE_ORIGINAL_MAX_AGE, |
|
secure: false, |
|
}, |
|
genid: ({ path }) => { |
|
const sid = uuid(); |
|
|
|
stdout(`Generated session identifier ${sid}; request.path=${path}`); |
|
|
|
return sid; |
|
}, |
|
resave: false, |
|
saveUninitialized: false, |
|
secret: await getSessionSecret(), |
|
store: new SessionStore(), |
|
}))();
|
|
|