parent
eb2c66de9f
commit
38181ac301
2 changed files with 202 additions and 0 deletions
@ -0,0 +1,199 @@ |
||||
import assert from 'assert'; |
||||
import session, { |
||||
SessionData as BaseSessionData, |
||||
Store as BaseStore, |
||||
} from 'express-session'; |
||||
|
||||
import { |
||||
dbQuery, |
||||
dbWrite, |
||||
getLocalHostUUID, |
||||
timestamp, |
||||
} from './lib/accessModule'; |
||||
import { getSessionSecret } from './lib/getSessionSecret'; |
||||
import { stdout, stdoutVar, uuidgen } from './lib/shell'; |
||||
|
||||
const DEFAULT_COOKIE_ORIGINAL_MAX_AGE = 1000 * 60 * 60; |
||||
|
||||
export class SessionStore extends BaseStore { |
||||
constructor(options = {}) { |
||||
super(options); |
||||
} |
||||
|
||||
public destroy( |
||||
sid: string, |
||||
done?: ((err?: unknown) => void) | undefined, |
||||
): void { |
||||
stdout(`Destroy session ${sid}`); |
||||
|
||||
try { |
||||
const { write_code: wcode }: { write_code: number } = dbWrite( |
||||
`DELETE FROM sessions WHERE session_uuid = '${sid}';`, |
||||
).stdout; |
||||
|
||||
assert(wcode === 0, `Delete session ${sid} failed with code ${wcode}`); |
||||
} catch (writeError) { |
||||
return done?.call(null, writeError); |
||||
} |
||||
|
||||
return done?.call(null); |
||||
} |
||||
|
||||
public get( |
||||
sid: string, |
||||
done: (err: unknown, session?: BaseSessionData | null | undefined) => void, |
||||
): void { |
||||
stdout(`Get session ${sid}`); |
||||
|
||||
let rows: [ |
||||
sessionUuid: string, |
||||
userUuid: string, |
||||
sessionModifiedDate: string, |
||||
][]; |
||||
|
||||
try { |
||||
rows = dbQuery( |
||||
`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_uuid = '${sid}';`,
|
||||
).stdout; |
||||
} 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 set( |
||||
sid: string, |
||||
session: BaseSessionData, |
||||
done?: ((err?: unknown) => void) | undefined, |
||||
): void { |
||||
stdout(`Set session ${sid}; session=${JSON.stringify(session, null, 2)}`); |
||||
|
||||
const { |
||||
passport: { user: userUuid }, |
||||
} = session as SessionData; |
||||
|
||||
try { |
||||
const localHostUuid = getLocalHostUUID(); |
||||
const modifiedDate = timestamp(); |
||||
|
||||
const { write_code: wcode }: { write_code: number } = dbWrite( |
||||
`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 session_host_uuid = '${localHostUuid}', |
||||
modified_date = '${modifiedDate}';`,
|
||||
).stdout; |
||||
|
||||
assert( |
||||
wcode === 0, |
||||
`Insert or update session ${sid} failed with code ${wcode}`, |
||||
); |
||||
} catch (error) { |
||||
return done?.call(null, error); |
||||
} |
||||
|
||||
return done?.call(null); |
||||
} |
||||
|
||||
public touch( |
||||
sid: string, |
||||
session: BaseSessionData, |
||||
done?: ((err?: unknown) => void) | undefined, |
||||
): void { |
||||
stdout( |
||||
`Update modified date in session ${sid}; session=${JSON.stringify( |
||||
session, |
||||
null, |
||||
2, |
||||
)}`,
|
||||
); |
||||
|
||||
try { |
||||
const { write_code: wcode }: { write_code: number } = dbWrite( |
||||
`UPDATE sessions SET modified_date = '${timestamp()}' WHERE session_uuid = '${sid}';`, |
||||
).stdout; |
||||
|
||||
assert( |
||||
wcode === 0, |
||||
`Update modified date for session ${sid} failed with code ${wcode}`, |
||||
); |
||||
} catch (writeError) { |
||||
return done?.call(null, writeError); |
||||
} |
||||
|
||||
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; |
||||
} |
||||
} |
||||
|
||||
const sessionHandler = session({ |
||||
cookie: { maxAge: DEFAULT_COOKIE_ORIGINAL_MAX_AGE }, |
||||
genid: ({ path }) => { |
||||
const sid = uuidgen('--random').trim(); |
||||
|
||||
stdout(`Generated session identifier ${sid}; request.path=${path}`); |
||||
|
||||
return sid; |
||||
}, |
||||
resave: false, |
||||
saveUninitialized: false, |
||||
secret: getSessionSecret(), |
||||
store: new SessionStore(), |
||||
}); |
||||
|
||||
export default sessionHandler; |
@ -0,0 +1,3 @@ |
||||
type SessionData = import('express-session').SessionData & { |
||||
passport: { user: string }; |
||||
}; |
Loading…
Reference in new issue