3 [ -n "$include_users" ] && return 0
6 . "${_EXEC}/cgilite/session.sh"
7 . "${_EXEC}/cgilite/storage.sh"
9 SENDMAIL=${SENDMAIL-sendmail}
11 USER_REGISTRATION="${USER_REGISTRATION-true}"
12 USER_REQUIREEMAIL="${USER_REQUIREEMAIL-true}"
13 USER_ACCOUNTPAGE="${USER_ACCOUNTPAGE}"
15 MAILFROM="${MAILDOMAIN-noreply@${HTTP_HOST%:*}}"
17 HTTP_HOST="$(HEADER Host)"
20 # UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
21 # (pending|active|deleted)
25 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
26 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
30 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
31 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
37 user_db="${user_db:-${_DATA}/users.db}"
43 USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
44 USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
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}")
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
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
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#*=}";;
81 if LOCK "$user_db"; then
82 while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
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")}")" \
93 elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
94 # omit expired invitations from output
97 printf '%s %s %s %s %s %s %i %s %s\n' \
98 "$UID" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
99 "$EXPIRE" "$DEVICES" "$FUTUREUSE"
101 done <"$user_db" >"${user_db}.$$"
102 mv -- "${user_db}.$$" "$user_db"
110 local user="${1:-$(timeid)}"
113 if LOCK "$user_db"; then
114 if grep -q "^${user} " "$user_db"; then
118 printf '%s \\ %s \\ \\ \\ %i \\ \\\n' \
119 "$user" "pending" "$(( $_DATE + 86400 ))" >>"$user_db"
124 if [ $# -eq 0 ]; then
127 elif update_user "$user" "$@"; then
139 if [ ! "$USER_IDMAP" ]; then
141 USER_IDMAP="${USER_IDMAP}${USER_ID} ${USER_NAME}${BR}"
144 if [ "$uid" -a "$USER_IDMAP" != "${USER_IDMAP##*${uid} }" ]; then
145 ret="${USER_IDMAP##*${uid} }"; ret="${ret%%${BR}*}";
148 elif [ "$uid" ]; then
151 printf '%s' "$USER_IDMAP"
157 local name="$(STRING "$1")" ret
158 [ "$USER_IDMAP" ] || user_idmap >/dev/null
160 if [ "${name%\\}" -a "$USER_IDMAP" != "${USER_IDMAP% ${name}${BR}*}" ]; then
161 ret="${USER_IDMAP% ${name}${BR}*}"; ret="${ret##*${BR}}"
170 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
176 /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
182 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
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;
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
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
211 local salt="$1" secret="$2" hash
212 hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
213 printf '%s\n' "${hash%% *}"
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)"
225 if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
226 REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
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
240 Subject: Your account registration at ${HTTP_HOST%:*}
242 Someone tried to sign up for a user account using this email address.
244 You can activate your account using this link:
246 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
248 This registration link will expire after 24 hours.
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.
255 This is an automatic email. Any direct reply will not be received.
256 Your Account Registration Robot.
258 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
260 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
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
274 SESSION_BIND user_id "$uid"
276 if [ "$USER_ACCOUNTPAGE" ]; then
277 REDIRECT "${USER_ACCOUNTPAGE}"
279 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
282 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
288 local uid="$(timeid)"
289 local email="$(POST email |user_checkemail)"
290 local message="$(POST message)"
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
302 Subject: You have been invited to ${HTTP_HOST%:*}
304 ${USER_NAME:-Someone} has offered an invitation to this email address.
308 You can create your account using this link:
310 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
312 This registration link will expire after 24 hours.
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.
319 This is an automatic email. Any direct reply will not be received.
320 Your Account Registration Robot.
322 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
324 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
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)"
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
353 SESSION_BIND user_id "$USER_ID"
354 if [ "$USER_ACCOUNTPAGE" ]; then
355 REDIRECT "${USER_ACCOUNTPAGE}"
357 REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
360 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
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)"
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
376 SESSION_BIND user_id "$UID"
377 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
381 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
385 # destroy cookie, destroy session
389 SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
390 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
405 read_user "$(SESSION_VAR user_id)"
406 [ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
408 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
409 user_register) user_register ;;
410 user_confirm) user_confirm ;;
411 user_invite) user_invite ;;
412 user_login) user_login ;;
413 user_logout) user_logout ;;
423 if [ "$(GET user_confirm)" ]; then
425 elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
427 [div #user_register .disabled
428 User Registration is disabled.
431 elif [ "$USER_REQUIREEMAIL" = true ]; then
433 [form #user_register .registeremail method=POST
434 [p We will send an activation mail to your email address.
435 You can continue the signup process when you click on the
436 activation link in this email.]
437 [input type=email name=email placeholder="Email"]
438 [submit "action" "user_register" Sign Up]
441 elif [ "$USER_REQUIREEMAIL" != true ]; then
443 [form #user_register .registername method=POST
444 [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]
445 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
446 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
447 [submit "action" "user_register" Sign Up]
454 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
455 local user_confirm="$(GET user_confirm)"
456 local uid="${user_confirm% *}" signature="${user_confirm#* }"
458 if [ "$signature" = "$(session_mac "$uid")" ]; then
459 read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
460 $(grep "^${uid} " "$user_db")
462 if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
464 [form #user_confirm method=POST
465 [input type=hidden name=uid value="${uid}"]
466 [input type=hidden name=signature value="${signature}"]
467 $([ "$EMAIL" != '\' ] && printf \
468 '[input disabled=disabled value="%s" placeholder="Email"]' "$(UNSTRING "$EMAIL" |HTML)"
470 [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]
471 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
472 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
473 [submit "action" "user_confirm" Finish Registration]
478 [div #user_confirm .expired
479 [p This activation link is not valid anymore.]
485 [div #user_confirm .invalid
486 [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.]
493 if [ "$(GET user_confirm)" ]; then
495 elif [ "$USER_ID" -a "$SENDMAIL" ]; then
497 [form #user_invite method=POST
498 [input placeholder="Email Recipient" name=email autocomplete=off]
499 [textarea name="message" placeholder="Message to recipient" . ]
500 [submit "action" "user_invite" Send Invitation]
503 elif [ "$USER_ID" ]; then
505 new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"
507 [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.]
508 [p . $(HTML "https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")" |debug)]
510 [p [a href="#" . Set up another account]]
514 [div #user_invite .notallowed
515 Only registered users may send an invitation to another user.
522 if [ ! "$USER_ID" ]; then
524 [form #user_login .login method=POST
525 [input name=uname placeholder="Username or Email" autocomplete=off]
526 [input type=password name=pw placeholder="Passphrase"]
527 [submit "action" "user_login" Login]
530 elif [ "$USER_ID" ]; then
532 [form #user_login .logout method=POST
533 [p Logged in as [span . $(HTML ${USER_NAME})]]
534 [submit "action" "user_logout" Logout]