Commit the app code currently running in Docker, including scheduler, store status, journal reminder, maintenance mode, and Foodsharing API check updates. Remove generated runtime logs from version control and ignore local runtime captures.
142 lines
3.8 KiB
JavaScript
142 lines
3.8 KiB
JavaScript
const foodsharingClient = require('./foodsharingClient');
|
||
const sessionStore = require('./sessionStore');
|
||
const notificationService = require('./notificationService');
|
||
|
||
const ADMIN_SESSION_ERROR_COOLDOWN_MS = 6 * 60 * 60 * 1000;
|
||
const adminSessionErrorCooldowns = new Map();
|
||
|
||
function shouldNotifyAdminSessionError(key) {
|
||
if (!key) {
|
||
return false;
|
||
}
|
||
const now = Date.now();
|
||
const lastNotified = adminSessionErrorCooldowns.get(key) || 0;
|
||
if (now - lastNotified < ADMIN_SESSION_ERROR_COOLDOWN_MS) {
|
||
return false;
|
||
}
|
||
adminSessionErrorCooldowns.set(key, now);
|
||
return true;
|
||
}
|
||
|
||
function isUnauthorizedError(error) {
|
||
const status = error?.response?.status;
|
||
if (status === 401) {
|
||
return true;
|
||
}
|
||
if (status !== 403) {
|
||
return false;
|
||
}
|
||
const data = error?.response?.data;
|
||
const message =
|
||
typeof data === 'string'
|
||
? data
|
||
: typeof data?.message === 'string'
|
||
? data.message
|
||
: '';
|
||
return message.toLowerCase().includes('not logged in');
|
||
}
|
||
|
||
function isCsrfError(error) {
|
||
const status = error?.response?.status;
|
||
if (status !== 400) {
|
||
return false;
|
||
}
|
||
const data = error?.response?.data;
|
||
const message =
|
||
typeof data === 'string'
|
||
? data
|
||
: typeof data?.message === 'string'
|
||
? data.message
|
||
: '';
|
||
return message.toLowerCase().includes('csrf');
|
||
}
|
||
|
||
async function refreshSession(session, { label } = {}) {
|
||
if (!session?.credentials?.email || !session?.credentials?.password) {
|
||
console.warn(
|
||
`[SESSION] Session ${session?.id || 'unbekannt'} kann nicht erneuert werden – keine Zugangsdaten gespeichert.`
|
||
);
|
||
return false;
|
||
}
|
||
try {
|
||
const refreshed = await foodsharingClient.login(
|
||
session.credentials.email,
|
||
session.credentials.password
|
||
);
|
||
sessionStore.update(session.id, {
|
||
cookieHeader: refreshed.cookieHeader,
|
||
csrfToken: refreshed.csrfToken,
|
||
profile: {
|
||
...session.profile,
|
||
...refreshed.profile
|
||
}
|
||
});
|
||
console.log(
|
||
`[SESSION] Session ${session.id} wurde erfolgreich erneuert${label ? ` (${label})` : ''}.`
|
||
);
|
||
return true;
|
||
} catch (error) {
|
||
console.error(
|
||
`[SESSION] Session ${session?.id || 'unbekannt'} konnte nicht erneuert werden${label ? ` (${label})` : ''}:`,
|
||
error.message
|
||
);
|
||
const profileId = session?.profile?.id ? String(session.profile.id) : null;
|
||
const notifyKey = profileId || session?.id || null;
|
||
if (shouldNotifyAdminSessionError(notifyKey)) {
|
||
try {
|
||
await notificationService.sendAdminSessionErrorNotification({
|
||
profileId,
|
||
profileEmail: session?.credentials?.email || session?.profile?.email,
|
||
profileName: session?.profile?.name,
|
||
sessionId: session?.id,
|
||
error: error?.message,
|
||
label
|
||
});
|
||
} catch (notifyError) {
|
||
console.error('[NOTIFY] Admin-Session-Fehler konnte nicht gemeldet werden:', notifyError.message);
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function ensureSession(session) {
|
||
if (!session?.profile?.id) {
|
||
return false;
|
||
}
|
||
if (!session.cookieHeader) {
|
||
return refreshSession(session, { label: 'missing-cookie' });
|
||
}
|
||
return true;
|
||
}
|
||
|
||
async function withSessionRetry(session, action, { label } = {}) {
|
||
if (!session) {
|
||
throw new Error('Session fehlt');
|
||
}
|
||
if (!session.cookieHeader && session.credentials) {
|
||
const refreshed = await refreshSession(session, { label });
|
||
if (!refreshed) {
|
||
throw new Error('Session konnte nicht erneuert werden');
|
||
}
|
||
}
|
||
try {
|
||
return await action();
|
||
} catch (error) {
|
||
if (!isUnauthorizedError(error) && !isCsrfError(error)) {
|
||
throw error;
|
||
}
|
||
const refreshed = await refreshSession(session, { label });
|
||
if (!refreshed) {
|
||
throw error;
|
||
}
|
||
return action();
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
ensureSession,
|
||
refreshSession,
|
||
withSessionRetry
|
||
};
|