--- /dev/null
+#!/bin/sh
+
+[ -n "$include_users" ] && return 0
+include_users="$0"
+
+. "${_EXEC}/cgilite/session.sh"
+. "${_EXEC}/cgilite/storage.sh"
+
+USER_REGISTRATION="${USER_REGISTRATION:-true}"
+USER_REQUIREEMAIL="${USER_REQUIREEMAIL:-true}"
+
+HTTP_HOST="$(HEADER Host)"
+MAILFROM="${MAILDOMAIN:-noreply@${HTTP_HOST%:*}}"
+
+user_db="${_DATA}/users.db"
+unset USER_ID USER_NAME USER_EMAIL
+
+# USER DB
+# UID UNAME STATUS (pending|active|deleted) EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
+
+user_init(){
+ local user_id="$(SESSION_VAR user_id)"
+ local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
+ [ "$user_id" ] \
+ && read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
+ $(grep "^${user_id} " "$user_db")
+ EOF
+ [ "$STATUS" -a "$EXPIRE" ] \
+ && if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" ]; then
+ USER_ID="$UID"
+ USER_NAME="$(UNSTRING "$UNAME")"
+ USER_EMAIL="$(UNSTRING "$EMAIL")"
+ fi
+}
+
+user_checkname(){
+ { [ $# -gt 0 ] && printf %s "$*" || cat; } \
+ | sed -nE '
+ :X; $!{N;bX;}
+ s;[ \t\r\n]+; ;g;
+ s;^ ;;; s; $;;;
+ /@/d;
+ /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
+ p;
+ '
+}
+
+user_checkemail(){
+ { [ $# -gt 0 ] && printf %s "$*" || cat; } \
+ | sed -nE '
+ # W3C recommended email regex
+ # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
+ /^[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;
+ '
+}
+
+user_nameexist(){
+ local uname="$(STRING "$1")"
+ local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
+ [ -f "$user_db" -a -r "$user_db" ] \
+ && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
+ [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
+ done <"$user_db"
+ return 1
+}
+
+user_emailexist(){
+ local email="$(STRING "$1")"
+ local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
+ [ -f "$user_db" -a -r "$user_db" ] \
+ && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
+ [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
+ done <"$user_db"
+ return 1
+}
+
+user_pwhash(){
+ local salt="$1" secret="$2" hash
+ hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
+ printf '%s\n' "${hash%% *}"
+}
+
+user_register(){
+ # reserve account, send registration mail
+ # preliminary uid, expiration, signature
+ local uid="$(timeid)"
+ local uname="$(POST uname |user_checkname)"
+ local email="$(POST email |user_checkemail)"
+ local pwsalt="$(randomid)"
+ local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
+
+ if [ "$USER_REGISTRATION" != true ]; then
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
+ fi
+
+ if [ "$USER_REQUIREEMAIL" = true ]; then
+ if [ ! "email" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
+ elif user_emailexist "$email"; then
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
+ elif LOCK "$user_db"; then
+ printf '%s \\ pending %s \\ \\ %i \\ \\\n' \
+ "$uid" "$(STRING "$email")" "$(( $_DATE + 86400 ))" \
+ >>"$user_db"
+ RELEASE "$user_db"
+ sendmail -t -f "$MAILFROM" <<-EOF
+ From: ${MAILFROM}
+ To: ${email}
+ Subject: Your account registration at ${HTTP_HOST%:*}
+
+ Someone tried to sign up for a user account using this email address.
+
+ You can activate your account using this link:
+
+ https://${HTTP_HOST%:*}/${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
+
+ This registration link will expire after 24 hours.
+
+ If you did not request an account at ${HTTP_HOST%:*}, then someone else
+ probably entered your email address by accident. In this case you shoud
+ simply ignore this message and we will remove your email address from
+ our database within the next day.
+
+ This is an automatic email. Any direct reply will not be received.
+ Your Account Registration Robot.
+ EOF
+ REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
+ else
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
+ fi
+
+ elif [ "$USER_REQUIREEMAIL" != true ]; then
+ if [ ! "$uname" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
+ elif user_nameexist "$uname"; then
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
+ elif [ ! "$pw" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
+ elif [ "$pw" != "$pwconfirm" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
+ elif LOCK "$user_db"; then
+ printf '%s %s active %s %s %s %i \\ \\\n' \
+ "$uid" "$(STRING "$uname")" "$(STRING "$email")" \
+ "$pwsalt" "$(user_pwhash "$pwsalt" "$pw")" \
+ "$(( $_DATE + 86400 * 730 ))" \
+ >>"$user_db"
+ RELEASE "$user_db"
+
+ SESSION_COOKIE new
+ SESSION_BIND user_id "$uid"
+
+ REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
+ else
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
+ fi
+ fi
+}
+
+user_confirm(){
+ # enable account
+ local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
+ local uid="$(POST uid |checkid)"
+ local signature="$(POST signature)"
+ local uname="$(POST uname |user_checkname)"
+ local pwsalt="$(randomid)"
+ local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
+
+ if [ "$signature" != "$(session_mac "$uid")" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
+ elif [ ! "$uname" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
+ elif user_nameexist "$uname"; then
+ REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
+ elif [ ! "$pw" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
+ elif [ "$pw" != "$pwconfirm" ]; then
+ REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
+ elif LOCK "$user_db"; then
+ read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
+ $(grep "^${uid} " "$user_db")
+ EOF
+
+ if [ "$STATUS" != pending -o "$EXPIRE" -le "$_DATE" ]; then
+ RELEASE "$user_db"
+ REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
+ else
+ printf '%s %s active %s %s %s %i %s %s\n' \
+ "$UID" "$(STRING "$uname")" "$EMAIL" \
+ "$pwsalt" "$(user_pwhash "$pwsalt" "$pw")" \
+ "$(( $_DATE + 86400 * 730 ))" "$DEVICES" "$FUTUREUSE" \
+ >"${user_db}.$$"
+ grep -v "^${uid} " "$user_db" >>"${user_db}.$$"
+ mv "${user_db}.$$" "${user_db}"
+ RELEASE "$user_db"
+
+ SESSION_COOKIE new
+ SESSION_BIND user_id "$UID"
+ REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
+ fi
+ else
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
+ fi
+}
+
+user_login(){
+ # set cookie
+ # keep logged in - device cookie?
+ # initialize new session!
+ local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
+ local uname="$(POST uname |STRING)" pw="$(POST pw)"
+
+ [ -f "$user_db" -a -r "$user_db" ] \
+ && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
+ if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
+ if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
+ SESSION_COOKIE new
+ SESSION_BIND user_id "$UID"
+ REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
+ fi
+ fi
+ done <"$user_db"
+ REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
+}
+
+user_logout(){
+ # destroy cookie, destroy session
+ # keep device cookie
+ new_session
+ SET_COOKIE 0 session=""
+ SET_COOKIE 0 user_id=""
+ REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
+}
+
+user_update(){
+ # passphrase, email
+ :
+}
+user_recover(){
+ # send recover link
+ :
+}
+user_disable(){
+ :
+}
+
+user_init
+
+[ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
+ user_register) user_register ;;
+ user_confirm) user_confirm ;;
+ user_login) user_login ;;
+ user_logout) user_logout ;;
+ user_update)
+ :;;
+ user_recover)
+ :;;
+ user_disable)
+ :;;
+esac
+
+w_user_register(){
+ if [ "$(GET user_confirm)" ]; then
+ w_user_confirm
+ elif [ "$USER_REGISTRATION" != true ]; then
+ cat <<-EOF
+ [div #user_register .disabled
+ User Registration is disabled.
+ ]
+ EOF
+ elif [ "$USER_REQUIREEMAIL" = true ]; then
+ cat <<-EOF
+ [form #user_register .registeremail method=POST
+ [p We will send an activation mail to your email address.
+ You can continue the signup process when you click on the
+ activation link in this email.]
+ [input type=email name=email placeholder="Email"]
+ [submit "action" "user_register" Sign Up]
+ ]
+ EOF
+ elif [ "$USER_REQUIREEMAIL" != true ]; then
+ cat <<-EOF
+ [form #user_register .registername method=POST
+ [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="^\[a-zA-Z\]\[a-zA-Z0-9 -~\]{2,127}$" autocomplete=off]
+ [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
+ [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
+ [submit "action" "user_register" Sign Up]
+ ]
+ EOF
+ fi
+}
+
+w_user_confirm(){
+ local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
+ local user_confirm="$(GET user_confirm)"
+ local uid="${user_confirm% *}" signature="${user_confirm#* }"
+
+ if [ "$signature" = "$(session_mac "$uid")" ]; then
+ read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
+ $(grep "^${uid} " "$user_db")
+ EOF
+ if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
+ cat <<-EOF
+ [form #user_confirm method=POST
+ [input type=hidden name=uid value="${uid}"]
+ [input type=hidden name=signature value="${signature}"]
+ [input disabled=disabled value="$(HTML "$EMAIL")"]
+ [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="^\[a-zA-Z\]\[a-zA-Z0-9 -~\]{2,127}$" autocomplete=off]
+ [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
+ [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
+ [submit "action" "user_confirm" Finish Registration]
+ ]
+ EOF
+ else
+ cat <<-EOF
+ [div #user_confirm .expired
+ [p This activation link is not valid anymore.]
+ ]
+ EOF
+ fi
+ else
+ cat <<-EOF
+ [div #user_confirm .invalid
+ [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.]
+ ]
+ EOF
+ fi
+}
+
+w_user_login(){
+ if [ ! "$USER_ID" ]; then
+ cat <<-EOF
+ [form #user_login .login method=POST
+ [input name=uname placeholder="Username or Email" autocomplete=off]
+ [input type=password name=pw placeholder="Passphrase"]
+ [submit "action" "user_login" Login]
+ ]
+ EOF
+ elif [ "$USER_ID" ]; then
+ cat <<-EOF
+ [form #user_login .logout method=POST
+ [p You are currently logged in as "${USER_NAME}"]
+ [submit "action" "user_logout" Logout]
+ ]
+ EOF
+ fi
+}