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 USER_ACCOUNTEXPIRE="${USER_ACCOUNTEXPIRE:-$((86400 * 730))}"
16 USER_CONFIRMEXPIRE="${USER_CONFIRMEXPIRE:-86400}"
18 MAILFROM="${MAILDOMAIN-noreply@${HTTP_HOST%:*}}"
20 HTTP_HOST="$(HEADER Host)"
22 [ "$HTTPS" ] && SCHEMA=https || SCHEMA=http
25 # UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
26 # (pending|active|deleted)
30 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
31 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
35 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
36 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
42 user_db="${user_db:-${_DATA}/users.db}"
48 USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
49 USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
52 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
53 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
54 elif [ "$user" -a -f "$user_db" -a -r "$user_db" ]; then
55 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
56 USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
57 $(grep "^${user} " "${user_db}")
60 if [ "$USER_ID" -a "${USER_EXPIRE:-0}" -gt "$_DATE" ]; then
61 USER_NAME="$(UNSTRING "$USER_NAME")"
62 USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
63 USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
64 unset USER_PWSALT USER_PWHASH
72 # internal function for user update
73 local uid="$1" uname status email pwsalt pwhash expire devices futureuse
74 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
77 for arg in "$@"; do case $arg in
78 uname=*) uname="${arg#*=}";;
79 status=*) status="${arg#*=}";;
80 email=*) email="${arg#*=}";;
81 password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
82 expire=*) expire="${arg#*=}";;
83 devices=*) devices="${arg#*=}";;
86 if LOCK "$user_db"; then
87 while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
89 if [ "$UID_" = "$uid" ]; then
90 printf '%s %s %s %s %s %s %i %s %s\n' \
91 "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
92 "${status:-${status-${STATUS}}${status+\\}}" \
93 "${email:-${email-${EMAIL}}${email+\\}}" \
94 "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
95 "${expire:-$((_DATE + USER_ACCOUNTEXPIRE))}" \
96 "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
98 elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
99 # omit expired invitations from output
102 printf '%s %s %s %s %s %s %i %s %s\n' \
103 "$UID_" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
104 "$EXPIRE" "$DEVICES" "$FUTUREUSE"
106 done <"$user_db" >"${user_db}.$$"
107 mv -- "${user_db}.$$" "$user_db"
115 local user="${1:-$(timeid)}"
118 if LOCK "$user_db"; then
119 if grep -q "^${user} " "$user_db"; then
123 printf '%s \\ %s \\ \\ \\ %i \\ \\\n' \
124 "$user" "pending" "$(( _DATE + USER_CONFIRMEXPIRE ))" >>"$user_db"
129 if [ $# -eq 0 ]; then
132 elif update_user "$user" "$@"; then
144 if [ ! "$USER_IDMAP" ]; then
146 USER_IDMAP="${USER_IDMAP}${USER_ID} ${USER_NAME}${BR}"
149 if [ "$uid" -a "$USER_IDMAP" != "${USER_IDMAP##*${uid} }" ]; then
150 ret="${USER_IDMAP##*${uid} }"; ret="${ret%%${BR}*}";
153 elif [ "$uid" ]; then
156 printf '%s' "$USER_IDMAP"
162 local name="$(STRING "$1")" ret
163 [ "$USER_IDMAP" ] || user_idmap >/dev/null
165 if [ "${name%\\}" -a "$USER_IDMAP" != "${USER_IDMAP% ${name}${BR}*}" ]; then
166 ret="${USER_IDMAP% ${name}${BR}*}"; ret="${ret##*${BR}}"
175 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
181 /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
187 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
189 # W3C recommended email regex
190 # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
191 /^[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;
196 local uname="$(STRING "$1")"
197 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
198 [ -f "$user_db" -a -r "$user_db" ] \
199 && while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
200 [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
206 local email="$(STRING "$1")"
207 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
208 [ -f "$user_db" -a -r "$user_db" ] \
209 && while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
210 [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
216 local salt="$1" secret="$2" hash
217 hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
218 printf '%s\n' "${hash%% *}"
222 # reserve account, send registration mail
223 # preliminary uid, expiration, signature
224 local uid="$(timeid)"
225 local uname="$(POST uname |user_checkname)"
226 local email="$(POST email |user_checkemail)"
227 local pwsalt="$(randomid)"
228 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
230 if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
231 REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
234 if [ "$USER_REQUIREEMAIL" = true ]; then
235 if [ ! "email" ]; then
236 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
237 elif user_emailexist "$email"; then
238 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
239 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + USER_CONFIRMEXPIRE))"; then
240 debug "Sending Activation Link:" \
241 "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
242 "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
245 Subject: Your account registration at ${HTTP_HOST%:*}
247 Someone tried to sign up for a user account using this email address.
249 You can activate your account using this link:
251 ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
253 This registration link will expire after $((USER_CONFIRMEXPIRE / 3600)) hours.
255 If you did not request an account at ${HTTP_HOST%:*}, then someone else
256 probably entered your email address by accident. In this case you shoud
257 simply ignore this message and we will remove your email address from
258 our database within the next day.
260 This is an automatic email. Any direct reply will not be received.
261 Your Account Registration Robot.
263 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
265 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
268 elif [ "$USER_REQUIREEMAIL" != true ]; then
269 if [ ! "$uname" ]; then
270 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
271 elif user_nameexist "$uname"; then
272 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
273 elif [ ! "$pw" ]; then
274 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
275 elif [ "$pw" != "$pwconfirm" ]; then
276 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
277 elif new_user "$uid" uname="$uname" status=active email="$email" password="$pw" expire="$((_DATE + USER_ACCOUNTEXPIRE))"; then
279 SESSION_BIND user_id "$uid"
281 if [ "$USER_ACCOUNTPAGE" ]; then
282 REDIRECT "${USER_ACCOUNTPAGE}"
284 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
287 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
293 local uid="$(timeid)"
294 local email="$(POST email |user_checkemail)"
295 local message="$(POST message)"
297 if [ ! "email" ]; then
298 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
299 elif user_emailexist "$email"; then
300 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
301 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + USER_CONFIRMEXPIRE))"; then
302 debug "Sending Invitation Link:" \
303 "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
304 "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
307 Subject: You have been invited to ${HTTP_HOST%:*}
309 ${USER_NAME:-Someone} has offered an invitation to this email address.
313 You can create your account using this link:
315 ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
317 This registration link will expire after $((USER_CONFIRMEXPIRE / 3600)) hours.
319 If you do not know what this is about, then someone else probably
320 entered your email address by accident. In this case you shoud
321 simply ignore this message and we will remove your email address from
322 our database within the next day.
324 This is an automatic email. Any direct reply will not be received.
325 Your Account Registration Robot.
327 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
329 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
336 local uid="$(POST uid |checkid || printf invalid)"
337 local signature="$(POST signature)"
338 local uname="$(POST uname |user_checkname)"
339 local pwsalt="$(randomid)"
340 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
344 if [ "$signature" != "$(session_mac "$uid")" ]; then
345 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
346 elif [ ! "$uname" ]; then
347 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
348 elif user_nameexist "$uname"; then
349 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
350 elif [ ! "$pw" ]; then
351 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
352 elif [ "$pw" != "$pwconfirm" ]; then
353 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
354 elif [ "$USER_STATUS" != pending -o \! "$USER_EXPIRE" -gt "$_DATE" ]; then
355 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
356 elif update_user "$USER_ID" uname="$uname" status=active password="$pw"; then
358 SESSION_BIND user_id "$USER_ID"
359 if [ "$USER_ACCOUNTPAGE" ]; then
360 REDIRECT "${USER_ACCOUNTPAGE}"
362 REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
365 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
371 # keep logged in - device cookie?
372 # initialize new session!
373 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
374 local uname="$(POST uname |STRING)" pw="$(POST pw)"
376 [ -f "$user_db" -a -r "$user_db" ] \
377 && while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
378 if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
379 if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
381 SESSION_BIND user_id "$UID_"
382 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
386 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
390 # destroy cookie, destroy session
394 SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
395 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
399 # todo: username update, email update / email confirm
400 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
401 # local uname="$(POST uname |STRING)"
402 local uid oldpw pw pwconfirm
405 oldpw="$(POST oldpw)"
406 pw="$(POST pw |grep -xE '.{6}')"
407 pwconfirm="$(POST pwconfirm)"
410 read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
411 $(grep "^${uid} " "$user_db")
414 if [ "$UID_" = "$USER_ID" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$oldpw")" ]; then
415 if [ "$pw" -a "$pw" = "$pwconfirm" ]; then
416 update_user "${uid}" password="$pw"
417 REDIRECT "${_BASE}${PATH_INFO}#UPDATE_SUCCESS"
419 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PWMISMATCH"
421 elif [ "$UID_" = "$USER_ID" ]; then
422 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_AUTH_PASSWORD"
424 REDIRECT "${_BASE}${PATH_INFO}#ERROR_NOTLOGGEDIN"
436 read_user "$(SESSION_VAR user_id)"
437 [ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
439 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
440 user_register) user_register ;;
441 user_confirm) user_confirm ;;
442 user_invite) user_invite ;;
443 user_login) user_login ;;
444 user_logout) user_logout ;;
445 user_update) user_update ;;
452 export USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
453 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
457 if [ ! "$USER_ID" ]; then
459 [div #user_update .nouser
460 This page can only be used by registered users
465 [form #user_update method=POST
466 [hidden "uid" "$USER_ID"]
467 [p .username Logged in as $USER_NAME]
468 [input type=password name=oldpw placeholder="Current Passphrase"]
469 [input type=password name=pw placeholder="New Passphrase" pattern=".{6,}"]
470 [input type=password name=pwconfirm placeholder="Confirm New Passphrase" pattern=".{6,}"]
471 [submit "action" "user_update" Update Passphrase]
478 if [ "$(GET user_confirm)" ]; then
480 elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
482 [div #user_register .disabled
483 User Registration is disabled.
486 elif [ "$USER_REQUIREEMAIL" = true ]; then
488 [form #user_register .registeremail method=POST
489 [p We will send an activation mail to your email address.
490 You can continue the signup process when you click on the
491 activation link in this email.]
492 [input type=email name=email placeholder="Email"]
493 [submit "action" "user_register" Sign Up]
496 elif [ "$USER_REQUIREEMAIL" != true ]; then
498 [form #user_register .registername method=POST
499 [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]
500 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
501 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
502 [submit "action" "user_register" Sign Up]
509 local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
510 local user_confirm="$(GET user_confirm)"
511 local uid="${user_confirm% *}" signature="${user_confirm#* }"
513 if [ "$signature" = "$(session_mac "$uid")" ]; then
514 read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
515 $(grep "^${uid} " "$user_db")
517 if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
519 [form #user_confirm method=POST
520 [input type=hidden name=uid value="${uid}"]
521 [input type=hidden name=signature value="${signature}"]
522 $([ "$EMAIL" != '\' ] && printf \
523 '[input disabled=disabled value="%s" placeholder="Email"]' "$(UNSTRING "$EMAIL" |HTML)"
525 [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]
526 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
527 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
528 [submit "action" "user_confirm" Finish Registration]
533 [div #user_confirm .expired
534 [p This activation link is not valid anymore.]
540 [div #user_confirm .invalid
541 [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.]
550 if [ "$(GET user_confirm)" ]; then
552 elif [ "$USER_ID" -a "$SENDMAIL" ]; then
554 [form #user_invite method=POST
555 [input placeholder="Email Recipient" name=email autocomplete=off]
556 [textarea name="message" placeholder="Message to recipient" . ]
557 [submit "action" "user_invite" Send Invitation]
560 elif [ "$USER_ID" ]; then
562 new_user "$uid" status=pending expire="$((_DATE + USER_CONFIRMEXPIRE))"
563 invlink="${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
564 debug "New Invitation Link: $invlink"
566 [div #user_invite .link
567 [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 $((USER_CONFIRMEXPIRE / 3600)) hours.]
568 [a href="$(HTML "$invlink")" . $(HTML "$invlink")]
570 [p [a href="#" . Set up another account]]
575 [div #user_invite .notallowed
576 Only registered users may send an invitation to another user.
583 if [ ! "$USER_ID" ]; then
585 [form #user_login .login method=POST
586 [input name=uname placeholder="Username or Email" autocomplete=off]
587 [input type=password name=pw placeholder="Passphrase"]
588 [submit "action" "user_login" Login]
591 elif [ "$USER_ID" ]; then
593 [form #user_login .logout method=POST
594 [p Logged in as [span . $(HTML ${USER_NAME})]]
595 [submit "action" "user_logout" Logout]