Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Initial complete implementation of self-password-reset. Just need refinement and a security audit before merging to trunk. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | self-service-password-reset |
| Files: | files | file ages | folders |
| SHA3-256: |
41bb73e9ba49c121bd95d2054fe1c1b9 |
| User & Date: | drh 2023-01-07 14:25:04.474 |
Context
|
2023-01-07
| ||
| 15:18 | Improved comments. Extra defensive code. check-in: d860e2b5f6 user: drh tags: self-service-password-reset | |
| 14:25 | Initial complete implementation of self-password-reset. Just need refinement and a security audit before merging to trunk. check-in: 41bb73e9ba user: drh tags: self-service-password-reset | |
| 11:58 | Add a non-functioning place-holder button to request a password reset to the /register page. check-in: 5c62a2c3a7 user: drh tags: self-service-password-reset | |
Changes
Changes to src/db.c.
| ︙ | ︙ | |||
4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 |
** for the repository list page. If none of the repositories on the list
** have a non-zero "repolist-skin" setting then the repository list is
** displayed using unadorned HTML ("skinless").
**
** If repolist-skin has a value of 2, then the repository is omitted from
** the list in use cases 1 through 4, but not for 5 and 6.
*/
/*
** SETTING: self-register boolean default=off sensitive
** Allow users to register themselves through the HTTP UI.
** This is useful if you want to see other names than
** "Anonymous" in e.g. ticketing system. On the other hand
** users can not be deleted.
*/
| > > > > > > > | 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 |
** for the repository list page. If none of the repositories on the list
** have a non-zero "repolist-skin" setting then the repository list is
** displayed using unadorned HTML ("skinless").
**
** If repolist-skin has a value of 2, then the repository is omitted from
** the list in use cases 1 through 4, but not for 5 and 6.
*/
/*
** SETTING: self-pw-reset boolean default=off sensitive
** Allow users to request that an email containing a hyperlink
** to the /resetpw page be sent to their email address of record,
** thus allowing forgetful users to reset their forgotten passwords
** without administrator involvement.
*/
/*
** SETTING: self-register boolean default=off sensitive
** Allow users to register themselves through the HTTP UI.
** This is useful if you want to see other names than
** "Anonymous" in e.g. ticketing system. On the other hand
** users can not be deleted.
*/
|
| ︙ | ︙ |
Changes to src/login.c.
| ︙ | ︙ | |||
508 509 510 511 512 513 514 515 516 517 518 519 520 521 |
int rc;
if( zReferer==0 ) return 0;
zPattern = mprintf("%s/login*", g.zBaseURL);
rc = sqlite3_strglob(zPattern, zReferer)==0;
fossil_free(zPattern);
return rc;
}
/*
** Return TRUE if self-registration is available. If the zNeeded
** argument is not NULL, then only return true if self-registration is
** available and any of the capabilities named in zNeeded are available
** to self-registered users.
*/
| > > > > > > > > > | 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
int rc;
if( zReferer==0 ) return 0;
zPattern = mprintf("%s/login*", g.zBaseURL);
rc = sqlite3_strglob(zPattern, zReferer)==0;
fossil_free(zPattern);
return rc;
}
/*
** Return true if users are allowed to reset their own passwords.
*/
int login_self_password_reset_available(void){
if( !db_get_boolean("self-pw-reset",0) ) return 0;
if( !alert_tables_exist() ) return 0;
return 1;
}
/*
** Return TRUE if self-registration is available. If the zNeeded
** argument is not NULL, then only return true if self-registration is
** available and any of the capabilities named in zNeeded are available
** to self-registered users.
*/
|
| ︙ | ︙ | |||
558 559 560 561 562 563 564 565 566 567 568 569 570 571 |
char *zSha1Pw;
const char *zIpAddr; /* IP address of requestor */
const int noAnon = P("noanon")!=0;
int rememberMe; /* If true, use persistent cookie, else
session cookie. Toggled per
checkbox. */
login_check_credentials();
fossil_redirect_to_https_if_needed(1);
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
zUsername = P("u");
zPasswd = P("p");
anonFlag = g.zLogin==0 && PB("anon");
| > > > > | 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 |
char *zSha1Pw;
const char *zIpAddr; /* IP address of requestor */
const int noAnon = P("noanon")!=0;
int rememberMe; /* If true, use persistent cookie, else
session cookie. Toggled per
checkbox. */
if( P("pwreset")!=0 ){
login_reqpwreset_page();
return;
}
login_check_credentials();
fossil_redirect_to_https_if_needed(1);
sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
constant_time_cmp_function, 0, 0);
zUsername = P("u");
zPasswd = P("p");
anonFlag = g.zLogin==0 && PB("anon");
|
| ︙ | ︙ | |||
778 779 780 781 782 783 784 785 786 787 788 789 790 791 |
@ <td><input type="submit" name="in" value="Login">
@ </tr>
if( !noAnon && login_self_register_available(0) ){
@ <tr>
@ <td></td>
@ <td><input type="submit" name="self" value="Create A New Account">
@ </tr>
}
@ </table>
if( zAnonPw && !noAnon ){
const char *zDecoded = captcha_decode(uSeed);
int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
char *zCaptcha = captcha_render(zDecoded);
| > > > > > > | 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 |
@ <td><input type="submit" name="in" value="Login">
@ </tr>
if( !noAnon && login_self_register_available(0) ){
@ <tr>
@ <td></td>
@ <td><input type="submit" name="self" value="Create A New Account">
@ </tr>
}
if( login_self_password_reset_available() ){
@ <tr>
@ <td></td>
@ <td><input type="submit" name="pwreset" value="Reset My Password">
@ </tr>
}
@ </table>
if( zAnonPw && !noAnon ){
const char *zDecoded = captcha_decode(uSeed);
int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
char *zCaptcha = captcha_render(zDecoded);
|
| ︙ | ︙ | |||
1371 1372 1373 1374 1375 1376 1377 | } /* ** Set the current logged in user to be uid. zCap is precomputed ** (override) capabilities. If zCap==0, then look up the capabilities ** in the USER table. */ | | | 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 |
}
/*
** Set the current logged in user to be uid. zCap is precomputed
** (override) capabilities. If zCap==0, then look up the capabilities
** in the USER table.
*/
int login_set_uid(int uid, const char *zCap){
const char *zPublicPages = 0; /* GLOB patterns of public pages */
/* At this point, we know that uid!=0. Find the privileges associated
** with user uid.
*/
assert( uid!=0 );
if( zCap==0 ){
|
| ︙ | ︙ | |||
1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 |
const char *zUri = PD("REQUEST_URI","");
zUri += (int)strlen(g.zTop);
if( glob_match(pGlob, zUri) ){
login_set_capabilities(db_get("default-perms", "u"), 0);
}
glob_free(pGlob);
}
}
/*
** Memory of settings
*/
static int login_anon_once = 1;
| > | 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 |
const char *zUri = PD("REQUEST_URI","");
zUri += (int)strlen(g.zTop);
if( glob_match(pGlob, zUri) ){
login_set_capabilities(db_get("default-perms", "u"), 0);
}
glob_free(pGlob);
}
return g.zLogin!=0;
}
/*
** Memory of settings
*/
static int login_anon_once = 1;
|
| ︙ | ︙ | |||
1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 |
const char *zErr = 0;
int uid = 0; /* User id with the same email */
int captchaIsCorrect = 0; /* True on a correct captcha */
char *zCaptcha = ""; /* Value of the captcha text */
char *zPerms; /* Permissions for the default user */
int canDoAlerts = 0; /* True if receiving email alerts is possible */
int doAlerts = 0; /* True if subscription is wanted too */
if( !db_get_boolean("self-register", 0) ){
style_header("Registration not possible");
@ <p>This project does not allow user self-registration. Please contact the
@ project administrator to obtain an account.</p>
style_finish_page();
return;
}
zPerms = db_get("default-perms", "u");
/* Prompt the user for email alerts if this repository is configured for
** email alerts and if the default permissions include "7" */
canDoAlerts = alert_tables_exist() && (db_int(0,
"SELECT fullcap(%Q) GLOB '*7*'", zPerms
| > > > > > | 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 |
const char *zErr = 0;
int uid = 0; /* User id with the same email */
int captchaIsCorrect = 0; /* True on a correct captcha */
char *zCaptcha = ""; /* Value of the captcha text */
char *zPerms; /* Permissions for the default user */
int canDoAlerts = 0; /* True if receiving email alerts is possible */
int doAlerts = 0; /* True if subscription is wanted too */
if( !db_get_boolean("self-register", 0) ){
style_header("Registration not possible");
@ <p>This project does not allow user self-registration. Please contact the
@ project administrator to obtain an account.</p>
style_finish_page();
return;
}
if( P("pwreset")!=0 ){
login_reqpwreset_page();
return;
}
zPerms = db_get("default-perms", "u");
/* Prompt the user for email alerts if this repository is configured for
** email alerts and if the default permissions include "7" */
canDoAlerts = alert_tables_exist() && (db_int(0,
"SELECT fullcap(%Q) GLOB '*7*'", zPerms
|
| ︙ | ︙ | |||
2102 2103 2104 2105 2106 2107 2108 |
@ <td class="form_label" align="right" id="emaddr">Email Address:</td>
@ <td><input aria-labelledby="emaddr" type="text" name="ea" \
@ value="%h(zEAddr)" size="30"></td>
@ </tr>
if( iErrLine==3 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span>
if( uid>0 ){
| > > | | 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 |
@ <td class="form_label" align="right" id="emaddr">Email Address:</td>
@ <td><input aria-labelledby="emaddr" type="text" name="ea" \
@ value="%h(zEAddr)" size="30"></td>
@ </tr>
if( iErrLine==3 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span>
if( uid>0 ){
@ <br />
@ <input type="submit" name="pwreset" \
@ value="Request Password Reset For %h(zEAddr)">
}
@ </td></tr>
}
if( canDoAlerts ){
int a = atoi(PD("alerts","1"));
@ <tr>
@ <td class="form_label" align="right" id="emalrt">Email Alerts?</td>
|
| ︙ | ︙ | |||
2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 | @ Enter this 8-letter code in the "Captcha" box above. @ </td></tr></table></div> @ </form> style_finish_page(); free(zCaptcha); } /* ** Run SQL on the repository database for every repository in our ** login group. The SQL is run in a separate database connection. ** ** Any members of the login group whose repository database file ** cannot be found is silently removed from the group. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 |
@ Enter this 8-letter code in the "Captcha" box above.
@ </td></tr></table></div>
@ </form>
style_finish_page();
free(zCaptcha);
}
/*
** WEBPAGE: reqpwreset
**
** A web page to request a password reset.
*/
void login_reqpwreset_page(void){
const char *zEAddr;
const char *zDecoded;
unsigned int uSeed;
int iErrLine = -1;
const char *zErr = 0;
int uid = 0; /* User id with the email zEAddr */
int captchaIsCorrect = 0; /* True on a correct captcha */
char *zCaptcha = ""; /* Value of the captcha text */
if( !db_get_boolean("self-pw-reset", 0) || !alert_tables_exist() ){
style_header("Password reset not possible");
@ <p>This project does not allow users to reset their own passwords.
@ If you need a password reset, you will have to negotiate that directly
@ with the project administrator.
style_finish_page();
return;
}
zEAddr = PDT("ea","");
/* Verify user imputs */
if( !cgi_csrf_safe(1) || P("reqpwreset")==0 ){
/* This is the initial display of the form. No processing or error
** checking is to be done. Fall through into the form display
*/
}else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){
iErrLine = 2;
zErr = "Incorrect CAPTCHA";
}else if( zEAddr[0]==0 ){
iErrLine = 1;
zErr = "Required";
}else if( email_address_is_valid(zEAddr,0)==0 ){
iErrLine = 1;
zErr = "Not a valid email address";
}else if( authorized_subscription_email(zEAddr)==0 ){
iErrLine = 1;
zErr = "Not an authorized email address";
}else if( (uid = email_address_in_use(zEAddr))<=0 ){
iErrLine = 1;
zErr = "This email address is not associated with a user who has "
"password reset privileges.";
}else if( login_set_uid(uid,0)==0 || g.perm.Admin || g.perm.Setup
|| !g.perm.Password ){
iErrLine = 1;
zErr = "This email address is not associated with a user who has "
"password reset privileges.";
}else{
/* If all of the tests above have passed, that means that the submitted
** form contains valid data and we can proceed to issue the password
** reset email. */
Blob hdr, body;
AlertSender *pSender;
char *zUrl = login_resetpw_suffix(uid, 0);
pSender = alert_sender_new(0,0);
blob_init(&hdr,0,0);
blob_init(&body,0,0);
blob_appendf(&hdr, "To: <%s>\n", zEAddr);
blob_appendf(&hdr, "Subject: Password reset for %s\n", g.zBaseURL);
blob_appendf(&body,
"Someone has requested to reset the password for user \"%s\"\n",
g.zLogin);
blob_appendf(&body, "at %s.\n\n", g.zBaseURL);
blob_appendf(&body,
"If you did not request this password reset, ignore\n"
"this email\n\n");
blob_appendf(&body,
"To reset the password, visit the following link:\n\n"
" %s/resetpw/%s\n\n", g.zBaseURL, zUrl);
fossil_free(zUrl);
alert_send(pSender, &hdr, &body, 0);
style_header("Email Verification");
if( pSender->zErr ){
@ <h1>Internal Error</h1>
@ <p>The following internal error was encountered while trying
@ to send the confirmation email:
@ <blockquote><pre>
@ %h(pSender->zErr)
@ </pre></blockquote>
}else{
@ <p>An email containing a hyperlink that can be used to reset
@ your password has been sent to "%h(zEAddr)".</p>
}
alert_sender_free(pSender);
style_finish_page();
return;
}
/* Prepare the captcha. */
if( captchaIsCorrect ){
uSeed = strtoul(P("captchaseed"),0,10);
}else{
uSeed = captcha_seed();
}
zDecoded = captcha_decode(uSeed);
zCaptcha = captcha_render(zDecoded);
style_header("Request Password Reset");
/* Print out the registration form. */
g.perm.Hyperlink = 1; /* Artificially enable hyperlinks */
form_begin(0, "%R/reqpwreset");
@ <p><input type="hidden" name="captchaseed" value="%u(uSeed)" />
@ <p><input type="hidden" name="reqpwreset" value="1" />
@ <table class="login_out">
@ <tr>
@ <td class="form_label" align="right" id="emaddr">Email Address:</td>
@ <td><input aria-labelledby="emaddr" type="text" name="ea" \
@ value="%h(zEAddr)" size="30"></td>
@ </tr>
if( iErrLine==1 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ <tr>
@ <td class="form_label" align="right" id="cptcha">Captcha:</td>
@ <td><input type="text" name="captcha" aria-labelledby="cptcha" \
@ value="%h(captchaIsCorrect?zDecoded:"")" size="30">
captcha_speakit_button(uSeed, "Speak the captcha text");
@ </td>
@ </tr>
if( iErrLine==2 ){
@ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr>
}
@ <tr><td></td>
@ <td><input type="submit" name="new" value="Request Password Reset"/>\
@ </td></tr>
@ </table>
@ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha">
@ %h(zCaptcha)
@ </pre>
@ Enter this 8-letter code in the "Captcha" box above.
@ </td></tr></table></div>
@ </form>
style_finish_page();
free(zCaptcha);
}
/*
** Run SQL on the repository database for every repository in our
** login group. The SQL is run in a separate database connection.
**
** Any members of the login group whose repository database file
** cannot be found is silently removed from the group.
|
| ︙ | ︙ |
Changes to src/setup.c.
| ︙ | ︙ | |||
592 593 594 595 596 597 598 599 600 601 602 603 604 605 |
"self-register", "selfreg", 0, 0);
@ <p>Allow users to register themselves on the /register webpage.
@ A self-registration creates a new entry in the USER table and
@ perhaps also in the SUBSCRIBER table if email notification is
@ enabled.
@ (Property: "self-register")</p>
@ <hr />
onoff_attribute("Email verification required for self-registration",
"selfreg-verify", "sfverify", 0, 0);
@ <p>If enabled, self-registration creates a new entry in the USER table
@ with only capabilities "7". The default user capabilities are not
@ added until the email address associated with the self-registration
@ has been verified. This setting only makes sense if
| > > > > > > > > > | 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 |
"self-register", "selfreg", 0, 0);
@ <p>Allow users to register themselves on the /register webpage.
@ A self-registration creates a new entry in the USER table and
@ perhaps also in the SUBSCRIBER table if email notification is
@ enabled.
@ (Property: "self-register")</p>
@ <hr />
onoff_attribute("Allow users to reset their own passwords",
"self-pw-reset", "selfpw", 0, 0);
@ <p>Allow users to request that an email contains a hyperlink to a
@ password reset page be sent to their email address of record. This
@ enables forgetful users to recover their forgotten passwords without
@ administrator intervention.
@ (Property: "self-pw-reset")</p>
@ <hr />
onoff_attribute("Email verification required for self-registration",
"selfreg-verify", "sfverify", 0, 0);
@ <p>If enabled, self-registration creates a new entry in the USER table
@ with only capabilities "7". The default user capabilities are not
@ added until the email address associated with the self-registration
@ has been verified. This setting only makes sense if
|
| ︙ | ︙ |