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)"
20 # UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
21 # (pending|active|deleted)
25 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
26 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
30 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
31 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
37 user_db="${user_db:-${_DATA}/users.db}"
43 USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
44 USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
47 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
48 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
49 elif [ "$user" -a -f "$user_db" -a -r "$user_db" ]; then
50 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
51 USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
52 $(grep "^${user} " "${user_db}")
55 if [ "$USER_ID" -a "${USER_EXPIRE:-0}" -gt "$_DATE" ]; then
56 USER_NAME="$(UNSTRING "$USER_NAME")"
57 USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
58 USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
59 unset USER_PWSALT USER_PWHASH
67 # internal function for user update
68 local uid="$1" uname status email pwsalt pwhash expire devices futureuse
69 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
72 for arg in "$@"; do case $arg in
73 uname=*) uname="${arg#*=}";;
74 status=*) status="${arg#*=}";;
75 email=*) email="${arg#*=}";;
76 password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
77 expire=*) expire="${arg#*=}";;
78 devices=*) devices="${arg#*=}";;
81 if LOCK "$user_db"; then
82 while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
84 if [ "$UID" = "$uid" ]; then
85 printf '%s %s %s %s %s %s %i %s %s\n' \
86 "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
87 "${status:-${status-${STATUS}}${status+\\}}" \
88 "${email:-${email-${EMAIL}}${email+\\}}" \
89 "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
90 "${expire:-$((_DATE + 86400 * 730))}" \
91 "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
93 elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
94 # omit expired invitations from output
97 printf '%s %s %s %s %s %s %i %s %s\n' \
98 "$UID" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
99 "$EXPIRE" "$DEVICES" "$FUTUREUSE"
101 done <"$user_db" >"${user_db}.$$"
102 mv -- "${user_db}.$$" "$user_db"
110 local user="${1:-$(timeid)}"
113 if LOCK "$user_db"; then
114 if grep -q "^${user} " "$user_db"; then
118 printf '%s \\ %s \\ \\ \\ %i \\ \\\n' \
119 "$user" "pending" "$(( $_DATE + 86400 ))" >>"$user_db"
124 if [ $# -eq 0 ]; then
127 elif update_user "$user" "$@"; then
139 if [ ! "$USER_IDMAP" ]; then
141 USER_IDMAP="${USER_IDMAP}${USER_ID} ${USER_NAME}${BR}"
144 if [ "$uid" -a "$USER_IDMAP" != "${USER_IDMAP##*${uid} }" ]; then
145 ret="${USER_IDMAP##*${uid} }"; ret="${ret%%${BR}*}";
148 elif [ "$uid" ]; then
151 printf '%s' "$USER_IDMAP"
157 local name="$(STRING "$1")" ret
158 [ "$USER_IDMAP" ] || user_idmap >/dev/null
160 if [ "${name%\\}" -a "$USER_IDMAP" != "${USER_IDMAP% ${name}${BR}*}" ]; then
161 ret="${USER_IDMAP% ${name}${BR}*}"; ret="${ret##*${BR}}"
170 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
176 /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
182 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
184 # W3C recommended email regex
185 # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
186 /^[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;
191 local uname="$(STRING "$1")"
192 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
193 [ -f "$user_db" -a -r "$user_db" ] \
194 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
195 [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
201 local email="$(STRING "$1")"
202 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
203 [ -f "$user_db" -a -r "$user_db" ] \
204 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
205 [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
211 local salt="$1" secret="$2" hash
212 hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
213 printf '%s\n' "${hash%% *}"
217 # reserve account, send registration mail
218 # preliminary uid, expiration, signature
219 local uid="$(timeid)"
220 local uname="$(POST uname |user_checkname)"
221 local email="$(POST email |user_checkemail)"
222 local pwsalt="$(randomid)"
223 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
225 if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
226 REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
229 if [ "$USER_REQUIREEMAIL" = true ]; then
230 if [ ! "email" ]; then
231 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
232 elif user_emailexist "$email"; then
233 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
234 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"; then
235 debug "Sending Activation Link:" \
236 "https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
237 "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
240 Subject: Your account registration at ${HTTP_HOST%:*}
242 Someone tried to sign up for a user account using this email address.
244 You can activate your account using this link:
246 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
248 This registration link will expire after 24 hours.
250 If you did not request an account at ${HTTP_HOST%:*}, then someone else
251 probably entered your email address by accident. In this case you shoud
252 simply ignore this message and we will remove your email address from
253 our database within the next day.
255 This is an automatic email. Any direct reply will not be received.
256 Your Account Registration Robot.
258 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
260 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
263 elif [ "$USER_REQUIREEMAIL" != true ]; then
264 if [ ! "$uname" ]; then
265 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
266 elif user_nameexist "$uname"; then
267 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
268 elif [ ! "$pw" ]; then
269 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
270 elif [ "$pw" != "$pwconfirm" ]; then
271 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
272 elif new_user "$uid" uname="$uname" status=active email="$email" password="$pw" expire="$((_DATE + 86400 * 730))"; then
274 SESSION_BIND user_id "$uid"
276 if [ "$USER_ACCOUNTPAGE" ]; then
277 REDIRECT "${USER_ACCOUNTPAGE}"
279 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
282 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
288 local uid="$(timeid)"
289 local email="$(POST email |user_checkemail)"
290 local message="$(POST message)"
292 if [ ! "email" ]; then
293 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
294 elif user_emailexist "$email"; then
295 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
296 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"; then
297 debug "Sending Invitation Link:" \
298 "https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
299 "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
302 Subject: You have been invited to ${HTTP_HOST%:*}
304 ${USER_NAME:-Someone} has offered an invitation to this email address.
308 You can create your account using this link:
310 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
312 This registration link will expire after 24 hours.
314 If you do not know what this is about, then someone else probably
315 entered your email address by accident. In this case you shoud
316 simply ignore this message and we will remove your email address from
317 our database within the next day.
319 This is an automatic email. Any direct reply will not be received.
320 Your Account Registration Robot.
322 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
324 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
331 local uid="$(POST uid |checkid || printf invalid)"
332 local signature="$(POST signature)"
333 local uname="$(POST uname |user_checkname)"
334 local pwsalt="$(randomid)"
335 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
339 if [ "$signature" != "$(session_mac "$uid")" ]; then
340 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
341 elif [ ! "$uname" ]; then
342 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
343 elif user_nameexist "$uname"; then
344 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
345 elif [ ! "$pw" ]; then
346 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
347 elif [ "$pw" != "$pwconfirm" ]; then
348 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
349 elif [ "$USER_STATUS" != pending -o \! "$USER_EXPIRE" -gt "$_DATE" ]; then
350 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
351 elif update_user "$USER_ID" uname="$uname" status=active password="$pw"; then
353 SESSION_BIND user_id "$USER_ID"
354 if [ "$USER_ACCOUNTPAGE" ]; then
355 REDIRECT "${USER_ACCOUNTPAGE}"
357 REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
360 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
366 # keep logged in - device cookie?
367 # initialize new session!
368 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
369 local uname="$(POST uname |STRING)" pw="$(POST pw)"
371 [ -f "$user_db" -a -r "$user_db" ] \
372 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
373 if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
374 if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
376 SESSION_BIND user_id "$UID"
377 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
381 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
385 # destroy cookie, destroy session
389 SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
390 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
394 # todo: username update, email update / email confirm
395 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
396 # local uname="$(POST uname |STRING)"
397 local uid oldpw pw pwconfirm
400 oldpw="$(POST oldpw)"
401 pw="$(POST pw |grep -xE '.{6}')"
402 pwconfirm="$(POST pwconfirm)"
405 read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
406 $(grep "^${uid} " "$user_db")
409 if [ "$UID" = "$USER_ID" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$oldpw")" ]; then
410 if [ "$pw" -a "$pw" = "$pwconfirm" ]; then
411 update_user "${uid}" password="$pw"
412 REDIRECT "${_BASE}${PATH_INFO}#UPDATE_SUCCESS"
414 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PWMISMATCH"
416 elif [ "$UID" = "$USER_ID" ]; then
417 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_AUTH_PASSWORD"
419 REDIRECT "${_BASE}${PATH_INFO}#ERROR_NOTLOGGEDIN"
431 read_user "$(SESSION_VAR user_id)"
432 [ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
434 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
435 user_register) user_register ;;
436 user_confirm) user_confirm ;;
437 user_invite) user_invite ;;
438 user_login) user_login ;;
439 user_logout) user_logout ;;
440 user_update) user_update ;;
448 if [ ! "$USER_ID" ]; then
450 [div #user_update .nouser
451 This page can only be used by registered users
456 [form #user_update method=POST
457 [hidden "uid" "$USER_ID"]
458 [p .username Logged in as $USER_NAME]
459 [input type=password name=oldpw placeholder="Current Passphrase"]
460 [input type=password name=pw placeholder="New Passphrase" pattern=".{6,}"]
461 [input type=password name=pwconfirm placeholder="Confirm New Passphrase" pattern=".{6,}"]
462 [submit "action" "user_update" Update Passphrase]
469 if [ "$(GET user_confirm)" ]; then
471 elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
473 [div #user_register .disabled
474 User Registration is disabled.
477 elif [ "$USER_REQUIREEMAIL" = true ]; then
479 [form #user_register .registeremail method=POST
480 [p We will send an activation mail to your email address.
481 You can continue the signup process when you click on the
482 activation link in this email.]
483 [input type=email name=email placeholder="Email"]
484 [submit "action" "user_register" Sign Up]
487 elif [ "$USER_REQUIREEMAIL" != true ]; then
489 [form #user_register .registername method=POST
490 [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]
491 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
492 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
493 [submit "action" "user_register" Sign Up]
500 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
501 local user_confirm="$(GET user_confirm)"
502 local uid="${user_confirm% *}" signature="${user_confirm#* }"
504 if [ "$signature" = "$(session_mac "$uid")" ]; then
505 read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
506 $(grep "^${uid} " "$user_db")
508 if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
510 [form #user_confirm method=POST
511 [input type=hidden name=uid value="${uid}"]
512 [input type=hidden name=signature value="${signature}"]
513 $([ "$EMAIL" != '\' ] && printf \
514 '[input disabled=disabled value="%s" placeholder="Email"]' "$(UNSTRING "$EMAIL" |HTML)"
516 [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]
517 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
518 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
519 [submit "action" "user_confirm" Finish Registration]
524 [div #user_confirm .expired
525 [p This activation link is not valid anymore.]
531 [div #user_confirm .invalid
532 [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.]
539 if [ "$(GET user_confirm)" ]; then
541 elif [ "$USER_ID" -a "$SENDMAIL" ]; then
543 [form #user_invite method=POST
544 [input placeholder="Email Recipient" name=email autocomplete=off]
545 [textarea name="message" placeholder="Message to recipient" . ]
546 [submit "action" "user_invite" Send Invitation]
549 elif [ "$USER_ID" ]; then
551 new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"
553 [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.]
554 [p . $(HTML "https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")" |debug)]
556 [p [a href="#" . Set up another account]]
560 [div #user_invite .notallowed
561 Only registered users may send an invitation to another user.
568 if [ ! "$USER_ID" ]; then
570 [form #user_login .login method=POST
571 [input name=uname placeholder="Username or Email" autocomplete=off]
572 [input type=password name=pw placeholder="Passphrase"]
573 [submit "action" "user_login" Login]
576 elif [ "$USER_ID" ]; then
578 [form #user_login .logout method=POST
579 [p Logged in as [span . $(HTML ${USER_NAME})]]
580 [submit "action" "user_logout" Logout]