3 [ -n "$include_users" ] && return 0
6 . "${_EXEC}/cgilite/session.sh"
7 . "${_EXEC}/cgilite/storage.sh"
9 SENDMAIL=${SENDMAIL-sendmail}
11 USER_REGISTRATION="${USER_REGISTRATION-true}"
12 USER_REQUIREEMAIL="${USER_REQUIREEMAIL-true}"
13 USER_ACCOUNTPAGE="${USER_ACCOUNTPAGE}"
15 MAILFROM="${MAILDOMAIN-noreply@${HTTP_HOST%:*}}"
17 HTTP_HOST="$(HEADER Host)"
19 [ "$HTTPS" ] && SCHEMA=https || SCHEMA=http
22 # UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
23 # (pending|active|deleted)
27 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
28 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
32 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
33 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
39 user_db="${user_db:-${_DATA}/users.db}"
45 USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
46 USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
49 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
50 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
51 elif [ "$user" -a -f "$user_db" -a -r "$user_db" ]; then
52 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
53 USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
54 $(grep "^${user} " "${user_db}")
57 if [ "$USER_ID" -a "${USER_EXPIRE:-0}" -gt "$_DATE" ]; then
58 USER_NAME="$(UNSTRING "$USER_NAME")"
59 USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
60 USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
61 unset USER_PWSALT USER_PWHASH
69 # internal function for user update
70 local uid="$1" uname status email pwsalt pwhash expire devices futureuse
71 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
74 for arg in "$@"; do case $arg in
75 uname=*) uname="${arg#*=}";;
76 status=*) status="${arg#*=}";;
77 email=*) email="${arg#*=}";;
78 password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
79 expire=*) expire="${arg#*=}";;
80 devices=*) devices="${arg#*=}";;
83 if LOCK "$user_db"; then
84 while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
86 if [ "$UID_" = "$uid" ]; then
87 printf '%s %s %s %s %s %s %i %s %s\n' \
88 "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
89 "${status:-${status-${STATUS}}${status+\\}}" \
90 "${email:-${email-${EMAIL}}${email+\\}}" \
91 "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
92 "${expire:-$((_DATE + 86400 * 730))}" \
93 "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
95 elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
96 # omit expired invitations from output
99 printf '%s %s %s %s %s %s %i %s %s\n' \
100 "$UID_" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
101 "$EXPIRE" "$DEVICES" "$FUTUREUSE"
103 done <"$user_db" >"${user_db}.$$"
104 mv -- "${user_db}.$$" "$user_db"
112 local user="${1:-$(timeid)}"
115 if LOCK "$user_db"; then
116 if grep -q "^${user} " "$user_db"; then
120 printf '%s \\ %s \\ \\ \\ %i \\ \\\n' \
121 "$user" "pending" "$(( $_DATE + 86400 ))" >>"$user_db"
126 if [ $# -eq 0 ]; then
129 elif update_user "$user" "$@"; then
141 if [ ! "$USER_IDMAP" ]; then
143 USER_IDMAP="${USER_IDMAP}${USER_ID} ${USER_NAME}${BR}"
146 if [ "$uid" -a "$USER_IDMAP" != "${USER_IDMAP##*${uid} }" ]; then
147 ret="${USER_IDMAP##*${uid} }"; ret="${ret%%${BR}*}";
150 elif [ "$uid" ]; then
153 printf '%s' "$USER_IDMAP"
159 local name="$(STRING "$1")" ret
160 [ "$USER_IDMAP" ] || user_idmap >/dev/null
162 if [ "${name%\\}" -a "$USER_IDMAP" != "${USER_IDMAP% ${name}${BR}*}" ]; then
163 ret="${USER_IDMAP% ${name}${BR}*}"; ret="${ret##*${BR}}"
172 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
178 /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
184 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
186 # W3C recommended email regex
187 # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
188 /^[a-zA-Z0-9.!#$%&'\''*+\/=?^_`{|}~-]+@[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/p;
193 local uname="$(STRING "$1")"
194 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
195 [ -f "$user_db" -a -r "$user_db" ] \
196 && while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
197 [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
203 local email="$(STRING "$1")"
204 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
205 [ -f "$user_db" -a -r "$user_db" ] \
206 && while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
207 [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
213 local salt="$1" secret="$2" hash
214 hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
215 printf '%s\n' "${hash%% *}"
219 # reserve account, send registration mail
220 # preliminary uid, expiration, signature
221 local uid="$(timeid)"
222 local uname="$(POST uname |user_checkname)"
223 local email="$(POST email |user_checkemail)"
224 local pwsalt="$(randomid)"
225 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
227 if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
228 REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
231 if [ "$USER_REQUIREEMAIL" = true ]; then
232 if [ ! "email" ]; then
233 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
234 elif user_emailexist "$email"; then
235 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
236 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"; then
237 debug "Sending Activation Link:" \
238 "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
239 "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
242 Subject: Your account registration at ${HTTP_HOST%:*}
244 Someone tried to sign up for a user account using this email address.
246 You can activate your account using this link:
248 ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
250 This registration link will expire after 24 hours.
252 If you did not request an account at ${HTTP_HOST%:*}, then someone else
253 probably entered your email address by accident. In this case you shoud
254 simply ignore this message and we will remove your email address from
255 our database within the next day.
257 This is an automatic email. Any direct reply will not be received.
258 Your Account Registration Robot.
260 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
262 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
265 elif [ "$USER_REQUIREEMAIL" != true ]; then
266 if [ ! "$uname" ]; then
267 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
268 elif user_nameexist "$uname"; then
269 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
270 elif [ ! "$pw" ]; then
271 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
272 elif [ "$pw" != "$pwconfirm" ]; then
273 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
274 elif new_user "$uid" uname="$uname" status=active email="$email" password="$pw" expire="$((_DATE + 86400 * 730))"; then
276 SESSION_BIND user_id "$uid"
278 if [ "$USER_ACCOUNTPAGE" ]; then
279 REDIRECT "${USER_ACCOUNTPAGE}"
281 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
284 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
290 local uid="$(timeid)"
291 local email="$(POST email |user_checkemail)"
292 local message="$(POST message)"
294 if [ ! "email" ]; then
295 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
296 elif user_emailexist "$email"; then
297 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
298 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"; then
299 debug "Sending Invitation Link:" \
300 "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
301 "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
304 Subject: You have been invited to ${HTTP_HOST%:*}
306 ${USER_NAME:-Someone} has offered an invitation to this email address.
310 You can create your account using this link:
312 ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
314 This registration link will expire after 24 hours.
316 If you do not know what this is about, then someone else probably
317 entered your email address by accident. In this case you shoud
318 simply ignore this message and we will remove your email address from
319 our database within the next day.
321 This is an automatic email. Any direct reply will not be received.
322 Your Account Registration Robot.
324 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
326 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
333 local uid="$(POST uid |checkid || printf invalid)"
334 local signature="$(POST signature)"
335 local uname="$(POST uname |user_checkname)"
336 local pwsalt="$(randomid)"
337 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
341 if [ "$signature" != "$(session_mac "$uid")" ]; then
342 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
343 elif [ ! "$uname" ]; then
344 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
345 elif user_nameexist "$uname"; then
346 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
347 elif [ ! "$pw" ]; then
348 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
349 elif [ "$pw" != "$pwconfirm" ]; then
350 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
351 elif [ "$USER_STATUS" != pending -o \! "$USER_EXPIRE" -gt "$_DATE" ]; then
352 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
353 elif update_user "$USER_ID" uname="$uname" status=active password="$pw"; then
355 SESSION_BIND user_id "$USER_ID"
356 if [ "$USER_ACCOUNTPAGE" ]; then
357 REDIRECT "${USER_ACCOUNTPAGE}"
359 REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
362 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
368 # keep logged in - device cookie?
369 # initialize new session!
370 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
371 local uname="$(POST uname |STRING)" pw="$(POST pw)"
373 [ -f "$user_db" -a -r "$user_db" ] \
374 && while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
375 if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
376 if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
378 SESSION_BIND user_id "$UID_"
379 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
383 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
387 # destroy cookie, destroy session
391 SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
392 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
396 # todo: username update, email update / email confirm
397 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
398 # local uname="$(POST uname |STRING)"
399 local uid oldpw pw pwconfirm
402 oldpw="$(POST oldpw)"
403 pw="$(POST pw |grep -xE '.{6}')"
404 pwconfirm="$(POST pwconfirm)"
407 read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
408 $(grep "^${uid} " "$user_db")
411 if [ "$UID_" = "$USER_ID" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$oldpw")" ]; then
412 if [ "$pw" -a "$pw" = "$pwconfirm" ]; then
413 update_user "${uid}" password="$pw"
414 REDIRECT "${_BASE}${PATH_INFO}#UPDATE_SUCCESS"
416 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PWMISMATCH"
418 elif [ "$UID_" = "$USER_ID" ]; then
419 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_AUTH_PASSWORD"
421 REDIRECT "${_BASE}${PATH_INFO}#ERROR_NOTLOGGEDIN"
433 read_user "$(SESSION_VAR user_id)"
434 [ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
436 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
437 user_register) user_register ;;
438 user_confirm) user_confirm ;;
439 user_invite) user_invite ;;
440 user_login) user_login ;;
441 user_logout) user_logout ;;
442 user_update) user_update ;;
449 export USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
450 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
454 if [ ! "$USER_ID" ]; then
456 [div #user_update .nouser
457 This page can only be used by registered users
462 [form #user_update method=POST
463 [hidden "uid" "$USER_ID"]
464 [p .username Logged in as $USER_NAME]
465 [input type=password name=oldpw placeholder="Current Passphrase"]
466 [input type=password name=pw placeholder="New Passphrase" pattern=".{6,}"]
467 [input type=password name=pwconfirm placeholder="Confirm New Passphrase" pattern=".{6,}"]
468 [submit "action" "user_update" Update Passphrase]
475 if [ "$(GET user_confirm)" ]; then
477 elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
479 [div #user_register .disabled
480 User Registration is disabled.
483 elif [ "$USER_REQUIREEMAIL" = true ]; then
485 [form #user_register .registeremail method=POST
486 [p We will send an activation mail to your email address.
487 You can continue the signup process when you click on the
488 activation link in this email.]
489 [input type=email name=email placeholder="Email"]
490 [submit "action" "user_register" Sign Up]
493 elif [ "$USER_REQUIREEMAIL" != true ]; then
495 [form #user_register .registername method=POST
496 [input name=uname placeholder="Choose Username" tooltip="Your username may contain any character but the @ sign. It must be at least 3 characters long, and it must start with a letter." pattern="^\[\\\\p{L}\]\[\\\\p{L}0-9 -~\]{2,127}$" autocomplete=off]
497 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
498 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
499 [submit "action" "user_register" Sign Up]
506 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
507 local user_confirm="$(GET user_confirm)"
508 local uid="${user_confirm% *}" signature="${user_confirm#* }"
510 if [ "$signature" = "$(session_mac "$uid")" ]; then
511 read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
512 $(grep "^${uid} " "$user_db")
514 if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
516 [form #user_confirm method=POST
517 [input type=hidden name=uid value="${uid}"]
518 [input type=hidden name=signature value="${signature}"]
519 $([ "$EMAIL" != '\' ] && printf \
520 '[input disabled=disabled value="%s" placeholder="Email"]' "$(UNSTRING "$EMAIL" |HTML)"
522 [input name=uname placeholder="Choose Username" tooltip="Your username may contain any character but the @ sign. It must be at least 3 characters long, and it must start with a letter." pattern="^\[\\\\p{L}\]\[\\\\p{L}0-9 -~\]{2,127}$" autocomplete=off]
523 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
524 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
525 [submit "action" "user_confirm" Finish Registration]
530 [div #user_confirm .expired
531 [p This activation link is not valid anymore.]
537 [div #user_confirm .invalid
538 [p This activation link is invalid. Make sure you copied the whole activation link from your email and be careful not to include any line breaks.]
545 if [ "$(GET user_confirm)" ]; then
547 elif [ "$USER_ID" -a "$SENDMAIL" ]; then
549 [form #user_invite method=POST
550 [input placeholder="Email Recipient" name=email autocomplete=off]
551 [textarea name="message" placeholder="Message to recipient" . ]
552 [submit "action" "user_invite" Send Invitation]
555 elif [ "$USER_ID" ]; then
557 new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"
559 [p An anonymous user account has been set up. Send the following link to the intended user, so they may claim their account. The link will remain valid for 24 hours.]
560 [p . $(HTML "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")" |debug)]
562 [p [a href="#" . Set up another account]]
566 [div #user_invite .notallowed
567 Only registered users may send an invitation to another user.
574 if [ ! "$USER_ID" ]; then
576 [form #user_login .login method=POST
577 [input name=uname placeholder="Username or Email" autocomplete=off]
578 [input type=password name=pw placeholder="Passphrase"]
579 [submit "action" "user_login" Login]
582 elif [ "$USER_ID" ]; then
584 [form #user_login .logout method=POST
585 [p Logged in as [span . $(HTML ${USER_NAME})]]
586 [submit "action" "user_logout" Logout]