feat(web): Desktop-Client per Cookie erkennen — Download-Links und Browser-Kontextmenü in der App aus
- withDesktopCookie in middleware.ts setzt tessera_desktop=1 auf JEDER Antwort (Fruehausstieg, Redirects, next()), wenn ?desktop=1 anliegt - desktop-client.ts: isDesktopClient() liest das Cookie, useIsDesktopClient() kapselt es hydration-sicher per useEffect - DesktopDownloadLinks fragt /desktop/latest im Desktop-Client gar nicht erst an und rendert nichts - DesktopContextMenuGuard unterdrueckt das WebView2-Kontextmenue ausserhalb von Eingabefeldern/contenteditable, in layout.tsx eingebunden - middleware.test.ts (neu), desktop-client.test.ts (neu), desktop-context-menu-guard.test.tsx (neu), Test 4 in desktop-download-links.test.tsx — alle 417 Web-Tests und type-check gruen Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// @vitest-environment node
|
||||
import { NextRequest } from 'next/server';
|
||||
import { SignJWT } from 'jose';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { middleware } from './middleware';
|
||||
|
||||
/**
|
||||
* middleware.test — Desktop-Client-Cookie (260917-h2s), eigener describe-Block
|
||||
* neben den bestehenden Redirect-/Session-Faellen. `@vitest-environment node`,
|
||||
* weil `NextRequest`/`NextResponse` node-typische APIs (Headers, URL) nutzen,
|
||||
* die im jsdom-Standardmilieu der Suite nicht gebraucht werden.
|
||||
*/
|
||||
describe('middleware — Desktop-Client-Cookie (260917-h2s)', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('JWT_SECRET', 'test-secret');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('Test 1: /login?desktop=1 setzt das Cookie tessera_desktop=1', async () => {
|
||||
const req = new NextRequest('http://localhost:3000/login?desktop=1');
|
||||
const res = await middleware(req);
|
||||
const setCookie = res.headers.get('set-cookie');
|
||||
expect(setCookie).toContain('tessera_desktop=1');
|
||||
expect(setCookie).toContain('Path=/');
|
||||
expect(setCookie).toContain('Max-Age=31536000');
|
||||
expect(setCookie).toContain('SameSite=lax');
|
||||
expect(setCookie).not.toContain('Secure');
|
||||
expect(setCookie).not.toContain('HttpOnly');
|
||||
});
|
||||
|
||||
it('Test 2: /login ohne Parameter setzt kein Cookie', async () => {
|
||||
const req = new NextRequest('http://localhost:3000/login');
|
||||
const res = await middleware(req);
|
||||
expect(res.headers.get('set-cookie')).toBeNull();
|
||||
});
|
||||
|
||||
it('Test 3: /dashboard?desktop=1 ohne Session leitet um und setzt das Cookie', async () => {
|
||||
const req = new NextRequest('http://localhost:3000/dashboard?desktop=1');
|
||||
const res = await middleware(req);
|
||||
expect(res.status).toBe(307);
|
||||
expect(res.headers.get('location')).toContain('/login');
|
||||
expect(res.headers.get('set-cookie')).toContain('tessera_desktop=1');
|
||||
});
|
||||
|
||||
it('Test 4: https setzt Secure', async () => {
|
||||
const req = new NextRequest('https://tessera.example.com/login?desktop=1');
|
||||
const res = await middleware(req);
|
||||
expect(res.headers.get('set-cookie')).toContain('Secure');
|
||||
});
|
||||
|
||||
it('Test 5: gueltiges JWT laesst die Anfrage durch und setzt trotzdem das Cookie', async () => {
|
||||
const token = await new SignJWT({ sub: 'u1' })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('5m')
|
||||
.sign(new TextEncoder().encode('test-secret'));
|
||||
|
||||
const req = new NextRequest('http://localhost:3000/dashboard?desktop=1', {
|
||||
headers: { cookie: `session=${token}` },
|
||||
});
|
||||
const res = await middleware(req);
|
||||
expect(res.headers.get('set-cookie')).toContain('tessera_desktop=1');
|
||||
expect(res.headers.get('x-middleware-next')).toBe('1');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user