]> git.plutz.net Git - cgilite/blob - users.sh
bugfix: endless loop in _startlist because of lacking indention removal
[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 USER_ACCOUNTEXPIRE="${USER_ACCOUNTEXPIRE:-$((86400 * 730))}"
16 USER_CONFIRMEXPIRE="${USER_CONFIRMEXPIRE:-86400}"
17
18 MAILFROM="${MAILDOMAIN-noreply@${HTTP_HOST%:*}}"
19
20 HTTP_HOST="$(HEADER Host)"
21
22 [ "$HTTPS" ] && SCHEMA=https || SCHEMA=http
23
24 # == FILE FORMAT ==
25 # UID   UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
26 #               (pending|active|deleted)
27
28 # == GLOBALS ==
29 UNSET_USER='unset \
30   USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
31   USER_EXPIRE USER_DEVICES USER_FUTUREUSE
32 '
33
34 LOCAL_USER='local \
35   USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
36   USER_EXPIRE USER_DEVICES USER_FUTUREUSE
37 '
38
39 # == TRANSLATIONS ==
40 # override all functions marked with "TRANSLATION"
41 # sed -n '/TRANSLATION$/,/^}/p;' <cgilite/users.sh
42
43 unset USER_IDMAP
44 eval "$UNSET_USER"
45
46 user_db="${user_db:-${_DATA}/users.db}"
47
48 read_user() {
49   local user="$1"
50
51   # Global exports
52   USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
53   USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
54
55   if [ $# -eq 0 ]; then
56     read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
57             USER_EXPIRE USER_DEVICES USER_FUTUREUSE
58   elif [ "$user" -a -f "$user_db" -a -r "$user_db" ]; then
59     read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
60             USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
61         $(grep "^${user}        " "${user_db}")
62         EOF
63   fi
64   if [ "$USER_ID" -a "${USER_EXPIRE:-0}" -gt "$_DATE" ]; then
65        USER_NAME="$(UNSTRING "$USER_NAME")"
66       USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
67     USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
68     unset USER_PWSALT USER_PWHASH
69   else
70     eval "$UNSET_USER"
71     return 1
72   fi
73 }
74
75 update_user() {
76   # internal function for user update
77   local uid="$1" uname status email pwsalt pwhash expire devices futureuse
78   local UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
79   local arg
80
81   for arg in "$@"; do case $arg in
82     uname=*) uname="${arg#*=}";;
83     status=*) status="${arg#*=}";;
84     email=*) email="${arg#*=}";;
85     password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
86     expire=*) expire="${arg#*=}";;
87     devices=*) devices="${arg#*=}";;
88   esac; done
89
90   if LOCK "$user_db"; then
91     while read -r UID_ UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
92                   FUTUREUSE; do
93     if [ "$UID_" = "$uid" ]; then
94       printf '%s        %s      %s      %s      %s      %s      %i      %s      %s\n' \
95              "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
96              "${status:-${status-${STATUS}}${status+\\}}" \
97              "${email:-${email-${EMAIL}}${email+\\}}" \
98              "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
99              "${expire:-$((_DATE + USER_ACCOUNTEXPIRE))}" \
100              "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
101              "${FUTUREUSE:-\\}"
102     elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
103       # omit expired invitations from output
104       :
105     else
106       printf '%s        %s      %s      %s      %s      %s      %i      %s      %s\n' \
107              "$UID_" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
108              "$EXPIRE" "$DEVICES" "$FUTUREUSE"
109     fi
110     done <"$user_db" >"${user_db}.$$"
111     mv -- "${user_db}.$$" "$user_db"
112     RELEASE "$user_db"
113   else
114     return 1
115   fi
116 }
117
118 new_user(){
119   local user="${1:-$(timeid)}"
120   shift 1
121
122   if LOCK "$user_db"; then
123     if grep -q "^${user}        " "$user_db"; then
124       RELEASE "$user_db"
125       return 1
126     fi
127     printf '%s  \\      %s      \\      \\      \\      %i      \\      \\\n' \
128            "$user" "pending" "$(( _DATE + USER_CONFIRMEXPIRE ))" >>"$user_db"
129   else
130     return 1
131   fi
132
133   if [ $# -eq 0 ]; then
134     RELEASE "$user_db"
135     return 0
136   elif update_user "$user" "$@"; then
137     return 0
138   else
139     RELEASE "$user_db"
140     return 1
141   fi
142 }
143
144 user_idmap(){
145   local uid="$1" ret
146   eval "$LOCAL_USER"
147
148   if [ ! "$USER_IDMAP" ]; then
149     while read_user; do
150       USER_IDMAP="${USER_IDMAP}${USER_ID}       ${USER_NAME}${BR}"
151     done <"$user_db"
152   fi
153   if [ "$uid" -a "$USER_IDMAP" != "${USER_IDMAP##*${uid}        }" ]; then
154     ret="${USER_IDMAP##*${uid}  }"; ret="${ret%%${BR}*}";
155     printf '%s\n' "$ret"
156     return 0
157   elif [ "$uid" ]; then
158     return 1
159   else
160     printf '%s' "$USER_IDMAP"
161     return 0
162   fi
163 }
164
165 user_idof(){
166   local name="$(STRING "$1")" ret
167   [ "$USER_IDMAP" ] || user_idmap >/dev/null
168
169   if [ "${name%\\}" -a "$USER_IDMAP" != "${USER_IDMAP%  ${name}${BR}*}" ]; then
170     ret="${USER_IDMAP%  ${name}${BR}*}"; ret="${ret##*${BR}}"
171     printf '%s\n' "$ret"
172     return 0
173   else
174     return 1
175   fi
176 }
177
178 user_checkname(){
179   { [ $# -gt 0 ] && printf %s "$*" || cat; } \
180   | sed -nE '
181     :X; $!{N;bX;}
182     s;[ \t\r\n]+; ;g;
183     s;^ ;;; s; $;;;
184     /@/d;
185     /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
186     p;
187     '
188 }
189
190 user_checkemail(){
191   { [ $# -gt 0 ] && printf %s "$*" || cat; } \
192   | sed -nE '
193     # W3C recommended email regex
194     # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
195     /^[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     '
197 }
198
199 user_nameexist(){
200   local uname="$(STRING "$1")"
201   local UID_    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
202   [ -f "$user_db" -a -r "$user_db" ] \
203   && while read -r UID_ UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE; do
204     [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
205   done <"$user_db"
206   return 1
207 }
208
209 user_emailexist(){
210   local email="$(STRING "$1")"
211   local UID_    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
212   [ -f "$user_db" -a -r "$user_db" ] \
213   && while read -r UID_ UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE; do
214     [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
215   done <"$user_db"
216   return 1
217 }
218
219 user_pwhash(){
220   local salt="$1" secret="$2" hash
221   hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
222   printf '%s\n' "${hash%% *}"
223 }
224
225 user_register_email() {  # TRANSLATION
226   "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
227         From: ${MAILFROM}
228         To: ${email}
229         Subject: Your account registration at ${HTTP_HOST%:*}
230
231         Someone tried to sign up for a user account using this email address.
232
233         You can activate your account using this link:
234
235             ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
236
237         This registration link will expire after $((USER_CONFIRMEXPIRE / 3600)) hours.
238
239         If you did not request an account at ${HTTP_HOST%:*}, then someone else
240         probably entered your email address by accident. In this case you shoud
241         simply ignore this message and we will remove your email address from
242         our database within the next day.
243
244         This is an automatic email. Any direct reply will not be received.
245         Your Account Registration Robot.
246         EOF
247 }
248
249 user_register(){
250   # reserve account, send registration mail
251   # preliminary uid, expiration, signature
252   local uid="$(timeid)"
253   local uname="$(POST uname |user_checkname)"
254   local email="$(POST email |user_checkemail)"
255   local pwsalt="$(randomid)"
256   local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
257
258   if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
259     REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
260   fi
261
262   if   [ "$USER_REQUIREEMAIL" = true ]; then
263     if [ ! "email" ]; then
264       REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
265     elif user_emailexist "$email"; then
266       REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
267     elif new_user "$uid" status=pending email="$email" expire="$((_DATE + USER_CONFIRMEXPIRE))"; then
268       debug "Sending Activation Link:" \
269             "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
270       user_register_email
271       REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
272     else
273       REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
274     fi
275
276   elif [ "$USER_REQUIREEMAIL" != true ]; then
277     if [ ! "$uname" ]; then
278       REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
279     elif user_nameexist "$uname"; then
280       REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
281     elif [ ! "$pw" ]; then
282       REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
283     elif [ "$pw" != "$pwconfirm" ]; then
284       REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
285     elif new_user "$uid" uname="$uname" status=active email="$email" password="$pw" expire="$((_DATE + USER_ACCOUNTEXPIRE))"; then
286       SESSION_COOKIE new
287       SESSION_BIND user_id "$uid"
288
289       if [ "$USER_ACCOUNTPAGE" ]; then
290         REDIRECT "${USER_ACCOUNTPAGE}"
291       else
292         REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
293       fi
294     else
295       REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
296     fi
297   fi
298 }
299
300 user_invite_email(){  # TRANSLATION
301   "$SENDMAIL" -t -f "$MAILFROM" <<-EOF
302         From: ${MAILFROM}
303         To: ${email}
304         Subject: You have been invited to ${HTTP_HOST%:*}
305
306         ${USER_NAME:-Someone} has offered an invitation to this email address.
307
308         ${message}
309
310         You can create your account using this link:
311
312             ${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
313
314         This registration link will expire after $((USER_CONFIRMEXPIRE / 3600)) hours.
315
316         If you do not know what this is about, then someone else probably
317         entered your email address by accident. In this case you shoud
318         simply ignore this message and we will remove your email address from
319         our database within the next day.
320
321         This is an automatic email. Any direct reply will not be received.
322         Your Account Registration Robot.
323         EOF
324 }
325
326 user_invite(){
327   local uid="$(timeid)"
328   local email="$(POST email |user_checkemail)"
329   local message="$(POST message)"
330
331   if [ ! "email" ]; then
332     REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
333   elif user_emailexist "$email"; then
334     REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
335   elif new_user "$uid" status=pending email="$email" expire="$((_DATE + USER_CONFIRMEXPIRE))"; then
336     debug "Sending Invitation Link:" \
337           "${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
338     user_invite_email
339     REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
340   else
341     REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
342   fi
343 }
344
345 user_confirm(){
346   # enable account
347   eval "$LOCAL_USER"
348   local uid="$(POST uid |checkid || printf invalid)"
349   local signature="$(POST signature)"
350   local uname="$(POST uname |user_checkname)"
351   local pwsalt="$(randomid)"
352   local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
353
354   read_user "${uid}"
355
356   if [ "$signature" != "$(session_mac "$uid")" ]; then
357     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
358   elif [ ! "$uname" ]; then
359     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
360   elif user_nameexist "$uname"; then
361     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
362   elif [ ! "$pw" ]; then
363     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
364   elif [ "$pw" != "$pwconfirm" ]; then
365     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
366   elif [ "$USER_STATUS" != pending -o \! "$USER_EXPIRE" -gt "$_DATE" ]; then
367     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
368   elif update_user "$USER_ID" uname="$uname" status=active password="$pw"; then
369     SESSION_COOKIE new
370     SESSION_BIND user_id "$USER_ID"
371     if [ "$USER_ACCOUNTPAGE" ]; then
372       REDIRECT "${USER_ACCOUNTPAGE}"
373     else
374       REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
375     fi
376   else
377     REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
378   fi
379 }
380
381 user_login(){
382   # set cookie
383   # keep logged in - device cookie?
384   # initialize new session!
385   local UID_    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
386   local uname="$(POST uname |STRING)" pw="$(POST pw)"
387
388   [ -f "$user_db" -a -r "$user_db" ] \
389   && while read -r UID_ UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE; do
390     if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
391       if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
392         SESSION_COOKIE new
393         SESSION_BIND user_id "$UID_"
394         REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
395       fi
396     fi
397   done <"$user_db"
398   REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
399 }
400
401 user_logout(){
402   # destroy cookie, destroy session
403   # keep device cookie
404   new_session
405   SESSION_COOKIE new
406   SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
407   REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
408 }
409
410 user_update(){
411   # todo: username update, email update / email confirm
412   local UID_    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
413   # local uname="$(POST uname |STRING)"
414   local uid oldpw pw pwconfirm
415
416         uid="$(POST uid)"
417       oldpw="$(POST oldpw)"
418          pw="$(POST pw |grep -xE '.{6}')"
419   pwconfirm="$(POST pwconfirm)"
420
421
422   read -r UID_  UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE <<-EOF
423         $(grep "^${uid} " "$user_db")
424         EOF
425
426   if [ "$UID_" = "$USER_ID" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$oldpw")" ]; then
427     if [ "$pw" -a "$pw" = "$pwconfirm" ]; then
428       update_user "${uid}" password="$pw"
429       REDIRECT "${_BASE}${PATH_INFO}#UPDATE_SUCCESS"
430     else
431       REDIRECT "${_BASE}${PATH_INFO}#ERROR_PWMISMATCH"
432     fi
433   elif [ "$UID_" = "$USER_ID" ]; then
434     REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_AUTH_PASSWORD"
435   else
436     REDIRECT "${_BASE}${PATH_INFO}#ERROR_NOTLOGGEDIN"
437   fi
438 }
439
440 user_recover(){
441   # send recover link
442   :
443 }
444 user_disable(){
445   :
446 }
447
448 read_user "$(SESSION_VAR user_id)"
449 [ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
450
451 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
452   user_register) user_register ;;
453   user_confirm)  user_confirm ;;
454   user_invite)   user_invite ;;
455   user_login)    user_login ;;
456   user_logout)   user_logout ;;
457   user_update)   user_update ;;
458   user_recover)
459     :;;
460   user_disable)
461     :;;
462 esac
463
464 export USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
465        USER_EXPIRE USER_DEVICES USER_FUTUREUSE
466
467
468 w_user_update(){
469   if [ ! "$USER_ID" ]; then
470     cat <<-EOF
471         [div #user_update .nouser
472         This page can only be used by registered users
473         ]
474         EOF
475   else
476     cat <<-EOF
477         [form #user_update method=POST
478           [hidden "uid" "$USER_ID"]
479           [p .username Logged in as $USER_NAME]
480           [input type=password name=oldpw placeholder="Current Passphrase"]
481           [input type=password name=pw placeholder="New Passphrase" pattern=".{6,}"]
482           [input type=password name=pwconfirm placeholder="Confirm New Passphrase" pattern=".{6,}"]
483           [submit "action" "user_update" Update Passphrase]
484         ]
485         EOF
486   fi
487 }
488
489 w_user_register_disabled(){  # TRANSLATION
490   cat <<-EOF
491         [div #user_register .disabled
492         User Registration is disabled.
493         ]
494         EOF
495 }
496 w_user_register_sendmail(){  # TRANSLATION
497   cat <<-EOF
498         [form #user_register .registeremail method=POST
499           [p We will send an activation mail to your email address.
500             You can continue the signup process when you click on the
501             activation link in this email.]
502           [input type=email name=email placeholder="Email"]
503           [submit "action" "user_register" Sign Up]
504         ]
505         EOF
506 }
507 w_user_register_direct(){  # TRANSLATION
508   cat <<-EOF
509         [form #user_register .registername method=POST
510           [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]
511           [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
512           [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
513           [submit "action" "user_register" Sign Up]
514         ]
515         EOF
516 }
517
518 w_user_register(){
519   if [ "$(GET user_confirm)" ]; then
520     w_user_confirm
521   elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
522     w_user_register_disabled
523   elif [ "$USER_REQUIREEMAIL" = true ]; then
524     w_user_register_sendmail
525   elif [ "$USER_REQUIREEMAIL" != true ]; then
526     w_user_register_direct
527   fi
528 }
529
530 w_user_confirm_proceed(){  # TRANSLATION
531   cat <<-EOF
532         [form #user_confirm method=POST
533           [input type=hidden name=uid value="${uid}"]
534           [input type=hidden name=signature value="${signature}"]
535           $([ "$EMAIL" != '\' ] && printf \
536             '[input disabled=disabled value="%s" placeholder="Email"]' "$(UNSTRING "$EMAIL" |HTML)"
537           )
538           [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]
539           [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
540           [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
541           [submit "action" "user_confirm" Finish Registration]
542         ]
543         EOF
544 }
545 w_user_confirm_expired(){  # TRANSLATION
546   cat <<-EOF
547         [div #user_confirm .expired
548           [p This activation link is not valid anymore.]
549         ]
550         EOF
551 }
552 w_user_confirm_invalid(){  # TRANSLATION
553   cat <<-EOF
554         [div #user_confirm .invalid
555           [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.]
556         ]
557         EOF
558 }
559
560 w_user_confirm(){
561   local UID_    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
562   local user_confirm="$(GET user_confirm)"
563   local uid="${user_confirm% *}" signature="${user_confirm#* }"
564
565   if [ "$signature" = "$(session_mac "$uid")" ]; then
566     read -r UID_        UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE <<-EOF
567         $(grep "^${uid} " "$user_db")
568         EOF
569     if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
570       w_user_confirm_proceed
571     else
572       w_user_confirm_expired
573     fi
574   else
575     w_user_confirm_invalid
576   fi
577 }
578
579 w_user_invite_email(){  # TRANSLATION
580   cat <<-EOF
581         [form #user_invite method=POST
582           [input placeholder="Email Recipient" name=email autocomplete=off]
583           [textarea name="message" placeholder="Message to recipient" . ]
584           [submit "action" "user_invite" Send Invitation]
585         ]
586         EOF
587 }
588 w_user_invite_link(){  # TRANSLATION
589   cat <<-EOF
590         [div #user_invite .link
591           [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.]
592           [a href="$(HTML "$invlink")" . $(HTML "$invlink")]
593
594           [p [a href="#" . Set up another account]]
595         ]
596         EOF
597 }
598 w_user_invite_deny(){  # TRANSLATION
599   cat <<-EOF
600         [div #user_invite .notallowed
601           Only registered users may send an invitation to another user.
602         ]
603         EOF
604 }
605
606 w_user_invite(){
607   local uid invlink
608
609   if [ "$(GET user_confirm)" ]; then
610     w_user_confirm
611   elif [ "$USER_ID" -a "$SENDMAIL" ]; then
612     w_user_invite_email
613   elif [ "$USER_ID" ]; then
614     uid="$(timeid)"
615     new_user "$uid" status=pending expire="$((_DATE + USER_CONFIRMEXPIRE))"
616     invlink="${SCHEMA}://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
617     debug "New Invitation Link: $invlink"
618     w_user_invite_link
619   else
620     w_user_invite_deny
621   fi
622 }
623
624 w_user_login_logon(){  # TRANSLATION
625   cat <<-EOF
626         [form #user_login .login method=POST
627           [input name=uname placeholder="Username or Email" autocomplete=off]
628           [input type=password name=pw placeholder="Passphrase"]
629           [submit "action" "user_login" Login]
630         ]
631         EOF
632 }
633 w_user_login_logoff(){  # TRANSLATION
634   cat <<-EOF
635         [form #user_login .logout method=POST
636           [p Logged in as [span . $(HTML ${USER_NAME})]]
637           [submit "action" "user_logout" Logout]
638         ]
639         EOF
640 }
641
642 w_user_login(){
643   if [ ! "$USER_ID" ]; then
644     w_user_login_logon
645   elif [ "$USER_ID" ]; then
646     w_user_login_logoff
647   fi
648 }