This commit is contained in:
Dmitry 2025-11-22 22:06:27 +03:00
parent 341a78774f
commit 83b776c00e
9 changed files with 156 additions and 17 deletions

View file

@ -54,11 +54,12 @@ class PackDtoConverter {
tip: available tip: available
? null ? null
: canOpenForAdVal : canOpenForAdVal
? TextPackTip( ? AssetPackTip(
'📺', 'asset:icons/ad.webp',
position: PackTipPosition.bottomRight, position: PackTipPosition.bottomRight,
) )
: TextPackTip('🔒'), : AssetPackTip('asset:icons/lock.webp',
position: PackTipPosition.bottomRight,),
version: model.version, version: model.version,
); );
} }

View file

@ -34,6 +34,44 @@ server {
add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Static files - no rate limiting (VSCode loads many files simultaneously)
location ~ ^/(static|out|node_modules|_next)/ {
# No rate limiting for static files
proxy_pass http://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Cache static files
proxy_cache_valid 200 1h;
add_header Cache-Control "public, max-age=3600";
# Timeout settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Static file extensions - no rate limiting
location ~ \.(js|css|woff|woff2|ttf|eot|png|jpg|jpeg|gif|svg|ico|webp|map|json)$ {
# No rate limiting for static file extensions
proxy_pass http://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Cache static files
proxy_cache_valid 200 1h;
add_header Cache-Control "public, max-age=3600";
# Timeout settings
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Special rate limiting for login attempts # Special rate limiting for login attempts
location /login { location /login {
# Strict rate limiting for login # Strict rate limiting for login
@ -62,9 +100,10 @@ server {
} }
# Proxy to code-server running on port 8443 # Proxy to code-server running on port 8443
# Increased rate limit for API and dynamic content
location / { location / {
# Apply rate limiting # Increased rate limiting (200 requests per minute with burst of 50)
limit_req zone=vscode_general burst=10 nodelay; limit_req zone=vscode_general burst=50 nodelay;
limit_req_status 429; limit_req_status 429;
proxy_pass http://127.0.0.1:8443; proxy_pass http://127.0.0.1:8443;

View file

@ -94,6 +94,8 @@ class _AppInitializerState extends State<_AppInitializer> {
log('Auto-login successful, creating UserScope', name: 'App'); log('Auto-login successful, creating UserScope', name: 'App');
// Create UserScope only for authenticated users // Create UserScope only for authenticated users
widget.appScope.userScopeHolder.scope!.userStateManager.setUser(user); widget.appScope.userScopeHolder.scope!.userStateManager.setUser(user);
// Notify router about auth change
widget.appScope.userScopeHolder.notifyAuthChanged();
log('UserScope created and user set', name: 'App'); log('UserScope created and user set', name: 'App');
} else { } else {
log('No saved session, starting as guest without UserScope', name: 'App'); log('No saved session, starting as guest without UserScope', name: 'App');

View file

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:yx_scope/yx_scope.dart'; import 'package:yx_scope/yx_scope.dart';
import 'user_scope.dart'; import 'user_scope.dart';
@ -8,11 +9,49 @@ import 'user_scope_container.dart';
/// Управляет жизненным циклом UserScope /// Управляет жизненным циклом UserScope
class UserScopeHolder extends BaseChildScopeHolder<UserScope, class UserScopeHolder extends BaseChildScopeHolder<UserScope,
UserScopeContainer, UserScopeParent> { UserScopeContainer, UserScopeParent> {
UserScopeHolder(super.parent); UserScopeHolder(super.parent) : _authNotifier = ValueNotifier<bool>(false) {
// Подписываемся на изменения состояния scope
listen((scope) {
_updateAuthNotifier();
}, emitImmediately: true);
}
final ValueNotifier<bool> _authNotifier;
/// Notifier для отслеживания изменений состояния авторизации
///
/// Используется для обновления роутера при изменении авторизации
ValueNotifier<bool> get authNotifier => _authNotifier;
@override @override
UserScopeContainer createContainer(UserScopeParent parent) { UserScopeContainer createContainer(UserScopeParent parent) {
return UserScopeContainer(parent: parent); return UserScopeContainer(parent: parent);
} }
void _updateAuthNotifier() {
final isAuth = isAuthenticated;
if (_authNotifier.value != isAuth) {
_authNotifier.value = isAuth;
}
}
/// Проверяет, авторизован ли пользователь
///
/// Возвращает true, если UserScope существует и пользователь авторизован
bool get isAuthenticated {
final userScope = scope;
if (userScope == null) {
return false;
}
return userScope.userStateManager.isAuthenticated;
}
/// Уведомляет о изменении состояния авторизации
///
/// Вызывается после изменения состояния пользователя (логин/логаут)
/// для немедленного обновления роутера
void notifyAuthChanged() {
_updateAuthNotifier();
}
} }

View file

@ -72,6 +72,9 @@ class _AuthPageState extends State<AuthPage> {
// Notify that UserScope has changed // Notify that UserScope has changed
appScope.notifyUserScopeChanged(); appScope.notifyUserScopeChanged();
// Notify router about auth change
appScope.userScopeHolder.notifyAuthChanged();
// Navigate to home // Navigate to home
router.go('/home'); router.go('/home');
} else { } else {
@ -156,6 +159,9 @@ class _AuthPageState extends State<AuthPage> {
// Notify that UserScope has changed // Notify that UserScope has changed
appScope.notifyUserScopeChanged(); appScope.notifyUserScopeChanged();
// Notify router about auth change
appScope.userScopeHolder.notifyAuthChanged();
// Navigate to home // Navigate to home
router.go('/home'); router.go('/home');
} else { } else {

View file

@ -46,13 +46,24 @@ class _ProfilePageState extends State<ProfilePage> {
if (!mounted) return; if (!mounted) return;
// UserScope will be automatically disposed when the holder is destroyed // Update user state to guest
final userScope = appScope.userScopeHolder.scope;
if (userScope != null) {
userScope.userStateManager.logout();
}
// Notify that UserScope has changed // Notify that UserScope has changed
appScope.notifyUserScopeChanged(); appScope.notifyUserScopeChanged();
// Notify router about auth change
appScope.userScopeHolder.notifyAuthChanged();
log('User logged out successfully', name: 'ProfilePage'); log('User logged out successfully', name: 'ProfilePage');
// Navigate to auth page
final router = GoRouter.of(context);
router.go('/auth');
// Show success message // Show success message
messenger.showSnackBar( messenger.showSnackBar(
const SnackBar(content: Text('Logged out successfully')), const SnackBar(content: Text('Logged out successfully')),

View file

@ -20,6 +20,30 @@ GoRouter createAppRouter({
}) { }) {
return GoRouter( return GoRouter(
initialLocation: '/home', initialLocation: '/home',
refreshListenable: userScopeHolder.authNotifier,
redirect: (context, state) {
final isAuthenticated = userScopeHolder.isAuthenticated;
final location = state.uri.path;
// Если пользователь не авторизован
if (!isAuthenticated) {
// Разрешаем доступ только к странице авторизации
if (location == '/auth') {
return null; // Разрешить доступ
}
// Перенаправляем на страницу авторизации
return '/auth';
}
// Если пользователь авторизован и пытается зайти на /auth
if (location == '/auth') {
// Перенаправляем на главную страницу
return '/home';
}
// Разрешаем доступ к остальным страницам
return null;
},
routes: [ routes: [
// Главный Shell с Bottom Navigation // Главный Shell с Bottom Navigation
ShellRoute( ShellRoute(

View file

@ -102,9 +102,8 @@ dev_dependencies:
flutter: flutter:
uses-material-design: true uses-material-design: true
# assets: assets:
# - assets/images/ - icons/
# - assets/icons/
fonts: fonts:
- family: Nunito - family: Nunito

View file

@ -68,11 +68,12 @@ Deployed proper nginx configuration for vscode.mnemo-cards.online:
Created the following scripts in `tools/deploy/`: Created the following scripts in `tools/deploy/`:
1. **fix-vscode-server.sh** - Diagnoses and fixes code-server installation and configuration 1. **fix-vscode-server.sh** - Diagnoses and fixes code-server installation and configuration
2. **check-nginx.sh** - Checks nginx status and reloads configuration 2. **fix-vscode-rate-limit.sh** - Fixes 429 errors by updating rate limits in nginx.conf
3. **deploy-vscode-nginx.sh** - Deploys VSCode nginx configuration to server 3. **check-nginx.sh** - Checks nginx status and reloads configuration
4. **fix-nginx-duplicates.sh** - Removes duplicate nginx configurations 4. **deploy-vscode-nginx.sh** - Deploys VSCode nginx configuration to server
5. **test-vscode-from-server.sh** - Tests VSCode connectivity from server 5. **fix-nginx-duplicates.sh** - Removes duplicate nginx configurations
6. **test-vscode-final.sh** - Final tests of both URLs 6. **test-vscode-from-server.sh** - Tests VSCode connectivity from server
7. **test-vscode-final.sh** - Final tests of both URLs
## How to Use ## How to Use
@ -143,6 +144,13 @@ nginx -t # Test configuration
2. Check nginx error logs: `tail -f /var/log/nginx/error.log` 2. Check nginx error logs: `tail -f /var/log/nginx/error.log`
3. Verify proxy_pass uses 127.0.0.1:8443 (not localhost) 3. Verify proxy_pass uses 127.0.0.1:8443 (not localhost)
### 429 Too Many Requests
If you get `429 (Too Many Requests)` errors when loading VSCode:
1. Run the fix script: `./fix-vscode-rate-limit.sh`
2. Redeploy nginx config: `./deploy-vscode-nginx.sh`
3. The issue is usually caused by too strict rate limiting for static files
4. Static files are now excluded from rate limiting automatically
### SSL Certificate Issues ### SSL Certificate Issues
Certificates are managed by Let's Encrypt and stored at: Certificates are managed by Let's Encrypt and stored at:
``` ```
@ -173,12 +181,22 @@ code-server (127.0.0.1:8443) - VSCode Server
- code-server runs as root user (WorkingDirectory=/root) - code-server runs as root user (WorkingDirectory=/root)
- Authentication is enabled with password - Authentication is enabled with password
- WebSocket support is enabled for VSCode features - WebSocket support is enabled for VSCode features
- Rate limiting is configured to prevent abuse - Rate limiting is configured to prevent abuse:
- General requests: 200 requests/minute (burst: 50)
- Login attempts: 5 requests/minute (burst: 2)
- Static files (JS, CSS, images): No rate limiting
- Automatic restart is configured (Restart=always) - Automatic restart is configured (Restart=always)
- Service is enabled to start on boot (enabled via systemctl) - Service is enabled to start on boot (enabled via systemctl)
## Recent Changes (2025-11-19) ## Recent Changes
### 2025-11-22
1. ✅ Fixed 429 (Too Many Requests) error for static files
- Excluded static files (`/static/`, `/out/`, file extensions) from rate limiting
- Increased general rate limit from 30r/m to 200r/m
- Added caching for static files
### 2025-11-19
1. ✅ Fixed code-server service configuration 1. ✅ Fixed code-server service configuration
2. ✅ Deployed nginx configuration 2. ✅ Deployed nginx configuration
3. ✅ Fixed IPv6/IPv4 issue (localhost → 127.0.0.1) 3. ✅ Fixed IPv6/IPv4 issue (localhost → 127.0.0.1)