From df2b9c2c7b18e4f6dde9401d77fd54f6be243ef2 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 5 Jul 2026 16:30:01 +0000 Subject: [PATCH] =?UTF-8?q?fix(audit):=20mobile=20critique=20+=20s=C3=A9cu?= =?UTF-8?q?rit=C3=A9=20mobile=20+=20verifyMobileToken=20async?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mobile fixes (CRITIQUE): - theme.ts: C.surface + emerald/blue/amber/purple ajoutés (crash Révision fix) - note/[id].tsx: édition préserve le HTML (titre seulement, contenu read-only) + message explicatif 'utilisez la version web' - store.ts: logout appelle POST /api/mobile/auth/logout avant clearToken Mobile auth (SÉCURITÉ): - Nouvelle route /api/mobile/auth/logout — blacklist Redis du token (TTL 90j) - verifyMobileToken devient async + check Redis blacklist - getMobileUserId devient async Note: Publication IA mode + templates déjà dans note-editor-toolbar.tsx (pas manquant) Note: Structured Views Wizard déjà branché via StructuredViewsIntro (pas orphelin) --- memento-mobile/app/note/[id].tsx | 24 +++++++++---------- memento-mobile/lib/store.ts | 1 + memento-mobile/lib/theme.ts | 6 +++++ .../app/api/mobile/auth/logout/route.ts | 19 +++++++++++++++ memento-note/lib/mobile-auth.ts | 11 +++++++-- 5 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 memento-note/app/api/mobile/auth/logout/route.ts diff --git a/memento-mobile/app/note/[id].tsx b/memento-mobile/app/note/[id].tsx index 8141bd9..0cd255a 100644 --- a/memento-mobile/app/note/[id].tsx +++ b/memento-mobile/app/note/[id].tsx @@ -158,7 +158,6 @@ export default function NoteScreen() { const handleEdit = () => { if (!note) return setEditTitle(note.title) - setEditContent(htmlToPlainText(note.content ?? '')) setEditMode(true) } @@ -168,10 +167,10 @@ export default function NoteScreen() { try { const res = await apiFetch(ENDPOINTS.note(note.id), { method: 'PUT', - body: JSON.stringify({ title: editTitle.trim(), content: editContent }), + body: JSON.stringify({ title: editTitle.trim() }), }) if (!res.ok) throw new Error('Erreur de sauvegarde') - setNote((prev) => prev ? { ...prev, title: editTitle.trim(), content: editContent } : prev) + setNote((prev) => prev ? { ...prev, title: editTitle.trim() } : prev) setEditMode(false) } catch (e: any) { Alert.alert('Erreur', e.message) @@ -257,20 +256,21 @@ export default function NoteScreen() { /> )} - {/* Mode édition */} + {/* Mode édition — titre uniquement, contenu preserve HTML */} {note && editMode && ( - + - + + + Le contenu enrichi ne peut pas être édité depuis mobile.{`\n\n`}Utilisez la version web pour modifier le texte. + + {/* Barre outils */} - - {audioState === 'recording' - ? ● Enregistrement… Appuyez pour arrêter - : setAiSheetOpen(true)} disabled={!editContent.trim()} style={[s.aiBtn, !editContent.trim() && { opacity: 0.35 }]} activeOpacity={0.8}> - ✨ Améliorer avec l'IA - } + + {saving ? 'Sauvegarde…' : 'Enregistrer le titre'} + )} diff --git a/memento-mobile/lib/store.ts b/memento-mobile/lib/store.ts index ba772e3..d12e663 100644 --- a/memento-mobile/lib/store.ts +++ b/memento-mobile/lib/store.ts @@ -43,6 +43,7 @@ export const useAuthStore = create((set) => ({ }, logout: async () => { + try { await apiFetch(ENDPOINTS.logout, { method: 'POST' }) } catch {} await clearToken() set({ user: null }) }, diff --git a/memento-mobile/lib/theme.ts b/memento-mobile/lib/theme.ts index 1587c2c..5ed418d 100644 --- a/memento-mobile/lib/theme.ts +++ b/memento-mobile/lib/theme.ts @@ -6,7 +6,13 @@ export const C = { concrete: '#8A8A82', border: '#E8E6E0', white: '#FFFFFF', + surface: '#F5F4F0', rose: '#e11d48', roseBg: '#fff1f2', roseBorder: '#fecdd3', + emerald: '#10b981', + emeraldBg: '#ecfdf5', + blue: '#3b82f6', + amber: '#f59e0b', + purple: '#8b5cf6', } diff --git a/memento-note/app/api/mobile/auth/logout/route.ts b/memento-note/app/api/mobile/auth/logout/route.ts new file mode 100644 index 0000000..2bdd581 --- /dev/null +++ b/memento-note/app/api/mobile/auth/logout/route.ts @@ -0,0 +1,19 @@ +import { NextResponse } from 'next/server' +import { getMobileUserId } from '@/lib/mobile-auth' +import { redis } from '@/lib/redis' + +export async function POST(req: Request) { + const userId = getMobileUserId(req) + if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + + const auth = req.headers.get('Authorization') ?? '' + const token = auth.startsWith('Bearer ') ? auth.slice(7) : '' + + if (token) { + try { + await redis.setex(`mobile:revoked:${token}`, 90 * 24 * 60 * 60, '1') + } catch {} + } + + return NextResponse.json({ success: true }) +} diff --git a/memento-note/lib/mobile-auth.ts b/memento-note/lib/mobile-auth.ts index 54d4405..f8a8a10 100644 --- a/memento-note/lib/mobile-auth.ts +++ b/memento-note/lib/mobile-auth.ts @@ -19,8 +19,15 @@ export function createMobileToken(userId: string): string { return Buffer.from(`${payload}:${sig}`).toString('base64url') } -export function verifyMobileToken(token: string): string | null { +export async function verifyMobileToken(token: string): Promise { try { + // Check Redis blacklist first (non-blocking — if Redis is down, allow) + try { + const { redis } = await import('@/lib/redis') + const revoked = await redis.get(`mobile:revoked:${token}`) + if (revoked) return null + } catch {} + const decoded = Buffer.from(token, 'base64url').toString('utf-8') const lastColon = decoded.lastIndexOf(':') if (lastColon === -1) return null @@ -41,7 +48,7 @@ export function verifyMobileToken(token: string): string | null { } } -export function getMobileUserId(request: Request): string | null { +export async function getMobileUserId(request: Request): Promise { const auth = request.headers.get('Authorization') if (!auth?.startsWith('Bearer ')) return null return verifyMobileToken(auth.slice(7))