]> git.plutz.net Git - cgilite/blobdiff - users.sh
ordered list of mime types, additional pdf and text types
[cgilite] / users.sh
index c13703b418fc057adcdc936d26b515c55a3a74f7..6a6833e96c592043669006bbeec91b628a70667e 100755 (executable)
--- a/users.sh
+++ b/users.sh
@@ -6,41 +6,72 @@ include_users="$0"
 . "${_EXEC}/cgilite/session.sh"
 . "${_EXEC}/cgilite/storage.sh"
 
 . "${_EXEC}/cgilite/session.sh"
 . "${_EXEC}/cgilite/storage.sh"
 
-USER_REGISTRATION="${USER_REGISTRATION:-true}"
-USER_REQUIREEMAIL="${USER_REQUIREEMAIL:-true}"
+SENDMAIL=${SENDMAIL-sendmail}
+
+USER_REGISTRATION="${USER_REGISTRATION-true}"
+USER_REQUIREEMAIL="${USER_REQUIREEMAIL-true}"
+USER_ACCOUNTPAGE="${USER_ACCOUNTPAGE}"
+
+USER_ACCOUNTEXPIRE="${USER_ACCOUNTEXPIRE:-$((86400 * 730))}"
+USER_CONFIRMEXPIRE="${USER_CONFIRMEXPIRE:-86400}"
+
+MAILFROM="${MAILDOMAIN-noreply@${HTTP_HOST%:*}}"
 
 HTTP_HOST="$(HEADER Host)"
 
 HTTP_HOST="$(HEADER Host)"
-MAILFROM="${MAILDOMAIN:-noreply@${HTTP_HOST%:*}}"
 
 
-user_db="${_DATA}/users.db"
-unset USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
-      USER_EXPIRE USER_DEVICES USER_FUTUREUSE
+[ "$HTTPS" ] && SCHEMA=https || SCHEMA=http
+
+# == FILE FORMAT ==
+# UID  UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
+#              (pending|active|deleted)
+
+# == GLOBALS ==
+UNSET_USER='unset \
+  USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
+  USER_EXPIRE USER_DEVICES USER_FUTUREUSE
+'
+
+LOCAL_USER='local \
+  USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
+  USER_EXPIRE USER_DEVICES USER_FUTUREUSE
+'
 
 
-# USER DB
-# UID  UNAME   STATUS (pending|active|deleted) EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
+unset USER_IDMAP
+eval "$UNSET_USER"
 
 
-user_id="$(SESSION_VAR user_id)"
-if [ "$user_id" -a -f "$user_db" -a -r "$user_db" ]; then
-   read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
-           USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
-       $(grep "^${user_id}     " "$user_db")
+user_db="${user_db:-${_DATA}/users.db}"
+
+read_user() {
+  local user="$1"
+
+  # Global exports
+  USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
+  USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
+
+  if [ $# -eq 0 ]; then
+    read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
+            USER_EXPIRE USER_DEVICES USER_FUTUREUSE
+  elif [ "$user" -a -f "$user_db" -a -r "$user_db" ]; then
+    read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
+            USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
+       $(grep "^${user}        " "${user_db}")
        EOF
        EOF
-  if [ "$USER_ID" -a "$USER_STATUS" = active -a "$USER_EXPIRE" -gt "$_DATE" ]; then
+  fi
+  if [ "$USER_ID" -a "${USER_EXPIRE:-0}" -gt "$_DATE" ]; then
        USER_NAME="$(UNSTRING "$USER_NAME")"
       USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
     USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
     unset USER_PWSALT USER_PWHASH
   else
        USER_NAME="$(UNSTRING "$USER_NAME")"
       USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
     USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
     unset USER_PWSALT USER_PWHASH
   else
-    unset USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
-          USER_EXPIRE USER_DEVICES USER_FUTUREUSE
+    eval "$UNSET_USER"
+    return 1
   fi
   fi
-fi
-unset user_id
+}
 
 
-user_update_user() {
+update_user() {
   # internal function for user update
   local uid="$1" uname status email pwsalt pwhash expire devices futureuse
   # internal function for user update
   local uid="$1" uname status email pwsalt pwhash expire devices futureuse
-  local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
+  local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
   local arg
 
   for arg in "$@"; do case $arg in
   local arg
 
   for arg in "$@"; do case $arg in
@@ -48,27 +79,28 @@ user_update_user() {
     status=*) status="${arg#*=}";;
     email=*) email="${arg#*=}";;
     password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
     status=*) status="${arg#*=}";;
     email=*) email="${arg#*=}";;
     password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
+    expire=*) expire="${arg#*=}";;
     devices=*) devices="${arg#*=}";;
   esac; done
 
   if LOCK "$user_db"; then
     devices=*) devices="${arg#*=}";;
   esac; done
 
   if LOCK "$user_db"; then
-    while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
+    while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
                   FUTUREUSE; do
                   FUTUREUSE; do
-    if [ "$UID" = "$uid" ]; then
-      printf '%s       %s      %s      %s      %s      %s      %s      %s      %s\n' \
+    if [ "$UID_" = "$uid" ]; then
+      printf '%s       %s      %s      %s      %s      %s      %i      %s      %s\n' \
              "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
              "${status:-${status-${STATUS}}${status+\\}}" \
              "${email:-${email-${EMAIL}}${email+\\}}" \
              "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
              "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
              "${status:-${status-${STATUS}}${status+\\}}" \
              "${email:-${email-${EMAIL}}${email+\\}}" \
              "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
-             "$((_DATE + 86400 * 730))" \
+             "${expire:-$((_DATE + USER_ACCOUNTEXPIRE))}" \
              "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
              "${FUTUREUSE:-\\}"
     elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
       # omit expired invitations from output
       :
     else
              "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
              "${FUTUREUSE:-\\}"
     elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
       # omit expired invitations from output
       :
     else
-      printf '%s       %s      %s      %s      %s      %s      %s      %s      %s\n' \
-             "$UID" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
+      printf '%s       %s      %s      %s      %s      %s      %i      %s      %s\n' \
+             "$UID_" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
              "$EXPIRE" "$DEVICES" "$FUTUREUSE"
     fi
     done <"$user_db" >"${user_db}.$$"
              "$EXPIRE" "$DEVICES" "$FUTUREUSE"
     fi
     done <"$user_db" >"${user_db}.$$"
@@ -79,6 +111,66 @@ user_update_user() {
   fi
 }
 
   fi
 }
 
+new_user(){
+  local user="${1:-$(timeid)}"
+  shift 1
+
+  if LOCK "$user_db"; then
+    if grep -q "^${user}       " "$user_db"; then
+      RELEASE "$user_db"
+      return 1
+    fi
+    printf '%s \\      %s      \\      \\      \\      %i      \\      \\\n' \
+           "$user" "pending" "$(( _DATE + USER_CONFIRMEXPIRE ))" >>"$user_db"
+  else
+    return 1
+  fi
+
+  if [ $# -eq 0 ]; then
+    RELEASE "$user_db"
+    return 0
+  elif update_user "$user" "$@"; then
+    return 0
+  else
+    RELEASE "$user_db"
+    return 1
+  fi
+}
+
+user_idmap(){
+  local uid="$1" ret
+  eval "$LOCAL_USER"
+
+  if [ ! "$USER_IDMAP" ]; then
+    while read_user; do
+      USER_IDMAP="${USER_IDMAP}${USER_ID}      ${USER_NAME}${BR}"
+    done <"$user_db"
+  fi
+  if [ "$uid" -a "$USER_IDMAP" != "${USER_IDMAP##*${uid}       }" ]; then
+    ret="${USER_IDMAP##*${uid} }"; ret="${ret%%${BR}*}";
+    printf '%s\n' "$ret"
+    return 0
+  elif [ "$uid" ]; then
+    return 1
+  else
+    printf '%s' "$USER_IDMAP"
+    return 0
+  fi
+}
+
+user_idof(){
+  local name="$(STRING "$1")" ret
+  [ "$USER_IDMAP" ] || user_idmap >/dev/null
+
+  if [ "${name%\\}" -a "$USER_IDMAP" != "${USER_IDMAP% ${name}${BR}*}" ]; then
+    ret="${USER_IDMAP% ${name}${BR}*}"; ret="${ret##*${BR}}"
+    printf '%s\n' "$ret"
+    return 0
+  else
+    return 1
+  fi
+}
+
 user_checkname(){
   { [ $# -gt 0 ] && printf %s "$*" || cat; } \
   | sed -nE '
 user_checkname(){
   { [ $# -gt 0 ] && printf %s "$*" || cat; } \
   | sed -nE '
@@ -102,9 +194,9 @@ user_checkemail(){
 
 user_nameexist(){
   local uname="$(STRING "$1")"
 
 user_nameexist(){
   local uname="$(STRING "$1")"
-  local UID    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
+  local UID_   UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
   [ -f "$user_db" -a -r "$user_db" ] \
   [ -f "$user_db" -a -r "$user_db" ] \
-  && while read -r UID UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE; do
+  && 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
     [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
   done <"$user_db"
   return 1
@@ -112,9 +204,9 @@ user_nameexist(){
 
 user_emailexist(){
   local email="$(STRING "$1")"
 
 user_emailexist(){
   local email="$(STRING "$1")"
-  local UID    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
+  local UID_   UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
   [ -f "$user_db" -a -r "$user_db" ] \
   [ -f "$user_db" -a -r "$user_db" ] \
-  && while read -r UID UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE; do
+  && 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
     [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
   done <"$user_db"
   return 1
@@ -144,15 +236,10 @@ user_register(){
       REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
     elif user_emailexist "$email"; then
       REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
       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"
-
+    elif new_user "$uid" status=pending email="$email" expire="$((_DATE + USER_CONFIRMEXPIRE))"; then
       debug "Sending Activation Link:" \
       debug "Sending Activation Link:" \
-            "https://${HTTP_HOST%:*}/${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
-      sendmail -t -f "$MAILFROM" <<-EOF
+            "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
+      "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
        From: ${MAILFROM}
        To: ${email}
        Subject: Your account registration at ${HTTP_HOST%:*}
        From: ${MAILFROM}
        To: ${email}
        Subject: Your account registration at ${HTTP_HOST%:*}
@@ -161,9 +248,9 @@ user_register(){
 
        You can activate your account using this link:
 
 
        You can activate your account using this link:
 
-           https://${HTTP_HOST%:*}/${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
+           ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
 
 
-       This registration link will expire after 24 hours.
+       This registration link will expire after $((USER_CONFIRMEXPIRE / 3600)) 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
 
        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
@@ -187,18 +274,15 @@ user_register(){
       REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
     elif [ "$pw" != "$pwconfirm" ]; then
       REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
       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"
-
+    elif new_user "$uid" uname="$uname" status=active email="$email" password="$pw" expire="$((_DATE + USER_ACCOUNTEXPIRE))"; then
       SESSION_COOKIE new
       SESSION_BIND user_id "$uid"
 
       SESSION_COOKIE new
       SESSION_BIND user_id "$uid"
 
-      REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
+      if [ "$USER_ACCOUNTPAGE" ]; then
+        REDIRECT "${USER_ACCOUNTPAGE}"
+      else
+        REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
+      fi
     else
       REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
     fi
     else
       REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
     fi
@@ -214,14 +298,10 @@ user_invite(){
     REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
   elif user_emailexist "$email"; then
     REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
     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"
+  elif new_user "$uid" status=pending email="$email" expire="$((_DATE + USER_CONFIRMEXPIRE))"; then
     debug "Sending Invitation Link:" \
     debug "Sending Invitation Link:" \
-          "https://${HTTP_HOST%:*}/${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
-    sendmail -t -f "$MAILFROM" <<-EOF
+          "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
+    "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
        From: ${MAILFROM}
        To: ${email}
        Subject: You have been invited to ${HTTP_HOST%:*}
        From: ${MAILFROM}
        To: ${email}
        Subject: You have been invited to ${HTTP_HOST%:*}
@@ -232,9 +312,9 @@ user_invite(){
 
        You can create your account using this link:
 
 
        You can create your account using this link:
 
-           https://${HTTP_HOST%:*}/${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
+           ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
 
 
-       This registration link will expire after 24 hours.
+       This registration link will expire after $((USER_CONFIRMEXPIRE / 3600)) hours.
 
        If you do not know what this is about, then someone else probably
        entered your email address by accident. In this case you shoud
 
        If you do not know what this is about, then someone else probably
        entered your email address by accident. In this case you shoud
@@ -252,16 +332,14 @@ user_invite(){
 
 user_confirm(){
   # enable account
 
 user_confirm(){
   # enable account
-  local UID    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
-  local uid="$(POST uid |checkid)"
+  eval "$LOCAL_USER"
+  local uid="$(POST uid |checkid || printf invalid)"
   local signature="$(POST signature)"
   local uname="$(POST uname |user_checkname)"
   local pwsalt="$(randomid)"
   local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
 
   local signature="$(POST signature)"
   local uname="$(POST uname |user_checkname)"
   local pwsalt="$(randomid)"
   local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
 
-  read -r UID  UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE <<-EOF
-       $(grep "^${uid:-invalid}        " "$user_db")
-       EOF
+  read_user "${uid}"
 
   if [ "$signature" != "$(session_mac "$uid")" ]; then
     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
 
   if [ "$signature" != "$(session_mac "$uid")" ]; then
     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
@@ -273,12 +351,16 @@ user_confirm(){
     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
   elif [ "$pw" != "$pwconfirm" ]; then
     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
   elif [ "$pw" != "$pwconfirm" ]; then
     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
-  elif [ "$STATUS" != pending -o \! "$EXPIRE" -gt "$_DATE" ]; then
+  elif [ "$USER_STATUS" != pending -o \! "$USER_EXPIRE" -gt "$_DATE" ]; then
     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
-  elif user_update_user "$UID" uname="$uname" status=active password="$pw"; then
+  elif update_user "$USER_ID" uname="$uname" status=active password="$pw"; then
     SESSION_COOKIE new
     SESSION_COOKIE new
-    SESSION_BIND user_id "$UID"
-    REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
+    SESSION_BIND user_id "$USER_ID"
+    if [ "$USER_ACCOUNTPAGE" ]; then
+      REDIRECT "${USER_ACCOUNTPAGE}"
+    else
+      REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
+    fi
   else
     REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
   fi
   else
     REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
   fi
@@ -288,15 +370,15 @@ user_login(){
   # set cookie
   # keep logged in - device cookie?
   # initialize new session!
   # set cookie
   # keep logged in - device cookie?
   # initialize new session!
-  local UID    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
+  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" ] \
   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
+  && 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
     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"
+        SESSION_BIND user_id "$UID_"
         REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
       fi
     fi
         REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
       fi
     fi
@@ -314,9 +396,35 @@ user_logout(){
 }
 
 user_update(){
 }
 
 user_update(){
-  # passphrase, email
-  :
+  # todo: username update, email update / email confirm
+  local UID_   UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
+  # local uname="$(POST uname |STRING)"
+  local uid oldpw pw pwconfirm
+
+        uid="$(POST uid)"
+      oldpw="$(POST oldpw)"
+         pw="$(POST pw |grep -xE '.{6}')"
+  pwconfirm="$(POST pwconfirm)"
+
+
+  read -r UID_ UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE <<-EOF
+       $(grep "^${uid} " "$user_db")
+       EOF
+
+  if [ "$UID_" = "$USER_ID" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$oldpw")" ]; then
+    if [ "$pw" -a "$pw" = "$pwconfirm" ]; then
+      update_user "${uid}" password="$pw"
+      REDIRECT "${_BASE}${PATH_INFO}#UPDATE_SUCCESS"
+    else
+      REDIRECT "${_BASE}${PATH_INFO}#ERROR_PWMISMATCH"
+    fi
+  elif [ "$UID_" = "$USER_ID" ]; then
+    REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_AUTH_PASSWORD"
+  else
+    REDIRECT "${_BASE}${PATH_INFO}#ERROR_NOTLOGGEDIN"
+  fi
 }
 }
+
 user_recover(){
   # send recover link
   :
 user_recover(){
   # send recover link
   :
@@ -325,20 +433,47 @@ user_disable(){
   :
 }
 
   :
 }
 
+read_user "$(SESSION_VAR user_id)"
+[ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
+
 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
   user_register) user_register ;;
   user_confirm)  user_confirm ;;
   user_invite)   user_invite ;;
   user_login)    user_login ;;
   user_logout)   user_logout ;;
 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
   user_register) user_register ;;
   user_confirm)  user_confirm ;;
   user_invite)   user_invite ;;
   user_login)    user_login ;;
   user_logout)   user_logout ;;
-  user_update)
-    :;;
+  user_update)   user_update ;;
   user_recover)
     :;;
   user_disable)
     :;;
 esac
 
   user_recover)
     :;;
   user_disable)
     :;;
 esac
 
+export USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
+       USER_EXPIRE USER_DEVICES USER_FUTUREUSE
+
+
+w_user_update(){
+  if [ ! "$USER_ID" ]; then
+    cat <<-EOF
+       [div #user_update .nouser
+       This page can only be used by registered users
+       ]
+       EOF
+  else
+    cat <<-EOF
+       [form #user_update method=POST
+         [hidden "uid" "$USER_ID"]
+         [p .username Logged in as $USER_NAME]
+         [input type=password name=oldpw placeholder="Current Passphrase"]
+         [input type=password name=pw placeholder="New Passphrase" pattern=".{6,}"]
+         [input type=password name=pwconfirm placeholder="Confirm New Passphrase" pattern=".{6,}"]
+         [submit "action" "user_update" Update Passphrase]
+       ]
+       EOF
+  fi
+}
+
 w_user_register(){
   if [ "$(GET user_confirm)" ]; then
     w_user_confirm
 w_user_register(){
   if [ "$(GET user_confirm)" ]; then
     w_user_confirm
@@ -361,7 +496,7 @@ w_user_register(){
   elif [ "$USER_REQUIREEMAIL" != true ]; then
     cat <<-EOF
        [form #user_register .registername method=POST
   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 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]
          [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]
          [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]
@@ -371,12 +506,12 @@ w_user_register(){
 }
 
 w_user_confirm(){
 }
 
 w_user_confirm(){
-  local UID    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
+  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
   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
+    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
        $(grep "^${uid} " "$user_db")
        EOF
     if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
@@ -384,8 +519,10 @@ w_user_confirm(){
        [form #user_confirm method=POST
          [input type=hidden name=uid value="${uid}"]
          [input type=hidden name=signature value="${signature}"]
        [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]
+         $([ "$EMAIL" != '\' ] && printf \
+           '[input disabled=disabled value="%s" placeholder="Email"]' "$(UNSTRING "$EMAIL" |HTML)"
+         )
+          [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]
          [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]
          [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]
@@ -408,9 +545,11 @@ w_user_confirm(){
 }
 
 w_user_invite(){
 }
 
 w_user_invite(){
+  local uid invlink
+
   if [ "$(GET user_confirm)" ]; then
     w_user_confirm
   if [ "$(GET user_confirm)" ]; then
     w_user_confirm
-  elif [ "$USER_ID" ]; then
+  elif [ "$USER_ID" -a "$SENDMAIL" ]; then
     cat <<-EOF
        [form #user_invite method=POST
          [input placeholder="Email Recipient" name=email autocomplete=off]
     cat <<-EOF
        [form #user_invite method=POST
          [input placeholder="Email Recipient" name=email autocomplete=off]
@@ -418,6 +557,19 @@ w_user_invite(){
          [submit "action" "user_invite" Send Invitation]
        ]
        EOF
          [submit "action" "user_invite" Send Invitation]
        ]
        EOF
+  elif [ "$USER_ID" ]; then
+    uid="$(timeid)"
+    new_user "$uid" status=pending expire="$((_DATE + USER_CONFIRMEXPIRE))"
+    invlink="${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
+    debug "New Invitation Link: $invlink"
+    cat <<-EOF
+       [div #user_invite .link
+          [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.]
+          [a href="$(HTML "$invlink")" . $(HTML "$invlink")]
+
+          [p [a href="#" . Set up another account]]
+       ]
+       EOF
   else
     cat <<-EOF
        [div #user_invite .notallowed
   else
     cat <<-EOF
        [div #user_invite .notallowed