Coverage for app_modules/initializer.py: 100%

46 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-08-20 00:55 +0200

1""" 

2initializer.py 

3 

4Purpose: 

5 Build and configure the Flask app, applying configuration, registering 

6 teardown hooks, assets, and all blueprints. This keeps `app.py` minimal so it 

7 only imports the created app and optionally runs a dev server. 

8 

9Exports: 

10 - create_app() -> Flask 

11""" 

12 

13import os 

14from flask import Flask 

15from .paths import ROOT_DIR, STATIC_PATH 

16from .config import apply_config 

17from .db import register_teardown 

18from .assets import register_assets 

19from .views import bp as views_bp 

20from .cameras_api import bp as cameras_api_bp 

21from .gallery import bp as gallery_bp 

22from .map_routes import bp as map_bp 

23from .admin_routes import bp as admin_bp 

24from .media_routes import bp as media_bp 

25from .security_enhancements import security_headers_middleware 

26 

27 

28def create_app() -> Flask: 

29 app = Flask( 

30 __name__, 

31 template_folder=os.path.join(ROOT_DIR, 'templates'), 

32 static_folder=STATIC_PATH, 

33 static_url_path='/static' 

34 ) 

35 

36 apply_config(app) 

37 register_teardown(app) 

38 register_assets(app) 

39 

40 # Enhanced security headers using security_enhancements module 

41 @app.after_request 

42 def add_security_headers(resp): 

43 return security_headers_middleware(resp) 

44 

45 # Mount blueprints (no prefixes to preserve existing URLs) 

46 app.register_blueprint(views_bp) 

47 app.register_blueprint(cameras_api_bp) 

48 app.register_blueprint(gallery_bp) 

49 app.register_blueprint(map_bp) 

50 app.register_blueprint(admin_bp) 

51 app.register_blueprint(media_bp) 

52 

53 # Minimal CSRF token support for JSON and forms 

54 # Token is stored in session and mirrored in a meta tag on HTML pages 

55 import secrets 

56 from flask import session, request, abort, g 

57 

58 @app.before_request 

59 def ensure_csrf_token(): 

60 # Create token if not present 

61 if 'csrf_token' not in session: 

62 session['csrf_token'] = secrets.token_urlsafe(32) 

63 # Per-request CSP nonce 

64 g.csp_nonce = secrets.token_urlsafe(16) 

65 

66 @app.context_processor 

67 def inject_csrf_token(): 

68 return { 'CSRF_TOKEN': session.get('csrf_token', ''), 'CSP_NONCE': getattr(g, 'csp_nonce', '') } 

69 

70 # Enforce CSRF on state-changing requests 

71 @app.before_request 

72 def csrf_protect(): 

73 if request.method in ('POST', 'PUT', 'PATCH', 'DELETE'): 

74 # Allow login to work before token is set, but encourage adding token later 

75 if request.endpoint in {'views.login_root', 'views.login_login', 'views.login_post_alias'}: 

76 return 

77 token = request.headers.get('X-CSRF-Token') or request.form.get('csrf_token') or request.args.get('csrf_token') 

78 if not token or token != session.get('csrf_token'): 

79 abort(403) 

80 

81 return app 

82 

83