]> git.plutz.net Git - cgilite/blob - users.sh
export user variables
[cgilite] / users.sh
1 #!/bin/sh
2
3 [ -n "$include_users" ] && return 0
4 include_users="$0"
5
6 . "${_EXEC}/cgilite/session.sh"
7 . "${_EXEC}/cgilite/storage.sh"
8
9 SENDMAIL=${SENDMAIL-sendmail}
10
11 USER_REGISTRATION="${USER_REGISTRATION-true}"
12 USER_REQUIREEMAIL="${USER_REQUIREEMAIL-true}"
13 USER_ACCOUNTPAGE="${USER_ACCOUNTPAGE}"
14
15 MAILFROM="${MAILDOMAIN-noreply@${HTTP_HOST%:*}}"
16
17 HTTP_HOST="$(HEADER Host)"
18
19 # == FILE FORMAT ==
20 # UID   UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
21 #               (pending|active|deleted)
22
23 # == GLOBALS ==
24 UNSET_USER='unset \
25   USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
26   USER_EXPIRE USER_DEVICES USER_FUTUREUSE
27 '
28
29 LOCAL_USER='local \
30   USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
31   USER_EXPIRE USER_DEVICES USER_FUTUREUSE
32 '
33
34 unset USER_IDMAP
35 eval "$UNSET_USER"
36
37 user_db="${user_db:-${_DATA}/users.db}"
38
39 read_user() {
40   local user="$1"
41
42   # Global exports
43   USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
44   USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
45
46   if [ $# -eq 0 ]; then
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}")
53         EOF
54   fi
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
60   else
61     eval "$UNSET_USER"
62     return 1
63   fi
64 }
65
66 update_user() {
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
70   local arg
71
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#*=}";;
79   esac; done
80
81   if LOCK "$user_db"; then
82     while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
83                   FUTUREUSE; do
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")}")" \
92              "${FUTUREUSE:-\\}"
93     elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
94       # omit expired invitations from output
95       :
96     else
97       printf '%s        %s      %s      %s      %s      %s      %i      %s      %s\n' \
98              "$UID_" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
99              "$EXPIRE" "$DEVICES" "$FUTUREUSE"
100     fi
101     done <"$user_db" >"${user_db}.$$"
102     mv -- "${user_db}.$$" "$user_db"
103     RELEASE "$user_db"
104   else
105     return 1
106   fi
107 }
108
109 new_user(){
110   local user="${1:-$(timeid)}"
111   shift 1
112
113   if LOCK "$user_db"; then
114     if grep -q "^${user}        " "$user_db"; then
115       RELEASE "$user_db"
116       return 1
117     fi
118     printf '%s  \\      %s      \\      \\      \\      %i      \\      \\\n' \
119            "$user" "pending" "$(( $_DATE + 86400 ))" >>"$user_db"
120   else
121     return 1
122   fi
123
124   if [ $# -eq 0 ]; then
125     RELEASE "$user_db"
126     return 0
127   elif update_user "$user" "$@"; then
128     return 0
129   else
130     RELEASE "$user_db"
131     return 1
132   fi
133 }
134
135 user_idmap(){
136   local uid="$1" ret
137   eval "$LOCAL_USER"
138
139   if [ ! "$USER_IDMAP" ]; then
140     while read_user; do
141       USER_IDMAP="${USER_IDMAP}${USER_ID}       ${USER_NAME}${BR}"
142     done <"$user_db"
143   fi
144   if [ "$uid" -a "$USER_IDMAP" != "${USER_IDMAP##*${uid}        }" ]; then
145     ret="${USER_IDMAP##*${uid}  }"; ret="${ret%%${BR}*}";
146     printf '%s\n' "$ret"
147     return 0
148   elif [ "$uid" ]; then
149     return 1
150   else
151     printf '%s' "$USER_IDMAP"
152     return 0
153   fi
154 }
155
156 user_idof(){
157   local name="$(STRING "$1")" ret
158   [ "$USER_IDMAP" ] || user_idmap >/dev/null
159
160   if [ "${name%\\}" -a "$USER_IDMAP" != "${USER_IDMAP%  ${name}${BR}*}" ]; then
161     ret="${USER_IDMAP%  ${name}${BR}*}"; ret="${ret##*${BR}}"
162     printf '%s\n' "$ret"
163     return 0
164   else
165     return 1
166   fi
167 }
168
169 user_checkname(){
170   { [ $# -gt 0 ] && printf %s "$*" || cat; } \
171   | sed -nE '
172     :X; $!{N;bX;}
173     s;[ \t\r\n]+; ;g;
174     s;^ ;;; s; $;;;
175     /@/d;
176     /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
177     p;
178     '
179 }
180
181 user_checkemail(){
182   { [ $# -gt 0 ] && printf %s "$*" || cat; } \
183   | sed -nE '
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;
187     '
188 }
189
190 user_nameexist(){
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
196   done <"$user_db"
197   return 1
198 }
199
200 user_emailexist(){
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
206   done <"$user_db"
207   return 1
208 }
209
210 user_pwhash(){
211   local salt="$1" secret="$2" hash
212   hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
213   printf '%s\n' "${hash%% *}"
214 }
215
216 user_register(){
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)"
224
225   if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
226     REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
227   fi
228
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
238         From: ${MAILFROM}
239         To: ${email}
240         Subject: Your account registration at ${HTTP_HOST%:*}
241
242         Someone tried to sign up for a user account using this email address.
243
244         You can activate your account using this link:
245
246             https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
247
248         This registration link will expire after 24 hours.
249
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.
254
255         This is an automatic email. Any direct reply will not be received.
256         Your Account Registration Robot.
257         EOF
258       REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
259     else
260       REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
261     fi
262
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
273       SESSION_COOKIE new
274       SESSION_BIND user_id "$uid"
275
276       if [ "$USER_ACCOUNTPAGE" ]; then
277         REDIRECT "${USER_ACCOUNTPAGE}"
278       else
279         REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
280       fi
281     else
282       REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
283     fi
284   fi
285 }
286
287 user_invite(){
288   local uid="$(timeid)"
289   local email="$(POST email |user_checkemail)"
290   local message="$(POST message)"
291
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
300         From: ${MAILFROM}
301         To: ${email}
302         Subject: You have been invited to ${HTTP_HOST%:*}
303
304         ${USER_NAME:-Someone} has offered an invitation to this email address.
305
306         ${message}
307
308         You can create your account using this link:
309
310             https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
311
312         This registration link will expire after 24 hours.
313
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.
318
319         This is an automatic email. Any direct reply will not be received.
320         Your Account Registration Robot.
321         EOF
322     REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
323   else
324     REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
325   fi
326 }
327
328 user_confirm(){
329   # enable account
330   eval "$LOCAL_USER"
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)"
336
337   read_user "${uid}"
338
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
352     SESSION_COOKIE new
353     SESSION_BIND user_id "$USER_ID"
354     if [ "$USER_ACCOUNTPAGE" ]; then
355       REDIRECT "${USER_ACCOUNTPAGE}"
356     else
357       REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
358     fi
359   else
360     REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
361   fi
362 }
363
364 user_login(){
365   # set cookie
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)"
370
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
375         SESSION_COOKIE new
376         SESSION_BIND user_id "$UID_"
377         REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
378       fi
379     fi
380   done <"$user_db"
381   REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
382 }
383
384 user_logout(){
385   # destroy cookie, destroy session
386   # keep device cookie
387   new_session
388   SESSION_COOKIE new
389   SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
390   REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
391 }
392
393 user_update(){
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
398
399         uid="$(POST uid)"
400       oldpw="$(POST oldpw)"
401          pw="$(POST pw |grep -xE '.{6}')"
402   pwconfirm="$(POST pwconfirm)"
403
404
405   read -r UID_  UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE <<-EOF
406         $(grep "^${uid} " "$user_db")
407         EOF
408
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"
413     else
414       REDIRECT "${_BASE}${PATH_INFO}#ERROR_PWMISMATCH"
415     fi
416   elif [ "$UID_" = "$USER_ID" ]; then
417     REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_AUTH_PASSWORD"
418   else
419     REDIRECT "${_BASE}${PATH_INFO}#ERROR_NOTLOGGEDIN"
420   fi
421 }
422
423 user_recover(){
424   # send recover link
425   :
426 }
427 user_disable(){
428   :
429 }
430
431 read_user "$(SESSION_VAR user_id)"
432 [ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
433
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 ;;
441   user_recover)
442     :;;
443   user_disable)
444     :;;
445 esac
446
447 export USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
448        USER_EXPIRE USER_DEVICES USER_FUTUREUSE
449
450
451 w_user_update(){
452   if [ ! "$USER_ID" ]; then
453     cat <<-EOF
454         [div #user_update .nouser
455         This page can only be used by registered users
456         ]
457         EOF
458   else
459     cat <<-EOF
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]
467         ]
468         EOF
469   fi
470 }
471
472 w_user_register(){
473   if [ "$(GET user_confirm)" ]; then
474     w_user_confirm
475   elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
476     cat <<-EOF
477         [div #user_register .disabled
478         User Registration is disabled.
479         ]
480         EOF
481   elif [ "$USER_REQUIREEMAIL" = true ]; then
482     cat <<-EOF
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]
489         ]
490         EOF
491   elif [ "$USER_REQUIREEMAIL" != true ]; then
492     cat <<-EOF
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]
498         ]
499         EOF
500   fi
501 }
502
503 w_user_confirm(){
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#* }"
507
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")
511         EOF
512     if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
513       cat <<-EOF
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)"
519           )
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]
524         ]
525         EOF
526     else
527       cat <<-EOF
528         [div #user_confirm .expired
529           [p This activation link is not valid anymore.]
530         ]
531         EOF
532     fi
533   else
534     cat <<-EOF
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.]
537         ]
538         EOF
539   fi
540 }
541
542 w_user_invite(){
543   if [ "$(GET user_confirm)" ]; then
544     w_user_confirm
545   elif [ "$USER_ID" -a "$SENDMAIL" ]; then
546     cat <<-EOF
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]
551         ]
552         EOF
553   elif [ "$USER_ID" ]; then
554     uid="$(timeid)"
555     new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"
556     cat <<-EOF
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)]
559
560         [p [a href="#" . Set up another account]]
561         EOF
562   else
563     cat <<-EOF
564         [div #user_invite .notallowed
565           Only registered users may send an invitation to another user.
566         ]
567         EOF
568   fi
569 }
570
571 w_user_login(){
572   if [ ! "$USER_ID" ]; then
573     cat <<-EOF
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]
578         ]
579         EOF
580   elif [ "$USER_ID" ]; then
581     cat <<-EOF
582         [form #user_login .logout method=POST
583           [p Logged in as [span . $(HTML ${USER_NAME})]]
584           [submit "action" "user_logout" Logout]
585         ]
586         EOF
587   fi
588 }