From: Paul Hänsch Date: Fri, 24 Sep 2021 10:19:22 +0000 (+0200) Subject: Merge commit '00aa1675c49f23eb075d1fc7ceb69bac5e1ba83c' X-Git-Url: https://git.plutz.net/?a=commitdiff_plain;h=adcddc790b325724f51774ea297f9404b62c3b67;hp=-c;p=serve0 Merge commit '00aa1675c49f23eb075d1fc7ceb69bac5e1ba83c' --- adcddc790b325724f51774ea297f9404b62c3b67 diff --combined cgilite/.gitignore index 0000000,0000000..5c9950a new file mode 100644 --- /dev/null +++ b/cgilite/.gitignore @@@ -1,0 -1,0 +1,3 @@@ ++cgilite ++serverkey ++users.db diff --combined cgilite/session.sh index 5b36ae0,8fb6236..8fb6236 --- a/cgilite/session.sh +++ b/cgilite/session.sh @@@ -75,6 -75,7 +75,7 @@@ checkid(){ { [ $# -gt 0 ] && printf %s update_session(){ local session sid time sig checksig + unset SESSION_KEY SESSION_ID read -r sid time sig <<-END $(POST session_key || COOKIE session) @@@ -82,23 -83,38 +83,38 @@@ checksig="$(session_mac "$sid" "$time")" - if ! [ "$checksig" = "$sig" \ - -a "$time" -ge "$_DATE" \ - -a "$(printf %s "$sid" |checkid)" ] 2>&- + if [ "$checksig" = "$sig" \ + -a "$time" -ge "$_DATE" \ + -a "$(checkid "$sid")" ] 2>&- then - debug "Setting up new session" - sid="$(randomid)" + time=$(( $_DATE + $SESSION_TIMEOUT )) + sig="$(session_mac "$sid" "$time")" + + SESSION_KEY="${sid} ${time} ${sig}" + SESSION_ID="${sid}" + return 0 + else + return 1 fi + } + + new_session(){ + local sid time sig + + debug "Setting up new session" + sid="$(randomid)" time=$(( $_DATE + $SESSION_TIMEOUT )) sig="$(session_mac "$sid" "$time")" - printf %s\\n "${sid} ${time} ${sig}" + + SESSION_KEY="${sid} ${time} ${sig}" + SESSION_ID="${sid}" } SESSION_BIND() { # Set tamper-proof authenticated cookie local key="$1" value="$2" - SET_COOKIE session "$key"="${value} $(session_mac "$value" "$SESSION_ID")" + SET_COOKIE session "$key"="${value} $(session_mac "$value" "$SESSION_ID")" Path="/${_BASE#/}" SameSite=Strict HttpOnly } SESSION_VAR() { @@@ -115,10 -131,10 +131,10 @@@ } SESSION_COOKIE() { - SET_COOKIE 0 session="$SESSION_KEY" Path=/ SameSite=Strict HttpOnly + [ "$1" = new ] && new_session + SET_COOKIE 0 session="$SESSION_KEY" Path="/${_BASE#/}" SameSite=Strict HttpOnly } - SESSION_KEY="$(update_session)" - SESSION_ID="${SESSION_KEY%% *}" + update_session || new_session [ "$1" = nocookie ] || SESSION_COOKIE diff --combined cgilite/users.sh index 0000000,1959e9d..1959e9d mode 000000,100755..100755 --- a/cgilite/users.sh +++ b/cgilite/users.sh @@@ -1,0 -1,346 +1,346 @@@ + #!/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 + }