11import os
2- from flask import Flask , redirect , url_for , session , render_template_string , render_template
2+ import logging
3+ from logging .handlers import RotatingFileHandler
4+
5+ from flask import Flask , redirect , url_for , session , render_template , render_template_string
36from authlib .integrations .flask_client import OAuth
47from authlib .integrations .base_client .errors import OAuthError
58from dotenv import load_dotenv
69from functools import wraps
710
8- # Загружаем переменные из .env файла
11+ # ─── Load environment ──────────────────────────────────────────────────────────
912load_dotenv ()
1013
14+ # ─── Flask app setup ──────────────────────────────────────────────────────────
1115app = Flask (__name__ )
12- app .secret_key = os .getenv ("FLASK_SECRET_KEY" , "2tKBGXHD9rjlJQvV1Z7ukCkDeyOjWvF7" )
16+ app .secret_key = os .getenv ("FLASK_SECRET_KEY" , "default_secret_key" )
17+
18+ # ─── Logging to file ──────────────────────────────────────────────────────────
19+ # ensure logs directory exists
20+ os .makedirs ("logs" , exist_ok = True )
21+
22+ # configure root logger
23+ formatter = logging .Formatter (
24+ "%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s"
25+ )
26+
27+ file_handler = RotatingFileHandler (
28+ filename = "logs/flask-app.log" ,
29+ maxBytes = 10 * 1024 * 1024 , # 10 MB
30+ backupCount = 5 ,
31+ encoding = "utf-8"
32+ )
33+ file_handler .setLevel (logging .INFO )
34+ file_handler .setFormatter (formatter )
1335
14- # Регистрация клиента OAuth
36+ # attach handler to Flask's and werkzeug's loggers
37+ app .logger .setLevel (logging .INFO )
38+ app .logger .addHandler (file_handler )
39+ logging .getLogger ("werkzeug" ).addHandler (file_handler )
40+
41+ app .logger .info ("Starting Flask application" )
42+
43+ # ─── OAuth / Keycloak setup ───────────────────────────────────────────────────
1544oauth = OAuth (app )
1645oauth .register (
1746 name = "keycloak" ,
2049 server_metadata_url = os .getenv ("KEYCLOAK_METADATA_URL" ),
2150 client_kwargs = {
2251 "scope" : "openid profile email" ,
23- "prompt" : "login" # всегда заставлять логиниться
52+ "prompt" : "login" ,
2453 },
2554)
2655
27- # Декоратор для проверки ролей
56+ # ─── Role check decorator ──────────────────────────────────────────────────────
2857def role_required (role ):
2958 def decorator (f ):
3059 @wraps (f )
3160 def decorated_function (* args , ** kwargs ):
3261 if 'user' not in session :
62+ app .logger .warning (f"Unauthorized access attempt to { url_for (f .__name__ )} " )
3363 return redirect (url_for ('login' ))
34-
3564 user_roles = session ['user' ].get ('realm_access' , {}).get ('roles' , [])
3665 if role not in user_roles :
66+ app .logger .warning (f"Forbidden: user lacks role '{ role } ' for { url_for (f .__name__ )} " )
3767 return "<h1>403 Forbidden</h1><p>You do not have permission to access this page.</p>" , 403
38-
3968 return f (* args , ** kwargs )
40-
4169 return decorated_function
4270 return decorator
4371
44- # Главная страница
72+ # ─── Routes ────────────────────────────────────────────────────────────────────
4573@app .route ("/" )
4674def index ():
4775 user = session .get ("user" )
48- if user :
49- return render_template ('index.html' , user = user )
50- else :
51- return render_template ('index.html' )
76+ return render_template ('index.html' , user = user )
5277
53- # Страница входа
54- @app .route ("/login" , methods = ["GET" ])
78+ @app .route ("/login" )
5579def login ():
5680 session .clear ()
57- nonce = os .urandom (16 ).hex () # Генерация nonce
81+ nonce = os .urandom (16 ).hex ()
5882 session ["nonce" ] = nonce
5983 redirect_uri = url_for ("auth" , _external = True )
6084 return oauth .keycloak .authorize_redirect (redirect_uri , nonce = nonce )
6185
62- # Обработка авторизации
6386@app .route ("/auth" )
6487def auth ():
6588 try :
6689 token = oauth .keycloak .authorize_access_token ()
67- print ("=== FULL TOKEN DATA ===" )
68- print (token )
69-
90+ app .logger .info (f"OAuth token received: { token .keys ()} " )
7091 nonce = session .pop ("nonce" , None )
7192 user = oauth .keycloak .parse_id_token (token , nonce = nonce )
7293 session ["user" ] = user
73- return redirect ("/" )
94+ app .logger .info (f"User logged in: { user .get ('preferred_username' )} " )
95+ return redirect (url_for ("index" ))
7496 except OAuthError as e :
97+ app .logger .error (f"OAuthError during auth: { e .error } - { e .description } " )
7598 return f"<h1>Authentication failed</h1><p>{ e .error } : { e .description } </p>" , 400
7699 except Exception as e :
100+ app .logger .exception ("Unexpected error during auth" )
77101 return f"<h1>Unexpected error</h1><p>{ str (e )} </p>" , 500
78102
79- # Общая защищённая страница (только вход)
80103@app .route ("/protected" )
81104def protected ():
82105 if 'user' not in session :
83106 return "<h1>401 Unauthorized</h1><p>You must log in to access this page.</p>" , 401
84107 return render_template ('protected.html' )
85108
86- # Страница для администраторов
87109@app .route ("/admin" )
88110@role_required ("admin" )
89111def admin ():
90112 return render_template ('admin.html' )
91113
92- # Страница для пользователей с ролью user
93114@app .route ("/user" )
94115@role_required ("user" )
95116def user_page ():
96117 return render_template ('user.html' )
97118
98- # Страница для пользователей с ролью manager
99119@app .route ("/manager" )
100120@role_required ("manager" )
101121def manager_page ():
102122 return render_template ('manager.html' )
103123
104- # Страница для пользователей с ролью viewer
105124@app .route ("/viewer" )
106125@role_required ("viewer" )
107126def viewer_page ():
108127 return render_template ('viewer.html' )
109128
110- # Выход
111129@app .route ("/logout" , methods = ["POST" ])
112130def logout ():
113131 session .clear ()
114132 redirect_uri = url_for ('index' , _external = True )
115133 logout_url = f"{ os .getenv ('KEYCLOAK_LOGOUT_URL' )} ?redirect_uri={ redirect_uri } "
116- print ( " logout" )
134+ app . logger . info ( "User logged out, redirecting to Keycloak logout" )
117135 return redirect (logout_url )
118136
119- # Обработчики ошибок
137+ # ─── Error handlers ────────────────────────────────────────────────────────────
120138@app .errorhandler (401 )
121139def unauthorized_error (error ):
122140 return render_template_string ("<h1>401 Unauthorized</h1><p>You must log in to access this page.</p>" ), 401
@@ -125,5 +143,6 @@ def unauthorized_error(error):
125143def forbidden_error (error ):
126144 return render_template_string ("<h1>403 Forbidden</h1><p>You do not have permission to access this page.</p>" ), 403
127145
146+ # ─── Main ─────────────────────────────────────────────────────────────────────
128147if __name__ == "__main__" :
129- app .run (host = "0.0.0.0" , port = 5000 , debug = True )
148+ app .run (host = "0.0.0.0" , port = 5000 , debug = True )
0 commit comments