diff --git a/SovereignAI-Starter/README.md b/SovereignAI-Starter/README.md index cc7d3bf..7b40599 100644 --- a/SovereignAI-Starter/README.md +++ b/SovereignAI-Starter/README.md @@ -162,7 +162,7 @@ python scripts/eval_knowledge_retrieval.py --dataset evals/knowledge_retrieval_p - تحفظ FastAPI المحادثات والرسائل في SQLite محليًا. على Windows يوجد الملف في `%LOCALAPPDATA%\SovereignAI\data\sovereign_ai.sqlite3`، وخارج مجلد المشروع لتجنب مزامنة قاعدة البيانات مع OneDrive. - لكل سجل محادثة `user_id`، وتُفلتر القراءة والتعديل والحذف بهوية جلسة Bearer موثقة؛ لم يعد `X-User-ID` يمنح أي صلاحية. في الوضع المحلي تطلب الواجهة تلقائيًا جلسة للمستخدم المحلي، ولا يصدرها الخادم إلا لاتصال loopback. -- يوفّر API الآن `POST /v1/auth/register` و`POST /v1/auth/login` و`GET /v1/auth/me` و`POST /v1/auth/logout`. كلمات المرور تُخزن بتجزئة PBKDF2 مع salt؛ رمز الجلسة العشوائي يُخزن كـSHA-256 وينتهي بعد 7 أيام ويمكن إلغاؤه. أضاف Flutter واجهة تسجيل ودخول وخروج، ويحفظ رمز الحساب عبر `flutter_secure_storage` حيث تتوفر آلية النظام. على الويب تعتمد الحزمة على تخزين المتصفح؛ دعمها تجريبي ويحتاج HTTPS أو localhost، لذا لا نعدّه حماية مماثلة لمخزن مفاتيح نظام التشغيل. +- يوفّر API الآن `POST /v1/auth/register` و`POST /v1/auth/login` و`GET /v1/auth/me` و`POST /v1/auth/logout`. كلمات المرور تُخزن بتجزئة PBKDF2 مع salt؛ رمز الجلسة العشوائي يُخزن كـSHA-256 وينتهي بعد 7 أيام ويمكن إلغاؤه. أضاف Flutter واجهة تسجيل ودخول وخروج، ويحفظ رمز الحساب عبر `flutter_secure_storage` في تطبيقات الأنظمة الأصلية. على الويب يبقى الرمز في الذاكرة فقط ولا يُحفظ بعد إعادة تحميل الصفحة أو إغلاق التبويب؛ بذلك يحتاج المستخدم لتسجيل الدخول مجددًا. هذا يتجنب التخزين الدائم في JavaScript-accessible browser storage، لكنه لا يمنع سرقة الجلسة أثناء تشغيل صفحة مخترقة؛ يحتاج النشر العام مراجعة حماية XSS وHTTPS. - بناء Windows يتطلب مكوّن C++ ATL ضمن Visual Studio Build Tools بسبب إضافة `flutter_secure_storage_windows`؛ ATL مثبت الآن ونجح بناء Debug على هذا الجهاز. على Android الحد الأدنى صار API 23. - كل عمليات `/v1/*` الخاصة تتطلب الآن Bearer session؛ الاستثناءات العامة هي الصحة وقائمة النماذج وتسجيل/دخول الحساب وإنشاء الجلسة المحلية. عميل Flutter يرسل الجلسة للمحادثة والوكيل والملفات والمعرفة والبحث والصوت. ملكية سجل تدقيق الوكيل وفهرس المعرفة ومقترحات تعديل الملفات مرتبطة بمعرّف الحساب. - أضيف حد أولي لمحاولات الدخول وإنشاء الحسابات في SQLite، لكنه لم يخضع بعد لاختبار ضغط/خلف reverse proxy؛ ولا توجد استعادة لكلمة المرور. يخصص `SOVEREIGNAI_USER_WORKSPACES` مجلدات منفصلة للحسابات ضمن جذور `SOVEREIGNAI_ALLOWED_WORKSPACES`، لكن عملية FastAPI ما زالت ترث صلاحيات نظام التشغيل. أبقِ الخدمة على `127.0.0.1` إلى أن يكتمل العزل على مستوى العملية ويُختبر TLS. diff --git a/SovereignAI-Starter/ROADMAP.md b/SovereignAI-Starter/ROADMAP.md index eb2b751..a946807 100644 --- a/SovereignAI-Starter/ROADMAP.md +++ b/SovereignAI-Starter/ROADMAP.md @@ -71,10 +71,12 @@ - [ ] عزل عملية FastAPI عن ملفات المضيف غير المصرح بها على مستوى نظام التشغيل قبل السماح بعميل شبكي أو خدمة مستضافة؛ القائمة البرمجية وحدها لا تحد صلاحيات العملية نفسها. - [x] إضافة قائمة سماح على مستوى الخادم عبر `SOVEREIGNAI_ALLOWED_WORKSPACES`؛ يرفض API أي مجلد خارج الجذور المعتمدة. مع `SOVEREIGNAI_USER_WORKSPACES` يطبق الخادم كذلك حدود جذور كل حساب. لا يغني ذلك عن عزل صلاحيات عملية FastAPI على مستوى نظام التشغيل. - [x] بناء واجهة إنشاء الحساب/الدخول والخروج وربطها بجلسات API؛ عند تبديل الحساب تمسح الواجهة الحالة المحلية ثم تعيد تحميل المحادثات والمهارات تحت الهوية الجديدة. -- [x] حفظ رمز الحساب عبر `flutter_secure_storage` في مخزن النظام على المنصات المدعومة، واستعادة `/v1/auth/me` عند بدء التطبيق؛ Android مضبوط على API 23 كحد أدنى. تخزين الويب تجريبي ويعتمد على المتصفح، لذلك لا يُعامل كحماية نظام تشغيل. +- [x] حفظ رمز الحساب عبر `flutter_secure_storage` في مخزن النظام على المنصات الأصلية المدعومة، واستعادة `/v1/auth/me` عند بدء التطبيق؛ Android مضبوط على API 23 كحد أدنى. الويب لا يحفظ رمز الجلسة دائمًا، ويحتفظ به في الذاكرة فقط حتى إعادة التحميل أو إغلاق التبويب. +- [x] اختبار مخزن جلسة الويب المؤقت (2/2) وبناء Flutter Web ناجح (2026-10-03). رسالة الدخول توضح انتهاء الجلسة عند إعادة تحميل الصفحة. - [ ] إضافة تدفق استعادة كلمة المرور واختبار نشر محلي عبر TLS قبل الوصول الشبكي. - [x] حد أولي دائم لتخمين كلمات المرور وإنشاء الحسابات في SQLite مع نافذة انتهاء و`Retry-After`؛ اختبرته مجموعة Python كاملة، وتبقى مراجعة الحدود تحت reverse proxy موثوق قبل أي نشر خارجي. -- [ ] تهيئة تخزين آمن للحساب على الويب أو تعطيل حفظ الرمز هناك؛ التحقق من متطلبات Linux keyring وWindows/macOS على منصات بناء فعلية. +- [x] تعطيل حفظ رمز الجلسة الدائم في الويب؛ استخدام مخزن ذاكرة مؤقت وإظهار ذلك للمستخدم. +- [ ] التحقق من متطلبات Linux keyring وتشغيل تخزين الجلسة الأصلي، وبناء macOS وLinux على منصات فعلية. - [x] تثبيت مكوّن C++ ATL في Visual Studio Build Tools؛ تحقق وجود `atlstr.h` ونجح `flutter build windows --debug` (2026-10-03). - تصميم بيانات المستخدمين والمحادثات والمرفقات ونسخ الإجابات مع ملكية واضحة وفهارس وترحيلات قاعدة بيانات. - SQLite مناسب لنسخة محلية أحادية الجهاز. عند تشغيل خدمة لعدة مستخدمين/أجهزة، ننتقل إلى PostgreSQL، مع نسخ احتياطية وسياسة حذف وتصدير. diff --git a/SovereignAI-Starter/flutter_app/lib/core/auth/session_token_store.dart b/SovereignAI-Starter/flutter_app/lib/core/auth/session_token_store.dart index c0a4790..1010c16 100644 --- a/SovereignAI-Starter/flutter_app/lib/core/auth/session_token_store.dart +++ b/SovereignAI-Starter/flutter_app/lib/core/auth/session_token_store.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; abstract interface class SessionTokenStore { @@ -25,3 +26,21 @@ class SecureSessionTokenStore implements SessionTokenStore { @override Future delete() => _storage.delete(key: _key); } + +/// Keeps browser sessions in memory only; a reload or tab close signs out. +class EphemeralSessionTokenStore implements SessionTokenStore { + String? _token; + + @override + Future read() async => _token; + + @override + Future write(String token) async => _token = token; + + @override + Future delete() async => _token = null; +} + +SessionTokenStore createDefaultSessionTokenStore() => kIsWeb + ? EphemeralSessionTokenStore() + : SecureSessionTokenStore(); diff --git a/SovereignAI-Starter/flutter_app/lib/core/network/api_repository.dart b/SovereignAI-Starter/flutter_app/lib/core/network/api_repository.dart index 3b6bd70..d23e2ea 100644 --- a/SovereignAI-Starter/flutter_app/lib/core/network/api_repository.dart +++ b/SovereignAI-Starter/flutter_app/lib/core/network/api_repository.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'package:file_selector/file_selector.dart'; @@ -91,10 +91,12 @@ class AgentToolStep { class ApiRepository { ApiRepository({required String baseUrl, SessionTokenStore? sessionTokenStore}) : _baseUrl = baseUrl, - _sessionTokenStore = sessionTokenStore ?? SecureSessionTokenStore(); + _sessionTokenStore = + sessionTokenStore ?? createDefaultSessionTokenStore(); String _baseUrl; final SessionTokenStore _sessionTokenStore; + bool get accountSessionPersists => !kIsWeb; String? _accessToken; Future? _loadingLocalSession; Future? _restoringSession; diff --git a/SovereignAI-Starter/flutter_app/lib/features/chat/presentation/pages/chat_page.dart b/SovereignAI-Starter/flutter_app/lib/features/chat/presentation/pages/chat_page.dart index 4a7de4b..534e776 100644 --- a/SovereignAI-Starter/flutter_app/lib/features/chat/presentation/pages/chat_page.dart +++ b/SovereignAI-Starter/flutter_app/lib/features/chat/presentation/pages/chat_page.dart @@ -634,9 +634,13 @@ class _ChatPageState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - credentials.$1 - ? 'تم إنشاء الحساب وتخزين الجلسة بأمان.' - : 'تم تسجيل الدخول وتخزين الجلسة بأمان.', + api.accountSessionPersists + ? credentials.$1 + ? 'تم إنشاء الحساب وتخزين الجلسة بأمان.' + : 'تم تسجيل الدخول وتخزين الجلسة بأمان.' + : credentials.$1 + ? 'تم إنشاء الحساب. جلسة الويب مؤقتة وستنتهي عند إعادة تحميل الصفحة.' + : 'تم تسجيل الدخول. جلسة الويب مؤقتة وستنتهي عند إعادة تحميل الصفحة.', ), ), ); diff --git a/SovereignAI-Starter/flutter_app/test/session_token_store_test.dart b/SovereignAI-Starter/flutter_app/test/session_token_store_test.dart new file mode 100644 index 0000000..9e9d94e --- /dev/null +++ b/SovereignAI-Starter/flutter_app/test/session_token_store_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_app/core/auth/session_token_store.dart'; + +void main() { + group('EphemeralSessionTokenStore', () { + test('keeps the token available only in the running app instance', () async { + final store = EphemeralSessionTokenStore(); + + expect(await store.read(), isNull); + await store.write('test-session-token'); + expect(await store.read(), 'test-session-token'); + }); + + test('deletes the in-memory token on logout', () async { + final store = EphemeralSessionTokenStore(); + await store.write('test-session-token'); + + await store.delete(); + + expect(await store.read(), isNull); + }); + }); +}