|
|
|
import expressSession, {
|
|
|
|
SessionData,
|
|
|
|
Store as BaseSessionStore,
|
|
|
|
} from 'express-session';
|
|
|
|
|
|
|
|
import {
|
|
|
|
awrite,
|
|
|
|
dbQuery,
|
|
|
|
getLocalHostUUID,
|
|
|
|
timestamp,
|
|
|
|
} from './lib/accessModule';
|
|
|
|
import { getSessionSecret } from './lib/getSessionSecret';
|
|
|
|
import { isObject } from './lib/isObject';
|
|
|
|
import { stderr, stdout, stdoutVar, uuidgen } from './lib/shell';
|
|
|
|
|
|
|
|
const DEFAULT_COOKIE_ORIGINAL_MAX_AGE = 3600000;
|
|
|
|
|
|
|
|
const getWriteCode = (obj: object) => {
|
|
|
|
let result: number | undefined;
|
|
|
|
|
|
|
|
if ('write_code' in obj) {
|
|
|
|
({ write_code: result } = obj as { write_code: number });
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
export class SessionStore extends BaseSessionStore {
|
|
|
|
constructor(options = {}) {
|
|
|
|
super(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
public destroy(
|
|
|
|
sid: string,
|
|
|
|
done?: ((err?: unknown) => void) | undefined,
|
|
|
|
): void {
|
|
|
|
stdout(`Destroy session ${sid}`);
|
|
|
|
|
|
|
|
try {
|
|
|
|
awrite(`DELETE FROM sessions WHERE session_uuid = '${sid}';`, {
|
|
|
|
onClose({ stdout: s1 }) {
|
|
|
|
const wcode = getWriteCode(isObject(s1).obj);
|
|
|
|
|
|
|
|
if (wcode !== 0) {
|
|
|
|
stderr(
|
|
|
|
`SQL script failed during destroy session ${sid}; code: ${wcode}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onError(error) {
|
|
|
|
stderr(
|
|
|
|
`Failed to complete DB write in destroy session ${sid}; CAUSE: ${error}`,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
return done?.call(null, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
return done?.call(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public get(
|
|
|
|
sid: string,
|
|
|
|
done: (err: unknown, session?: SessionData | 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: SessionData,
|
|
|
|
done?: ((err?: unknown) => void) | undefined,
|
|
|
|
): void {
|
|
|
|
stdout(`Set session ${sid}`);
|
|
|
|
|
|
|
|
const {
|
|
|
|
passport: { user: userUuid },
|
|
|
|
} = session;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const localHostUuid = getLocalHostUUID();
|
|
|
|
const modifiedDate = timestamp();
|
|
|
|
|
|
|
|
awrite(
|
|
|
|
`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}';`,
|
|
|
|
{
|
|
|
|
onClose: ({ stdout: s1 }) => {
|
|
|
|
const wcode = getWriteCode(isObject(s1).obj);
|
|
|
|
|
|
|
|
if (wcode !== 0) {
|
|
|
|
stderr(
|
|
|
|
`SQL script failed during set session ${sid}; code: ${wcode}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onError: (error) => {
|
|
|
|
stderr(
|
|
|
|
`Failed to complete DB write in set session ${sid}; CAUSE: ${error}`,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
return done?.call(null, error);
|
|
|
|
}
|
|
|
|
|
|
|
|
return done?.call(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public touch(
|
|
|
|
sid: string,
|
|
|
|
session: SessionData,
|
|
|
|
done?: ((err?: unknown) => void) | undefined,
|
|
|
|
): void {
|
|
|
|
stdout(`Touch session ${sid}`);
|
|
|
|
|
|
|
|
try {
|
|
|
|
awrite(
|
|
|
|
`UPDATE sessions SET modified_date = '${timestamp()}' WHERE session_uuid = '${sid}';`,
|
|
|
|
{
|
|
|
|
onClose: ({ stdout: s1 }) => {
|
|
|
|
const wcode = getWriteCode(isObject(s1).obj);
|
|
|
|
|
|
|
|
if (wcode !== 0) {
|
|
|
|
stderr(
|
|
|
|
`SQL script failed during touch session ${sid}; code: ${wcode}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onError: (error) => {
|
|
|
|
stderr(
|
|
|
|
`Failed to complete DB write in touch session ${sid}; CAUSE: ${error}`,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} catch (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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const session = expressSession({
|
|
|
|
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 session;
|