Spaces:
Running
Running
Upload 8 files
Browse files- app.py +12 -0
- templates/auth.html +39 -1
app.py
CHANGED
@@ -13,6 +13,12 @@ load_dotenv()
|
|
13 |
app = Flask(__name__)
|
14 |
app.secret_key = os.urandom(24)
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
# MongoDB connection
|
17 |
MONGO_URI = os.getenv('MONGO_URI')
|
18 |
client = MongoClient(MONGO_URI)
|
@@ -85,6 +91,8 @@ def register():
|
|
85 |
|
86 |
user_bookmarks.insert_many(default_bookmarks)
|
87 |
|
|
|
|
|
88 |
session['user_id'] = user_id
|
89 |
session['username'] = username
|
90 |
|
@@ -95,6 +103,7 @@ def login():
|
|
95 |
data = request.json
|
96 |
username = data.get('username')
|
97 |
password = data.get('password')
|
|
|
98 |
|
99 |
if not username or not password:
|
100 |
return jsonify({'error': 'Username and password required'}), 400
|
@@ -104,8 +113,11 @@ def login():
|
|
104 |
if not user or not check_password_hash(user['password'], password):
|
105 |
return jsonify({'error': 'Invalid credentials'}), 401
|
106 |
|
|
|
|
|
107 |
session['user_id'] = user['user_id']
|
108 |
session['username'] = username
|
|
|
109 |
|
110 |
return jsonify({'message': 'Login successful'})
|
111 |
|
|
|
13 |
app = Flask(__name__)
|
14 |
app.secret_key = os.urandom(24)
|
15 |
|
16 |
+
# Configure session to be permanent and last for 30 days
|
17 |
+
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=30)
|
18 |
+
app.config['SESSION_COOKIE_SECURE'] = False # Set to True in production with HTTPS
|
19 |
+
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
20 |
+
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
21 |
+
|
22 |
# MongoDB connection
|
23 |
MONGO_URI = os.getenv('MONGO_URI')
|
24 |
client = MongoClient(MONGO_URI)
|
|
|
91 |
|
92 |
user_bookmarks.insert_many(default_bookmarks)
|
93 |
|
94 |
+
# Make session permanent so it persists across browser sessions
|
95 |
+
session.permanent = True
|
96 |
session['user_id'] = user_id
|
97 |
session['username'] = username
|
98 |
|
|
|
103 |
data = request.json
|
104 |
username = data.get('username')
|
105 |
password = data.get('password')
|
106 |
+
remember_me = data.get('rememberMe', True) # Default to True for backward compatibility
|
107 |
|
108 |
if not username or not password:
|
109 |
return jsonify({'error': 'Username and password required'}), 400
|
|
|
113 |
if not user or not check_password_hash(user['password'], password):
|
114 |
return jsonify({'error': 'Invalid credentials'}), 401
|
115 |
|
116 |
+
# Set session permanence based on user choice
|
117 |
+
session.permanent = remember_me
|
118 |
session['user_id'] = user['user_id']
|
119 |
session['username'] = username
|
120 |
+
session['remember_me'] = remember_me
|
121 |
|
122 |
return jsonify({'message': 'Login successful'})
|
123 |
|
templates/auth.html
CHANGED
@@ -119,6 +119,27 @@
|
|
119 |
color: #999;
|
120 |
}
|
121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
.auth-btn {
|
123 |
padding: 15px 30px;
|
124 |
border: none;
|
@@ -192,6 +213,12 @@
|
|
192 |
<div class="form-group">
|
193 |
<input type="password" class="form-input" id="password" placeholder="Password" required>
|
194 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
<button type="submit" class="auth-btn" id="authBtn">Login</button>
|
196 |
<div class="error-message hidden" id="errorMessage"></div>
|
197 |
<div class="success-message hidden" id="successMessage"></div>
|
@@ -211,6 +238,8 @@
|
|
211 |
const toggleText = document.getElementById('toggleText');
|
212 |
const errorMessage = document.getElementById('errorMessage');
|
213 |
const successMessage = document.getElementById('successMessage');
|
|
|
|
|
214 |
|
215 |
function showError(message) {
|
216 |
errorMessage.textContent = message;
|
@@ -237,10 +266,12 @@
|
|
237 |
authBtn.textContent = 'Login';
|
238 |
toggleText.textContent = "Don't have an account? ";
|
239 |
toggleLink.textContent = 'Sign up';
|
|
|
240 |
} else {
|
241 |
authBtn.textContent = 'Sign Up';
|
242 |
toggleText.textContent = 'Already have an account? ';
|
243 |
toggleLink.textContent = 'Login';
|
|
|
244 |
}
|
245 |
|
246 |
hideMessages();
|
@@ -251,6 +282,7 @@
|
|
251 |
|
252 |
const username = document.getElementById('username').value;
|
253 |
const password = document.getElementById('password').value;
|
|
|
254 |
|
255 |
if (!username || !password) {
|
256 |
showError('Please fill in all fields');
|
@@ -258,6 +290,12 @@
|
|
258 |
}
|
259 |
|
260 |
const endpoint = isLoginMode ? '/login' : '/register';
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
|
262 |
try {
|
263 |
const response = await fetch(endpoint, {
|
@@ -265,7 +303,7 @@
|
|
265 |
headers: {
|
266 |
'Content-Type': 'application/json',
|
267 |
},
|
268 |
-
body: JSON.stringify(
|
269 |
});
|
270 |
|
271 |
const data = await response.json();
|
|
|
119 |
color: #999;
|
120 |
}
|
121 |
|
122 |
+
.checkbox-group {
|
123 |
+
display: flex;
|
124 |
+
align-items: center;
|
125 |
+
gap: 10px;
|
126 |
+
margin-top: 5px;
|
127 |
+
}
|
128 |
+
|
129 |
+
.checkbox-input {
|
130 |
+
width: 18px;
|
131 |
+
height: 18px;
|
132 |
+
accent-color: #667eea;
|
133 |
+
cursor: pointer;
|
134 |
+
}
|
135 |
+
|
136 |
+
.checkbox-label {
|
137 |
+
font-size: 0.9rem;
|
138 |
+
color: #666;
|
139 |
+
cursor: pointer;
|
140 |
+
user-select: none;
|
141 |
+
}
|
142 |
+
|
143 |
.auth-btn {
|
144 |
padding: 15px 30px;
|
145 |
border: none;
|
|
|
213 |
<div class="form-group">
|
214 |
<input type="password" class="form-input" id="password" placeholder="Password" required>
|
215 |
</div>
|
216 |
+
<div class="form-group" id="rememberMeGroup">
|
217 |
+
<div class="checkbox-group">
|
218 |
+
<input type="checkbox" class="checkbox-input" id="rememberMe" checked>
|
219 |
+
<label for="rememberMe" class="checkbox-label">Remember me for 30 days</label>
|
220 |
+
</div>
|
221 |
+
</div>
|
222 |
<button type="submit" class="auth-btn" id="authBtn">Login</button>
|
223 |
<div class="error-message hidden" id="errorMessage"></div>
|
224 |
<div class="success-message hidden" id="successMessage"></div>
|
|
|
238 |
const toggleText = document.getElementById('toggleText');
|
239 |
const errorMessage = document.getElementById('errorMessage');
|
240 |
const successMessage = document.getElementById('successMessage');
|
241 |
+
const rememberMeGroup = document.getElementById('rememberMeGroup');
|
242 |
+
const rememberMeCheckbox = document.getElementById('rememberMe');
|
243 |
|
244 |
function showError(message) {
|
245 |
errorMessage.textContent = message;
|
|
|
266 |
authBtn.textContent = 'Login';
|
267 |
toggleText.textContent = "Don't have an account? ";
|
268 |
toggleLink.textContent = 'Sign up';
|
269 |
+
rememberMeGroup.style.display = 'block'; // Show remember me for login
|
270 |
} else {
|
271 |
authBtn.textContent = 'Sign Up';
|
272 |
toggleText.textContent = 'Already have an account? ';
|
273 |
toggleLink.textContent = 'Login';
|
274 |
+
rememberMeGroup.style.display = 'none'; // Hide remember me for signup
|
275 |
}
|
276 |
|
277 |
hideMessages();
|
|
|
282 |
|
283 |
const username = document.getElementById('username').value;
|
284 |
const password = document.getElementById('password').value;
|
285 |
+
const rememberMe = rememberMeCheckbox.checked;
|
286 |
|
287 |
if (!username || !password) {
|
288 |
showError('Please fill in all fields');
|
|
|
290 |
}
|
291 |
|
292 |
const endpoint = isLoginMode ? '/login' : '/register';
|
293 |
+
const requestBody = { username, password };
|
294 |
+
|
295 |
+
// Add rememberMe flag for login requests
|
296 |
+
if (isLoginMode) {
|
297 |
+
requestBody.rememberMe = rememberMe;
|
298 |
+
}
|
299 |
|
300 |
try {
|
301 |
const response = await fetch(endpoint, {
|
|
|
303 |
headers: {
|
304 |
'Content-Type': 'application/json',
|
305 |
},
|
306 |
+
body: JSON.stringify(requestBody)
|
307 |
});
|
308 |
|
309 |
const data = await response.json();
|