Oviya commited on
Commit
6d37fa5
Β·
1 Parent(s): 449b610

Add backend: requirements, Dockerfile, verification.py

Browse files
Files changed (3) hide show
  1. Dockerfile +22 -0
  2. requirements.txt +6 -0
  3. verification.py +302 -0
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+ ENV DEBIAN_FRONTEND=noninteractive
3
+
4
+ # ODBC + Microsoft SQL Server ODBC driver 17 (matches your connection string)
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ curl gnupg2 apt-transport-https unixodbc unixodbc-dev \
7
+ && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \
8
+ && echo "deb [arch=amd64] https://packages.microsoft.com/debian/12/prod bookworm main" > /etc/apt/sources.list.d/mssql-release.list \
9
+ && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql17 \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ WORKDIR /app
13
+ COPY requirements.txt /app/
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
+ COPY . /app
16
+
17
+ # Spaces expect your server on port 7860
18
+ EXPOSE 7860
19
+
20
+ # Your Flask app object is `app` in verification.py
21
+ # app.run() will NOT execute because gunicorn imports the module.
22
+ CMD ["gunicorn", "--workers", "2", "--threads", "4", "--timeout", "120", "-b", "0.0.0.0:7860", "verification:app"]
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Flask
2
+ flask-cors
3
+ pyodbc
4
+ bcrypt
5
+ PyJWT
6
+ gunicorn
verification.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pyodbc
2
+ from flask import Flask, request, jsonify, make_response
3
+ from flask_cors import CORS
4
+ import jwt
5
+ import datetime
6
+ import bcrypt
7
+ from functools import wraps # Import wraps for decorators
8
+ import pdb
9
+ import os # add this
10
+
11
+ app = Flask(__name__)
12
+ # CORS(app,
13
+ # supports_credentials=True,
14
+ # resources={r"/*": {"origins": "http://localhost:4200"}})
15
+
16
+
17
+
18
+ CORS(app, supports_credentials=True, origins=["http://localhost:4200"])
19
+
20
+ # CORS(app, supports_credentials=True, origins=["http://localhost:4200"])
21
+ # CORS(app, supports_credentials=True) # Enable CORS with credentials
22
+ app.config['SECRET_KEY'] = '96c63da06374c1bde332516f3acbd23c84f35f90d8a6321a25d790a0a451af32'
23
+
24
+ # βœ… Configure SQL Server Connection (Windows Authentication)
25
+ DB_SERVER = "pykara-sqlserver.c5aosm6ie5j3.eu-north-1.rds.amazonaws.com,1433"
26
+ DB_DATABASE = "AuthenticationDB1"
27
+
28
+ # βœ… Create Connection String for Windows Authentication
29
+ # conn_str = f"DRIVER={{SQL Server}};SERVER={DB_SERVER};DATABASE={DB_DATABASE};Trusted_Connection=yes"
30
+ # Use Windows Authentication only for local SQLEXPRESS; otherwise use SQL login from environment
31
+ if DB_SERVER.lower().startswith("localhost") or "\\" in DB_SERVER:
32
+ # Local dev: Windows Authentication
33
+ conn_str = f"DRIVER={{SQL Server}};SERVER={DB_SERVER};DATABASE={DB_DATABASE};Trusted_Connection=yes"
34
+ else:
35
+ # RDS: SQL authentication (no hard-coding; read from env)
36
+ conn_str = (
37
+ "DRIVER={ODBC Driver 17 for SQL Server};"
38
+ f"SERVER={DB_SERVER};DATABASE={DB_DATABASE};"
39
+ f"UID={os.getenv('DB_USER')};PWD={os.getenv('DB_PASSWORD')};"
40
+ "Encrypt=yes;TrustServerCertificate=yes"
41
+ )
42
+
43
+
44
+ # βœ… Function to Connect to SQL Server
45
+ def get_db_connection():
46
+ return pyodbc.connect(conn_str)
47
+
48
+ # βœ… Initialize the database (Create required tables if they do not exist)
49
+ def init_db():
50
+ conn = get_db_connection()
51
+ cursor = conn.cursor()
52
+
53
+ # βœ… Create Users Table
54
+ cursor.execute("""
55
+ IF OBJECT_ID('Users', 'U') IS NULL
56
+ CREATE TABLE Users (
57
+ id INT IDENTITY(1,1) PRIMARY KEY,
58
+ username NVARCHAR(100) UNIQUE NOT NULL,
59
+ password_hash NVARCHAR(500) NOT NULL,
60
+ role NVARCHAR(50) DEFAULT 'user'
61
+ )
62
+ """)
63
+
64
+ # βœ… Create Blacklisted Tokens Table (For Logout)
65
+ cursor.execute("""
66
+ IF OBJECT_ID('BlacklistedTokens', 'U') IS NULL
67
+ CREATE TABLE BlacklistedTokens (
68
+ id INT IDENTITY(1,1) PRIMARY KEY,
69
+ token NVARCHAR(1000) UNIQUE NOT NULL,
70
+ created_at DATETIME DEFAULT GETDATE()
71
+ )
72
+ """)
73
+
74
+ # βœ… Create Refresh Tokens Table (For Secure Token Refresh)
75
+ cursor.execute("""
76
+ IF OBJECT_ID('RefreshTokens', 'U') IS NULL
77
+ CREATE TABLE RefreshTokens (
78
+ id INT IDENTITY(1,1) PRIMARY KEY,
79
+ username NVARCHAR(100) NOT NULL,
80
+ token NVARCHAR(1000) UNIQUE NOT NULL,
81
+ created_at DATETIME DEFAULT GETDATE(),
82
+ FOREIGN KEY (username) REFERENCES Users(username) ON DELETE CASCADE
83
+ )
84
+ """)
85
+
86
+ conn.commit()
87
+ conn.close()
88
+
89
+ # βœ… Initialize Database (Call once when Flask starts)
90
+ init_db()
91
+
92
+ # βœ… Implement token verification function
93
+ def token_required(f):
94
+ @wraps(f)
95
+ def decorated(*args, **kwargs):
96
+ print("πŸ” Accessing protected route:", request.path)
97
+ print("πŸͺ Cookies received:", request.cookies)
98
+ token = request.cookies.get('access_token') # Access token from cookies
99
+
100
+ if not token:
101
+ return jsonify({"message": "Token is missing"}), 401
102
+
103
+ try:
104
+ # βœ… Check if token is blacklisted in SQL Server
105
+ conn = get_db_connection()
106
+ cursor = conn.cursor()
107
+ cursor.execute("SELECT token FROM BlacklistedTokens WHERE token = ?", (token,))
108
+ result = cursor.fetchone()
109
+ conn.close()
110
+
111
+ if result:
112
+ return jsonify({"message": "Token has been revoked. Please log in again."}), 401
113
+
114
+ data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
115
+ return f(data['username'], *args, **kwargs)
116
+
117
+ except jwt.ExpiredSignatureError:
118
+ return jsonify({"message": "Token has expired"}), 401
119
+ except jwt.InvalidTokenError:
120
+ return jsonify({"message": "Invalid token"}), 401
121
+
122
+ return decorated
123
+
124
+ # βœ… Protected route: Dashboard
125
+ @app.route('/dashboard', methods=['GET'])
126
+ @token_required
127
+ def dashboard(username):
128
+ return jsonify({"message": f"Welcome {username} to your dashboard!"})
129
+
130
+
131
+
132
+ # βœ… Login: Generate Access & Refresh Tokens (Uses SQL Server for authentication)
133
+ @app.route('/login', methods=['POST'])
134
+ def login():
135
+ print("Login Request works")
136
+ data = request.json
137
+ username = data.get('username')
138
+ password = data.get('password')
139
+
140
+ conn = get_db_connection()
141
+ cursor = conn.cursor()
142
+
143
+ # βœ… Retrieve user from SQL Server
144
+ cursor.execute("SELECT password_hash FROM Users WHERE username = ?", (username,))
145
+ user = cursor.fetchone()
146
+
147
+ conn.close()
148
+
149
+ if not user:
150
+ return jsonify({"message": "Invalid credentials"}), 401
151
+
152
+ stored_hashed_password = user[0]
153
+
154
+ if bcrypt.checkpw(password.encode('utf-8'), stored_hashed_password.encode('utf-8')):
155
+ # βœ… Generate Access Token (expires in 15 minutes)
156
+ access_token = jwt.encode(
157
+ {'username': username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=15)},
158
+ app.config['SECRET_KEY'],
159
+ algorithm="HS256"
160
+ )
161
+ print(f"Generated Access Token: {access_token}")
162
+ # βœ… Generate Refresh Token (expires in 7 days)
163
+ refresh_token = jwt.encode(
164
+ {'username': username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=7)},
165
+ app.config['SECRET_KEY'],
166
+ algorithm="HS256"
167
+ )
168
+
169
+
170
+ print(f"Generated Refresh Token: {refresh_token}")
171
+
172
+ # βœ… Store refresh token in SQL Server
173
+ conn = get_db_connection()
174
+ cursor = conn.cursor()
175
+ cursor.execute("INSERT INTO RefreshTokens (username, token) VALUES (?, ?)", (username, refresh_token))
176
+ conn.commit()
177
+ conn.close()
178
+
179
+
180
+ # Set the tokens in cookies
181
+ resp = make_response(jsonify({"message": "Login successful"}))
182
+
183
+ resp.set_cookie('access_token', access_token, httponly=True, secure=False, samesite='Lax', max_age=900) # Adjusted secure flag for development (set to True for production)
184
+ resp.set_cookie('refresh_token', refresh_token, httponly=True, secure=False, samesite='Lax', max_age=7*24*60*60 ) # Same adjustment for refresh token
185
+ print(f"Set Cookies: access_token={access_token}, refresh_token={refresh_token}")
186
+
187
+
188
+ return resp
189
+ return jsonify({"message": "Invalid credentials"}), 401
190
+
191
+ # βœ… Refresh Token: Get New Access Token
192
+ @app.route('/refresh', methods=['POST'])
193
+ def refresh():
194
+ refresh_token = request.cookies.get("refresh_token")
195
+
196
+ if not refresh_token:
197
+ return jsonify({'message': 'Refresh token is missing'}), 400
198
+
199
+ # Step 1: Decode and verify expiration
200
+ try:
201
+ payload = jwt.decode(refresh_token, app.config['SECRET_KEY'], algorithms=["HS256"])
202
+ except jwt.ExpiredSignatureError:
203
+ return jsonify({'message': 'Refresh token has expired'}), 401
204
+ except jwt.InvalidTokenError:
205
+ return jsonify({'message': 'Invalid refresh token'}), 401
206
+
207
+ # Step 2: Check if the token exists in DB
208
+ conn = get_db_connection()
209
+ cursor = conn.cursor()
210
+ cursor.execute("SELECT username FROM RefreshTokens WHERE token = ?", (refresh_token,))
211
+ result = cursor.fetchone()
212
+ conn.close()
213
+
214
+ if not result:
215
+ return jsonify({'message': 'Invalid refresh token'}), 401
216
+
217
+ username = result[0]
218
+
219
+ # Step 3: Generate new access token
220
+ new_access_token = jwt.encode(
221
+ {'username': username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=15)},
222
+ app.config['SECRET_KEY'],
223
+ algorithm="HS256"
224
+ )
225
+
226
+ resp = make_response(jsonify({'access_token': new_access_token}))
227
+ resp.set_cookie(
228
+ 'access_token',
229
+ new_access_token,
230
+ httponly=True,
231
+ secure=False,
232
+ samesite='Lax',
233
+ max_age=900 # 15 minutes
234
+ )
235
+
236
+ return resp
237
+
238
+
239
+
240
+
241
+ @app.route('/logout', methods=['POST'])
242
+ @token_required
243
+ def logout(username):
244
+ print("Logout Request works")
245
+ print("Incoming Cookies:", request.cookies)
246
+
247
+ # βœ… Get the access token from cookies
248
+ token = request.cookies.get('access_token')
249
+ print("Logout Request - Token received:", token)
250
+
251
+ if not token:
252
+ return jsonify({"message": "Invalid token format"}), 401
253
+
254
+ try:
255
+ print("Decoding token...")
256
+ # βœ… Decode the token manually to extract username
257
+ data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
258
+ username = data['username']
259
+ print(f"Token decoded successfully. Username: {username}, Expiry: {data['exp']}")
260
+
261
+ except jwt.ExpiredSignatureError:
262
+ return jsonify({"message": "Token has expired"}), 401
263
+ except jwt.InvalidTokenError:
264
+ return jsonify({"message": "Invalid token"}), 401
265
+
266
+ print(f"Blacklisting token: {token}")
267
+
268
+ # βœ… Store the revoked token in SQL Server **only if it's not already blacklisted**
269
+ conn = get_db_connection()
270
+ cursor = conn.cursor()
271
+
272
+ # Check if the token is already blacklisted
273
+ cursor.execute("SELECT token FROM BlacklistedTokens WHERE token = ?", (token,))
274
+ existing_token = cursor.fetchone()
275
+
276
+ if not existing_token:
277
+ cursor.execute("INSERT INTO BlacklistedTokens (token) VALUES (?)", (token,))
278
+
279
+ # βœ… Remove refresh token for the user
280
+ cursor.execute("DELETE FROM RefreshTokens WHERE username = ?", (username,))
281
+
282
+ conn.commit()
283
+ conn.close()
284
+
285
+ # βœ… Clear cookies
286
+ resp = make_response(jsonify({"message": "Logged out successfully!"}))
287
+ resp.delete_cookie('access_token', path='/')
288
+ resp.delete_cookie('refresh_token', path='/')
289
+
290
+ return resp
291
+
292
+ @app.route('/check-auth', methods=['GET'])
293
+ @token_required
294
+ def check_auth(username):
295
+ return jsonify({"message": "Authenticated", "username": username}), 200
296
+
297
+
298
+
299
+
300
+
301
+ if __name__ == '__main__':
302
+ app.run(debug=True)