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 ;;
447 export USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
448 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
452 if [ ! "$USER_ID" ]; then
454 [div #user_update .nouser
455 This page can only be used by registered users
460 [form #user_update method=POST
461 [hidden "uid" "$USER_ID"]
462 [p .username Logged in as $USER_NAME]
463 [input type=password name=oldpw placeholder="Current Passphrase"]
464 [input type=password name=pw placeholder="New Passphrase" pattern=".{6,}"]
465 [input type=password name=pwconfirm placeholder="Confirm New Passphrase" pattern=".{6,}"]
466 [submit "action" "user_update" Update Passphrase]
473 if [ "$(GET user_confirm)" ]; then
475 elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
477 [div #user_register .disabled
478 User Registration is disabled.
481 elif [ "$USER_REQUIREEMAIL" = true ]; then
483 [form #user_register .registeremail method=POST
484 [p We will send an activation mail to your email address.
485 You can continue the signup process when you click on the
486 activation link in this email.]
487 [input type=email name=email placeholder="Email"]
488 [submit "action" "user_register" Sign Up]
491 elif [ "$USER_REQUIREEMAIL" != true ]; then
493 [form #user_register .registername method=POST
494 [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]
495 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
496 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
497 [submit "action" "user_register" Sign Up]
504 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
505 local user_confirm="$(GET user_confirm)"
506 local uid="${user_confirm% *}" signature="${user_confirm#* }"
508 if [ "$signature" = "$(session_mac "$uid")" ]; then
509 read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
510 $(grep "^${uid} " "$user_db")
512 if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
514 [form #user_confirm method=POST
515 [input type=hidden name=uid value="${uid}"]
516 [input type=hidden name=signature value="${signature}"]
517 $([ "$EMAIL" != '\' ] && printf \
518 '[input disabled=disabled value="%s" placeholder="Email"]' "$(UNSTRING "$EMAIL" |HTML)"
520 [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]
521 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
522 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
523 [submit "action" "user_confirm" Finish Registration]
528 [div #user_confirm .expired
529 [p This activation link is not valid anymore.]
535 [div #user_confirm .invalid
536 [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.]
543 if [ "$(GET user_confirm)" ]; then
545 elif [ "$USER_ID" -a "$SENDMAIL" ]; then
547 [form #user_invite method=POST
548 [input placeholder="Email Recipient" name=email autocomplete=off]
549 [textarea name="message" placeholder="Message to recipient" . ]
550 [submit "action" "user_invite" Send Invitation]
553 elif [ "$USER_ID" ]; then
555 new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"
557 [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.]
558 [p . $(HTML "https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")" |debug)]
560 [p [a href="#" . Set up another account]]
564 [div #user_invite .notallowed
565 Only registered users may send an invitation to another user.
572 if [ ! "$USER_ID" ]; then
574 [form #user_login .login method=POST
575 [input name=uname placeholder="Username or Email" autocomplete=off]
576 [input type=password name=pw placeholder="Passphrase"]
577 [submit "action" "user_login" Login]
580 elif [ "$USER_ID" ]; then
582 [form #user_login .logout method=POST
583 [p Logged in as [span . $(HTML ${USER_NAME})]]
584 [submit "action" "user_logout" Logout]