3 [ -n "$include_users" ] && return 0
6 . "${_EXEC}/cgilite/session.sh"
7 . "${_EXEC}/cgilite/storage.sh"
9 USER_REGISTRATION="${USER_REGISTRATION:-true}"
10 USER_REQUIREEMAIL="${USER_REQUIREEMAIL:-true}"
12 HTTP_HOST="$(HEADER Host)"
13 MAILFROM="${MAILDOMAIN:-noreply@${HTTP_HOST%:*}}"
16 # UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
17 # (pending|active|deleted)
21 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
22 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
26 USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
27 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
33 user_db="${user_db:-${_DATA}/users.db}"
39 USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
40 USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
43 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
44 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
45 elif [ "$user" -a -f "$user_db" -a -r "$user_db" ]; then
46 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
47 USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
48 $(grep "^${user} " "${user_db}")
51 if [ "$USER_ID" -a "${USER_EXPIRE:-0}" -gt "$_DATE" ]; then
52 USER_NAME="$(UNSTRING "$USER_NAME")"
53 USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
54 USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
55 unset USER_PWSALT USER_PWHASH
63 # internal function for user update
64 local uid="$1" uname status email pwsalt pwhash expire devices futureuse
65 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
68 for arg in "$@"; do case $arg in
69 uname=*) uname="${arg#*=}";;
70 status=*) status="${arg#*=}";;
71 email=*) email="${arg#*=}";;
72 password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
73 expire=*) expire="${arg#*=}";;
74 devices=*) devices="${arg#*=}";;
77 if LOCK "$user_db"; then
78 while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
80 if [ "$UID" = "$uid" ]; then
81 printf '%s %s %s %s %s %s %i %s %s\n' \
82 "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
83 "${status:-${status-${STATUS}}${status+\\}}" \
84 "${email:-${email-${EMAIL}}${email+\\}}" \
85 "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
86 "${expire:-$((_DATE + 86400 * 730))}" \
87 "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
89 elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
90 # omit expired invitations from output
93 printf '%s %s %s %s %s %s %i %s %s\n' \
94 "$UID" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
95 "$EXPIRE" "$DEVICES" "$FUTUREUSE"
97 done <"$user_db" >"${user_db}.$$"
98 mv -- "${user_db}.$$" "$user_db"
106 local user="${1:-$(timeid)}"
109 if LOCK "$user_db"; then
110 if grep -q "^${user} " "$user_db"; then
114 printf '%s \\ %s \\ \\ \\ %i \\ \\\n' \
115 "$user" "pending" "$(( $_DATE + 86400 ))" >>"$user_db"
120 if [ $# -eq 0 ]; then
123 elif update_user "$user" "$@"; then
135 if [ ! "$USER_IDMAP" ]; then
137 USER_IDMAP="${USER_IDMAP}${USER_ID} ${USER_NAME}${BR}"
140 if [ "$uid" -a "$USER_IDMAP" != "${USER_IDMAP##*${uid} }" ]; then
141 ret="${USER_IDMAP##*${uid} }"; ret="${ret%%${BR}*}";
144 elif [ "$uid" ]; then
147 printf '%s' "$USER_IDMAP"
153 local name="$(STRING "$1")" ret
154 [ "$USER_IDMAP" ] || user_idmap >/dev/null
156 if [ "${name%\\}" -a "$USER_IDMAP" != "${USER_IDMAP% ${name}${BR}*}" ]; then
157 ret="${USER_IDMAP% ${name}${BR}*}"; ret="${ret##*${BR}}"
166 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
172 /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
178 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
180 # W3C recommended email regex
181 # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
182 /^[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 local uname="$(STRING "$1")"
188 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
189 [ -f "$user_db" -a -r "$user_db" ] \
190 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
191 [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
197 local email="$(STRING "$1")"
198 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
199 [ -f "$user_db" -a -r "$user_db" ] \
200 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
201 [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
207 local salt="$1" secret="$2" hash
208 hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
209 printf '%s\n' "${hash%% *}"
213 # reserve account, send registration mail
214 # preliminary uid, expiration, signature
215 local uid="$(timeid)"
216 local uname="$(POST uname |user_checkname)"
217 local email="$(POST email |user_checkemail)"
218 local pwsalt="$(randomid)"
219 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
221 if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
222 REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
225 if [ "$USER_REQUIREEMAIL" = true ]; then
226 if [ ! "email" ]; then
227 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
228 elif user_emailexist "$email"; then
229 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
230 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"; then
231 debug "Sending Activation Link:" \
232 "https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
233 sendmail -t -f "$MAILFROM" <<-EOF
236 Subject: Your account registration at ${HTTP_HOST%:*}
238 Someone tried to sign up for a user account using this email address.
240 You can activate your account using this link:
242 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
244 This registration link will expire after 24 hours.
246 If you did not request an account at ${HTTP_HOST%:*}, then someone else
247 probably entered your email address by accident. In this case you shoud
248 simply ignore this message and we will remove your email address from
249 our database within the next day.
251 This is an automatic email. Any direct reply will not be received.
252 Your Account Registration Robot.
254 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
256 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
259 elif [ "$USER_REQUIREEMAIL" != true ]; then
260 if [ ! "$uname" ]; then
261 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
262 elif user_nameexist "$uname"; then
263 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
264 elif [ ! "$pw" ]; then
265 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
266 elif [ "$pw" != "$pwconfirm" ]; then
267 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
268 elif new_user "$uid" uname="$uname" status=active email="$email" password="$pw" expire="$((_DATE + 86400 * 730))"; then
270 SESSION_BIND user_id "$uid"
272 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
274 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
280 local uid="$(timeid)"
281 local email="$(POST email |user_checkemail)"
282 local message="$(POST message)"
284 if [ ! "email" ]; then
285 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
286 elif user_emailexist "$email"; then
287 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
288 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"; then
289 debug "Sending Invitation Link:" \
290 "https://${HTTP_HOST}${BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
291 sendmail -t -f "$MAILFROM" <<-EOF
294 Subject: You have been invited to ${HTTP_HOST%:*}
296 ${USER_NAME:-Someone} has offered an invitation to this email address.
300 You can create your account using this link:
302 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
304 This registration link will expire after 24 hours.
306 If you do not know what this is about, then someone else probably
307 entered your email address by accident. In this case you shoud
308 simply ignore this message and we will remove your email address from
309 our database within the next day.
311 This is an automatic email. Any direct reply will not be received.
312 Your Account Registration Robot.
314 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
316 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
323 local uid="$(POST uid |checkid || printf invalid)"
324 local signature="$(POST signature)"
325 local uname="$(POST uname |user_checkname)"
326 local pwsalt="$(randomid)"
327 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
331 if [ "$signature" != "$(session_mac "$uid")" ]; then
332 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
333 elif [ ! "$uname" ]; then
334 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
335 elif user_nameexist "$uname"; then
336 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
337 elif [ ! "$pw" ]; then
338 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
339 elif [ "$pw" != "$pwconfirm" ]; then
340 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
341 elif [ "$USER_STATUS" != pending -o \! "$USER_EXPIRE" -gt "$_DATE" ]; then
342 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
343 elif update_user "$USER_ID" uname="$uname" status=active password="$pw"; then
345 SESSION_BIND user_id "$USER_ID"
346 REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
348 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
354 # keep logged in - device cookie?
355 # initialize new session!
356 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
357 local uname="$(POST uname |STRING)" pw="$(POST pw)"
359 [ -f "$user_db" -a -r "$user_db" ] \
360 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
361 if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
362 if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
364 SESSION_BIND user_id "$UID"
365 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
369 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
373 # destroy cookie, destroy session
377 SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
378 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
393 read_user "$(SESSION_VAR user_id)"
394 [ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
396 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
397 user_register) user_register ;;
398 user_confirm) user_confirm ;;
399 user_invite) user_invite ;;
400 user_login) user_login ;;
401 user_logout) user_logout ;;
411 if [ "$(GET user_confirm)" ]; then
413 elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
415 [div #user_register .disabled
416 User Registration is disabled.
419 elif [ "$USER_REQUIREEMAIL" = true ]; then
421 [form #user_register .registeremail method=POST
422 [p We will send an activation mail to your email address.
423 You can continue the signup process when you click on the
424 activation link in this email.]
425 [input type=email name=email placeholder="Email"]
426 [submit "action" "user_register" Sign Up]
429 elif [ "$USER_REQUIREEMAIL" != true ]; then
431 [form #user_register .registername method=POST
432 [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]
433 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
434 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
435 [submit "action" "user_register" Sign Up]
442 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
443 local user_confirm="$(GET user_confirm)"
444 local uid="${user_confirm% *}" signature="${user_confirm#* }"
446 if [ "$signature" = "$(session_mac "$uid")" ]; then
447 read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
448 $(grep "^${uid} " "$user_db")
450 if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
452 [form #user_confirm method=POST
453 [input type=hidden name=uid value="${uid}"]
454 [input type=hidden name=signature value="${signature}"]
455 [input disabled=disabled value="$(HTML "$EMAIL")"]
456 [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]
457 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
458 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
459 [submit "action" "user_confirm" Finish Registration]
464 [div #user_confirm .expired
465 [p This activation link is not valid anymore.]
471 [div #user_confirm .invalid
472 [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.]
479 if [ "$(GET user_confirm)" ]; then
481 elif [ "$USER_ID" ]; then
483 [form #user_invite method=POST
484 [input placeholder="Email Recipient" name=email autocomplete=off]
485 [textarea name="message" placeholder="Message to recipient" . ]
486 [submit "action" "user_invite" Send Invitation]
491 [div #user_invite .notallowed
492 Only registered users may send an invitation to another user.
499 if [ ! "$USER_ID" ]; then
501 [form #user_login .login method=POST
502 [input name=uname placeholder="Username or Email" autocomplete=off]
503 [input type=password name=pw placeholder="Passphrase"]
504 [submit "action" "user_login" Login]
507 elif [ "$USER_ID" ]; then
509 [form #user_login .logout method=POST
510 [p Logged in as [span . $(HTML ${USER_NAME})]]
511 [submit "action" "user_logout" Logout]