Fossil SCM

Added "Remember me?" checkbox to login (default=on). Corrected cgi_set_cookie() to immediately expire the cookie for a negative lifetime (it was previously re-setting the cookie as a session cookie for that case).

stephan 2020-07-26 19:57 login-session-cookie
Commit 32975aabe7ea4c492410f302b48f8dcb5c6b27a84054d5d73ad86d352d134be7
2 files changed +8 -5 +30 -14
+8 -5
--- src/cgi.c
+++ src/cgi.c
@@ -234,11 +234,12 @@
234234
235235
/*
236236
** Set a cookie by queuing up the appropriate HTTP header output. If
237237
** !g.isHTTP, this is a no-op.
238238
**
239
-** Zero lifetime implies a session cookie.
239
+** Zero lifetime implies a session cookie. A negative one expires
240
+** the cookie immediately.
240241
*/
241242
void cgi_set_cookie(
242243
const char *zName, /* Name of the cookie */
243244
const char *zValue, /* Value of the cookie. Automatically escaped */
244245
const char *zPath, /* Path cookie applies to. NULL means "/" */
@@ -251,17 +252,19 @@
251252
if( zPath[0]==0 ) zPath = "/";
252253
}
253254
if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){
254255
zSecure = " secure;";
255256
}
256
- if( lifetime>0 ){
257
+ if( lifetime!=0 ){
257258
blob_appendf(&extraHeader,
258
- "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly;%s Version=1\r\n",
259
- zName, zValue, zPath, lifetime, zSecure);
259
+ "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; "
260
+ "SameSite=strict; %s Version=1\r\n",
261
+ zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure);
260262
}else{
261263
blob_appendf(&extraHeader,
262
- "Set-Cookie: %s=%t; Path=%s; HttpOnly;%s Version=1\r\n",
264
+ "Set-Cookie: %s=%t; Path=%s; HttpOnly; SameSite=strict; "
265
+ "%s Version=1\r\n",
263266
zName, zValue, zPath, zSecure);
264267
}
265268
}
266269
267270
268271
--- src/cgi.c
+++ src/cgi.c
@@ -234,11 +234,12 @@
234
235 /*
236 ** Set a cookie by queuing up the appropriate HTTP header output. If
237 ** !g.isHTTP, this is a no-op.
238 **
239 ** Zero lifetime implies a session cookie.
 
240 */
241 void cgi_set_cookie(
242 const char *zName, /* Name of the cookie */
243 const char *zValue, /* Value of the cookie. Automatically escaped */
244 const char *zPath, /* Path cookie applies to. NULL means "/" */
@@ -251,17 +252,19 @@
251 if( zPath[0]==0 ) zPath = "/";
252 }
253 if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){
254 zSecure = " secure;";
255 }
256 if( lifetime>0 ){
257 blob_appendf(&extraHeader,
258 "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly;%s Version=1\r\n",
259 zName, zValue, zPath, lifetime, zSecure);
 
260 }else{
261 blob_appendf(&extraHeader,
262 "Set-Cookie: %s=%t; Path=%s; HttpOnly;%s Version=1\r\n",
 
263 zName, zValue, zPath, zSecure);
264 }
265 }
266
267
268
--- src/cgi.c
+++ src/cgi.c
@@ -234,11 +234,12 @@
234
235 /*
236 ** Set a cookie by queuing up the appropriate HTTP header output. If
237 ** !g.isHTTP, this is a no-op.
238 **
239 ** Zero lifetime implies a session cookie. A negative one expires
240 ** the cookie immediately.
241 */
242 void cgi_set_cookie(
243 const char *zName, /* Name of the cookie */
244 const char *zValue, /* Value of the cookie. Automatically escaped */
245 const char *zPath, /* Path cookie applies to. NULL means "/" */
@@ -251,17 +252,19 @@
252 if( zPath[0]==0 ) zPath = "/";
253 }
254 if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){
255 zSecure = " secure;";
256 }
257 if( lifetime!=0 ){
258 blob_appendf(&extraHeader,
259 "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; "
260 "SameSite=strict; %s Version=1\r\n",
261 zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure);
262 }else{
263 blob_appendf(&extraHeader,
264 "Set-Cookie: %s=%t; Path=%s; HttpOnly; SameSite=strict; "
265 "%s Version=1\r\n",
266 zName, zValue, zPath, zSecure);
267 }
268 }
269
270
271
+30 -14
--- src/login.c
+++ src/login.c
@@ -262,11 +262,13 @@
262262
**
263263
** If zDest is not NULL then the generated cookie is copied to
264264
** *zDdest and ownership is transfered to the caller (who should
265265
** eventually pass it to free()).
266266
**
267
-** If bSessionCookie is true, the cookie will be a session cookie.
267
+** If bSessionCookie is true, the cookie will be a session cookie
268
+** and the [user].[cexpire] and [user].[cookie] entries will not be
269
+** modified.
268270
*/
269271
void login_set_user_cookie(
270272
const char *zUsername, /* User's name */
271273
int uid, /* User's ID */
272274
char **zDest, /* Optional: store generated cookie value. */
@@ -273,31 +275,33 @@
273275
int bSessionCookie /* True for session-only cookie */
274276
){
275277
const char *zCookieName = login_cookie_name();
276278
const char *zExpire = bSessionCookie ? 0 : db_get("cookie-expire","8766");
277279
int expires = bSessionCookie ? 0 : atoi(zExpire)*3600;
278
- char *zHash;
280
+ char *zHash = 0;
279281
char *zCookie;
280282
const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
281283
282284
assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
283
- zHash = db_text(0,
285
+ if(bSessionCookie==0){
286
+ zHash = db_text(0,
284287
"SELECT cookie FROM user"
285288
" WHERE uid=%d"
286289
" AND cexpire>julianday('now')"
287290
" AND length(cookie)>30",
288291
uid);
292
+ }
289293
if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
290294
zCookie = login_gen_user_cookie_value(zUsername, zHash);
291295
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
292296
record_login_attempt(zUsername, zIpAddr, 1);
293
- db_multi_exec(
294
- "UPDATE user SET cookie=%Q,"
295
- " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
296
- zHash, expires, uid
297
- );
298
- free(zHash);
297
+ if(bSessionCookie==0){
298
+ db_multi_exec("UPDATE user SET cookie=%Q,"
299
+ " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
300
+ zHash, expires, uid);
301
+ }
302
+ fossil_free(zHash);
299303
if( zDest ){
300304
*zDest = zCookie;
301305
}else{
302306
free(zCookie);
303307
}
@@ -521,20 +525,21 @@
521525
char *zErrMsg = "";
522526
int uid; /* User id logged in user */
523527
char *zSha1Pw;
524528
const char *zIpAddr; /* IP address of requestor */
525529
const char *zReferer;
526
- int noAnon = P("noanon")!=0;
530
+ const int noAnon = P("noanon")!=0;
531
+ int rememberMe; /* If true, use persistent cookie,
532
+ else session cookie */
527533
528534
login_check_credentials();
529535
fossil_redirect_to_https_if_needed(1);
530536
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
531537
constant_time_cmp_function, 0, 0);
532538
zUsername = P("u");
533539
zPasswd = P("p");
534540
anonFlag = g.zLogin==0 && PB("anon");
535
-
536541
/* Handle log-out requests */
537542
if( P("out") ){
538543
login_clear_login_data();
539544
redirect_to_g();
540545
return;
@@ -606,12 +611,18 @@
606611
}
607612
}
608613
zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */
609614
zReferer = P("HTTP_REFERER");
610615
uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs"));
616
+ if(zUsername==0){
617
+ /* Initial login page hit. */
618
+ rememberMe = 1 /* seems like a sensible default */;
619
+ }else{
620
+ rememberMe = P("remember")!=0;
621
+ }
611622
if( uid>0 ){
612
- login_set_anon_cookie(zIpAddr, NULL, 0);
623
+ login_set_anon_cookie(zIpAddr, NULL, rememberMe?0:1);
613624
record_login_attempt("anonymous", zIpAddr, 1);
614625
redirect_to_g();
615626
}
616627
if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
617628
/* Attempting to log in as a user other than anonymous.
@@ -631,11 +642,11 @@
631642
** HASH/PROJECT/LOGIN
632643
**
633644
** where HASH is a random hex number, PROJECT is either project
634645
** code prefix, and LOGIN is the user name.
635646
*/
636
- login_set_user_cookie(zUsername, uid, NULL, 0);
647
+ login_set_user_cookie(zUsername, uid, NULL, rememberMe?0:1);
637648
redirect_to_g();
638649
}
639650
}
640651
style_header("Login/Logout");
641652
style_adunit_config(ADUNIT_OFF);
@@ -652,10 +663,11 @@
652663
@ <p>Login as <b>anonymous</b> or any named user
653664
@ to access page <b>%h(zAbbrev)</b>.
654665
}else{
655666
@ <p>Login as a named user to access page <b>%h(zAbbrev)</b>.
656667
}
668
+ fossil_free(zAbbrev);
657669
}
658670
if( g.sslNotAvailable==0
659671
&& strncmp(g.zBaseURL,"https:",6)!=0
660672
&& db_get_boolean("https-login",0)
661673
){
@@ -712,11 +724,15 @@
712724
}
713725
@ </span></td></tr>
714726
}
715727
@ <tr>
716728
@ <td></td>
717
- @ <td><input type="submit" name="in" value="Login"></td>
729
+ @ <td><input type="submit" name="in" value="Login">
730
+ @ <input type="checkbox" name="remember" value="1" \
731
+ @ %s(rememberMe ? "checked=\"checked\"" : "")>Remember me?
732
+ @ (If checked, login will use a persistent cookie, else it
733
+ @ will use a session cookie.)</td>
718734
@ </tr>
719735
if( !noAnon && login_self_register_available(0) ){
720736
@ <tr>
721737
@ <td></td>
722738
@ <td><input type="submit" name="self" value="Create A New Account">
723739
--- src/login.c
+++ src/login.c
@@ -262,11 +262,13 @@
262 **
263 ** If zDest is not NULL then the generated cookie is copied to
264 ** *zDdest and ownership is transfered to the caller (who should
265 ** eventually pass it to free()).
266 **
267 ** If bSessionCookie is true, the cookie will be a session cookie.
 
 
268 */
269 void login_set_user_cookie(
270 const char *zUsername, /* User's name */
271 int uid, /* User's ID */
272 char **zDest, /* Optional: store generated cookie value. */
@@ -273,31 +275,33 @@
273 int bSessionCookie /* True for session-only cookie */
274 ){
275 const char *zCookieName = login_cookie_name();
276 const char *zExpire = bSessionCookie ? 0 : db_get("cookie-expire","8766");
277 int expires = bSessionCookie ? 0 : atoi(zExpire)*3600;
278 char *zHash;
279 char *zCookie;
280 const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
281
282 assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
283 zHash = db_text(0,
 
284 "SELECT cookie FROM user"
285 " WHERE uid=%d"
286 " AND cexpire>julianday('now')"
287 " AND length(cookie)>30",
288 uid);
 
289 if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
290 zCookie = login_gen_user_cookie_value(zUsername, zHash);
291 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
292 record_login_attempt(zUsername, zIpAddr, 1);
293 db_multi_exec(
294 "UPDATE user SET cookie=%Q,"
295 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
296 zHash, expires, uid
297 );
298 free(zHash);
299 if( zDest ){
300 *zDest = zCookie;
301 }else{
302 free(zCookie);
303 }
@@ -521,20 +525,21 @@
521 char *zErrMsg = "";
522 int uid; /* User id logged in user */
523 char *zSha1Pw;
524 const char *zIpAddr; /* IP address of requestor */
525 const char *zReferer;
526 int noAnon = P("noanon")!=0;
 
 
527
528 login_check_credentials();
529 fossil_redirect_to_https_if_needed(1);
530 sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
531 constant_time_cmp_function, 0, 0);
532 zUsername = P("u");
533 zPasswd = P("p");
534 anonFlag = g.zLogin==0 && PB("anon");
535
536 /* Handle log-out requests */
537 if( P("out") ){
538 login_clear_login_data();
539 redirect_to_g();
540 return;
@@ -606,12 +611,18 @@
606 }
607 }
608 zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */
609 zReferer = P("HTTP_REFERER");
610 uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs"));
 
 
 
 
 
 
611 if( uid>0 ){
612 login_set_anon_cookie(zIpAddr, NULL, 0);
613 record_login_attempt("anonymous", zIpAddr, 1);
614 redirect_to_g();
615 }
616 if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
617 /* Attempting to log in as a user other than anonymous.
@@ -631,11 +642,11 @@
631 ** HASH/PROJECT/LOGIN
632 **
633 ** where HASH is a random hex number, PROJECT is either project
634 ** code prefix, and LOGIN is the user name.
635 */
636 login_set_user_cookie(zUsername, uid, NULL, 0);
637 redirect_to_g();
638 }
639 }
640 style_header("Login/Logout");
641 style_adunit_config(ADUNIT_OFF);
@@ -652,10 +663,11 @@
652 @ <p>Login as <b>anonymous</b> or any named user
653 @ to access page <b>%h(zAbbrev)</b>.
654 }else{
655 @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>.
656 }
 
657 }
658 if( g.sslNotAvailable==0
659 && strncmp(g.zBaseURL,"https:",6)!=0
660 && db_get_boolean("https-login",0)
661 ){
@@ -712,11 +724,15 @@
712 }
713 @ </span></td></tr>
714 }
715 @ <tr>
716 @ <td></td>
717 @ <td><input type="submit" name="in" value="Login"></td>
 
 
 
 
718 @ </tr>
719 if( !noAnon && login_self_register_available(0) ){
720 @ <tr>
721 @ <td></td>
722 @ <td><input type="submit" name="self" value="Create A New Account">
723
--- src/login.c
+++ src/login.c
@@ -262,11 +262,13 @@
262 **
263 ** If zDest is not NULL then the generated cookie is copied to
264 ** *zDdest and ownership is transfered to the caller (who should
265 ** eventually pass it to free()).
266 **
267 ** If bSessionCookie is true, the cookie will be a session cookie
268 ** and the [user].[cexpire] and [user].[cookie] entries will not be
269 ** modified.
270 */
271 void login_set_user_cookie(
272 const char *zUsername, /* User's name */
273 int uid, /* User's ID */
274 char **zDest, /* Optional: store generated cookie value. */
@@ -273,31 +275,33 @@
275 int bSessionCookie /* True for session-only cookie */
276 ){
277 const char *zCookieName = login_cookie_name();
278 const char *zExpire = bSessionCookie ? 0 : db_get("cookie-expire","8766");
279 int expires = bSessionCookie ? 0 : atoi(zExpire)*3600;
280 char *zHash = 0;
281 char *zCookie;
282 const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */
283
284 assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data.");
285 if(bSessionCookie==0){
286 zHash = db_text(0,
287 "SELECT cookie FROM user"
288 " WHERE uid=%d"
289 " AND cexpire>julianday('now')"
290 " AND length(cookie)>30",
291 uid);
292 }
293 if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
294 zCookie = login_gen_user_cookie_value(zUsername, zHash);
295 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
296 record_login_attempt(zUsername, zIpAddr, 1);
297 if(bSessionCookie==0){
298 db_multi_exec("UPDATE user SET cookie=%Q,"
299 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
300 zHash, expires, uid);
301 }
302 fossil_free(zHash);
303 if( zDest ){
304 *zDest = zCookie;
305 }else{
306 free(zCookie);
307 }
@@ -521,20 +525,21 @@
525 char *zErrMsg = "";
526 int uid; /* User id logged in user */
527 char *zSha1Pw;
528 const char *zIpAddr; /* IP address of requestor */
529 const char *zReferer;
530 const int noAnon = P("noanon")!=0;
531 int rememberMe; /* If true, use persistent cookie,
532 else session cookie */
533
534 login_check_credentials();
535 fossil_redirect_to_https_if_needed(1);
536 sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
537 constant_time_cmp_function, 0, 0);
538 zUsername = P("u");
539 zPasswd = P("p");
540 anonFlag = g.zLogin==0 && PB("anon");
 
541 /* Handle log-out requests */
542 if( P("out") ){
543 login_clear_login_data();
544 redirect_to_g();
545 return;
@@ -606,12 +611,18 @@
611 }
612 }
613 zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */
614 zReferer = P("HTTP_REFERER");
615 uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs"));
616 if(zUsername==0){
617 /* Initial login page hit. */
618 rememberMe = 1 /* seems like a sensible default */;
619 }else{
620 rememberMe = P("remember")!=0;
621 }
622 if( uid>0 ){
623 login_set_anon_cookie(zIpAddr, NULL, rememberMe?0:1);
624 record_login_attempt("anonymous", zIpAddr, 1);
625 redirect_to_g();
626 }
627 if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
628 /* Attempting to log in as a user other than anonymous.
@@ -631,11 +642,11 @@
642 ** HASH/PROJECT/LOGIN
643 **
644 ** where HASH is a random hex number, PROJECT is either project
645 ** code prefix, and LOGIN is the user name.
646 */
647 login_set_user_cookie(zUsername, uid, NULL, rememberMe?0:1);
648 redirect_to_g();
649 }
650 }
651 style_header("Login/Logout");
652 style_adunit_config(ADUNIT_OFF);
@@ -652,10 +663,11 @@
663 @ <p>Login as <b>anonymous</b> or any named user
664 @ to access page <b>%h(zAbbrev)</b>.
665 }else{
666 @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>.
667 }
668 fossil_free(zAbbrev);
669 }
670 if( g.sslNotAvailable==0
671 && strncmp(g.zBaseURL,"https:",6)!=0
672 && db_get_boolean("https-login",0)
673 ){
@@ -712,11 +724,15 @@
724 }
725 @ </span></td></tr>
726 }
727 @ <tr>
728 @ <td></td>
729 @ <td><input type="submit" name="in" value="Login">
730 @ <input type="checkbox" name="remember" value="1" \
731 @ %s(rememberMe ? "checked=\"checked\"" : "")>Remember me?
732 @ (If checked, login will use a persistent cookie, else it
733 @ will use a session cookie.)</td>
734 @ </tr>
735 if( !noAnon && login_self_register_available(0) ){
736 @ <tr>
737 @ <td></td>
738 @ <td><input type="submit" name="self" value="Create A New Account">
739

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button