feat(agrarmonitor): Cookies in den Einstellungen zurücksetzen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 42s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 42s
Neuer Button „Cookies zurücksetzen“ neben „Status abrufen“. Er löscht die gespeicherte Agrarmonitor-Session samt Cookie-Datei; beim nächsten Zugriff meldet sich der Manager neu an. Eine laufende Connector-Instanz wird über clearSession() zurückgesetzt und verworfen, damit sie ihren alten Cookie-Jar nicht wieder in die Datei schreibt. Während einer Lieferantennummern-Vergabe ist das Zurücksetzen gesperrt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
549948913a
commit
0ff78707d1
5 files changed
+133
-18
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
ConflictException,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
@@ -36,6 +37,18 @@ export class AgrarmonitorController {
|
||||
return this.service.registerDevice(body.pcName, body.agrarmonitorId);
|
||||
}
|
||||
|
||||
@Post('reset-cookies')
|
||||
@HttpCode(200)
|
||||
@RequirePermissions(Permission.MANAGE_SETTINGS)
|
||||
async resetCookies() {
|
||||
if (this.lieferantennummernService.getStatus().aktiv) {
|
||||
throw new ConflictException(
|
||||
'Während der Lieferantennummern-Vergabe können die Cookies nicht gelöscht werden.',
|
||||
);
|
||||
}
|
||||
return this.service.resetCookies();
|
||||
}
|
||||
|
||||
@Get('polling-config')
|
||||
@RequirePermissions(Permission.MANAGE_SETTINGS)
|
||||
async getPollingConfig() {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { ConfigService } from '@nestjs/config';
|
||||
import type { AgrarmonitorConnectorResult } from 'agrarmonitor-connector';
|
||||
import { AgrarmonitorService } from './agrarmonitor.service';
|
||||
|
||||
const storeClear = jest.fn(() => Promise.resolve());
|
||||
|
||||
// Der echte Connector lädt jsdom (ESM), das Jest nicht verarbeiten kann.
|
||||
jest.mock('agrarmonitor-connector', () => ({
|
||||
FileCookieStore: jest.fn(() => ({ clear: storeClear })),
|
||||
AesGcmCookieEncryptor: jest.fn(),
|
||||
createAgrarmonitorClient: jest.fn(),
|
||||
}));
|
||||
|
||||
function service(): AgrarmonitorService {
|
||||
const config = {
|
||||
get: (_key: string, fallback?: string) => fallback,
|
||||
} as unknown as ConfigService;
|
||||
return new AgrarmonitorService(config);
|
||||
}
|
||||
|
||||
describe('AgrarmonitorService.resetCookies', () => {
|
||||
beforeEach(() => storeClear.mockClear());
|
||||
|
||||
it('setzt über die laufende Instanz zurück und verwirft sie', async () => {
|
||||
const s = service();
|
||||
const clearSession = jest.fn(() => Promise.resolve());
|
||||
(s as unknown as { client: Partial<AgrarmonitorConnectorResult> }).client =
|
||||
{ clearSession };
|
||||
|
||||
const result = await s.resetCookies();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(clearSession).toHaveBeenCalledTimes(1);
|
||||
expect(storeClear).not.toHaveBeenCalled();
|
||||
expect((s as unknown as { client: unknown }).client).toBeNull();
|
||||
});
|
||||
|
||||
it('löscht ohne Instanz direkt die Cookie-Datei, ohne sich anzumelden', async () => {
|
||||
const result = await service().resetCookies();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(storeClear).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -46,21 +46,7 @@ export class AgrarmonitorService {
|
||||
'https://api.agrarmonitor.de',
|
||||
);
|
||||
const apiToken = this.configService.get<string>('AGRARMONITOR_API_TOKEN');
|
||||
const cookiePath = this.configService.get<string>(
|
||||
'AGRARMONITOR_COOKIE_PATH',
|
||||
'./data/agrarmonitor-cookies.json',
|
||||
);
|
||||
const encryptionKey = this.configService.get<string>(
|
||||
'AGRARMONITOR_ENCRYPTION_KEY',
|
||||
);
|
||||
|
||||
const encryptor = encryptionKey
|
||||
? new AesGcmCookieEncryptor(encryptionKey)
|
||||
: undefined;
|
||||
const cookieStore = new FileCookieStore(cookiePath, {
|
||||
encryptor,
|
||||
logger: this.logger,
|
||||
});
|
||||
const cookieStore = this.createCookieStore();
|
||||
|
||||
this.client = await createAgrarmonitorClient({
|
||||
baseUrl,
|
||||
@@ -79,10 +65,45 @@ export class AgrarmonitorService {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
private createCookieStore(): FileCookieStore {
|
||||
const cookiePath = this.configService.get<string>(
|
||||
'AGRARMONITOR_COOKIE_PATH',
|
||||
'./data/agrarmonitor-cookies.json',
|
||||
);
|
||||
const encryptionKey = this.configService.get<string>(
|
||||
'AGRARMONITOR_ENCRYPTION_KEY',
|
||||
);
|
||||
const encryptor = encryptionKey
|
||||
? new AesGcmCookieEncryptor(encryptionKey)
|
||||
: undefined;
|
||||
return new FileCookieStore(cookiePath, {
|
||||
encryptor,
|
||||
logger: this.logger,
|
||||
});
|
||||
}
|
||||
|
||||
clearClient(): void {
|
||||
this.client = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht alle gespeicherten Agrarmonitor-Cookies (Session). Der nächste
|
||||
* Zugriff meldet sich neu an.
|
||||
*/
|
||||
async resetCookies(): Promise<AgrarmonitorRegisterResultDto> {
|
||||
const client = this.client;
|
||||
this.client = null;
|
||||
// Über die laufende Instanz zurücksetzen – sonst schreibt sie ihren alten
|
||||
// Cookie-Jar beim nächsten Request wieder in die Datei.
|
||||
if (client) await client.clearSession();
|
||||
else await this.createCookieStore().clear();
|
||||
this.logger.warn('Agrarmonitor-Cookies wurden zurückgesetzt');
|
||||
return {
|
||||
success: true,
|
||||
message: 'Cookies gelöscht – beim nächsten Zugriff wird neu angemeldet.',
|
||||
};
|
||||
}
|
||||
|
||||
async getStatus(): Promise<AgrarmonitorStatusDto> {
|
||||
try {
|
||||
const client = await this.getClient();
|
||||
|
||||
@@ -317,6 +317,8 @@ export const agrarmonitorApi = {
|
||||
api.post<SyncCorrespondentsResult>('/api/agrarmonitor/sync-correspondents').then((r) => r.data),
|
||||
mergeCorrespondents: (keepId: number, deleteId: number) =>
|
||||
api.post<{ mergedDocuments: number }>('/api/agrarmonitor/merge-correspondents', { keepId, deleteId }).then((r) => r.data),
|
||||
resetCookies: () =>
|
||||
api.post<{ success: boolean; message: string }>('/api/agrarmonitor/reset-cookies').then((r) => r.data),
|
||||
getLieferantennummernStatus: () =>
|
||||
api.get<LieferantennummerStatus>('/api/agrarmonitor/lieferantennummern/status').then((r) => r.data),
|
||||
lieferantennummernVorschau: () =>
|
||||
|
||||
@@ -2529,6 +2529,7 @@ function AgrarmonitorTab() {
|
||||
const [pollingForm] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [registering, setRegistering] = useState(false);
|
||||
const [cookiesResetting, setCookiesResetting] = useState(false);
|
||||
const [pollingConfigLoading, setPollingConfigLoading] = useState(false);
|
||||
const [pollingSaving, setPollingSaving] = useState(false);
|
||||
const [pollingRunning, setPollingRunning] = useState(false);
|
||||
@@ -2555,6 +2556,20 @@ function AgrarmonitorTab() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetCookies = async () => {
|
||||
setCookiesResetting(true);
|
||||
try {
|
||||
const result = await agrarmonitorApi.resetCookies();
|
||||
setStatus(null);
|
||||
setRegisterResult(null);
|
||||
message.success(result.message);
|
||||
} catch (err) {
|
||||
message.error(fehlermeldung(err, 'Cookies konnten nicht gelöscht werden'));
|
||||
} finally {
|
||||
setCookiesResetting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegister = async () => {
|
||||
const values = await form.validateFields();
|
||||
setRegistering(true);
|
||||
@@ -2646,9 +2661,29 @@ function AgrarmonitorTab() {
|
||||
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div>
|
||||
<Button loading={loading} onClick={handleLoadStatus}>
|
||||
Status abrufen
|
||||
</Button>
|
||||
<Space>
|
||||
<Button loading={loading} onClick={handleLoadStatus}>
|
||||
Status abrufen
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="Agrarmonitor-Cookies löschen?"
|
||||
description={
|
||||
<div style={{ maxWidth: 320 }}>
|
||||
Die gespeicherte Agrarmonitor-Session wird verworfen. Beim nächsten Zugriff meldet sich
|
||||
der Manager neu an. Möglicherweise muss das Gerät danach neu registriert bzw.
|
||||
freigeschaltet werden.
|
||||
</div>
|
||||
}
|
||||
okText="Löschen"
|
||||
okButtonProps={{ danger: true }}
|
||||
cancelText="Abbrechen"
|
||||
onConfirm={handleResetCookies}
|
||||
>
|
||||
<Button danger loading={cookiesResetting}>
|
||||
Cookies zurücksetzen
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{status && (
|
||||
|
||||
Reference in new issue
Block a user