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
32 user_db="${_DATA}/users.db"
38 USER_ID='' USER_NAME='' USER_STATUS='' USER_EMAIL='' USER_PWSALT=''
39 USER_PWHASH='' USER_EXPIRE='' USER_DEVICES='' USER_FUTUREUSE=''
42 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
43 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
44 elif [ "$user" -a -f "$user_db" -a -r "$user_db" ]; then
45 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
46 USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
47 $(grep "^${user} " "${user_db}")
50 if [ "$USER_ID" -a "${USER_EXPIRE:-0}" -gt "$_DATE" ]; then
51 USER_NAME="$(UNSTRING "$USER_NAME")"
52 USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
53 USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
54 unset USER_PWSALT USER_PWHASH
62 # internal function for user update
63 local uid="$1" uname status email pwsalt pwhash expire devices futureuse
64 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
67 for arg in "$@"; do case $arg in
68 uname=*) uname="${arg#*=}";;
69 status=*) status="${arg#*=}";;
70 email=*) email="${arg#*=}";;
71 password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
72 expire=*) expire="${arg#*=}";;
73 devices=*) devices="${arg#*=}";;
76 if LOCK "$user_db"; then
77 while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
79 if [ "$UID" = "$uid" ]; then
80 printf '%s %s %s %s %s %s %i %s %s\n' \
81 "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
82 "${status:-${status-${STATUS}}${status+\\}}" \
83 "${email:-${email-${EMAIL}}${email+\\}}" \
84 "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
85 "${expire:-$((_DATE + 86400 * 730))}" \
86 "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
88 elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
89 # omit expired invitations from output
92 printf '%s %s %s %s %s %s %i %s %s\n' \
93 "$UID" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
94 "$EXPIRE" "$DEVICES" "$FUTUREUSE"
96 done <"$user_db" >"${user_db}.$$"
97 mv -- "${user_db}.$$" "$user_db"
105 local user="${1:-$(timeid)}"
108 if LOCK "$user_db"; then
109 if grep -q "^${user} " "$user_db"; then
113 printf '%s \\ %s \\ \\ \\ %i \\ \\\n' \
114 "$user" "pending" "$(( $_DATE + 86400 ))" >>"$user_db"
119 if [ $# -eq 0 ]; then
122 elif update_user "$user" "$@"; then
131 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
137 /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
143 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
145 # W3C recommended email regex
146 # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
147 /^[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;
152 local uname="$(STRING "$1")"
153 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
154 [ -f "$user_db" -a -r "$user_db" ] \
155 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
156 [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
162 local email="$(STRING "$1")"
163 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
164 [ -f "$user_db" -a -r "$user_db" ] \
165 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
166 [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
172 local salt="$1" secret="$2" hash
173 hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
174 printf '%s\n' "${hash%% *}"
178 # reserve account, send registration mail
179 # preliminary uid, expiration, signature
180 local uid="$(timeid)"
181 local uname="$(POST uname |user_checkname)"
182 local email="$(POST email |user_checkemail)"
183 local pwsalt="$(randomid)"
184 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
186 if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
187 REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
190 if [ "$USER_REQUIREEMAIL" = true ]; then
191 if [ ! "email" ]; then
192 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
193 elif user_emailexist "$email"; then
194 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
195 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"; then
196 debug "Sending Activation Link:" \
197 "https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
198 sendmail -t -f "$MAILFROM" <<-EOF
201 Subject: Your account registration at ${HTTP_HOST%:*}
203 Someone tried to sign up for a user account using this email address.
205 You can activate your account using this link:
207 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
209 This registration link will expire after 24 hours.
211 If you did not request an account at ${HTTP_HOST%:*}, then someone else
212 probably entered your email address by accident. In this case you shoud
213 simply ignore this message and we will remove your email address from
214 our database within the next day.
216 This is an automatic email. Any direct reply will not be received.
217 Your Account Registration Robot.
219 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
221 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
224 elif [ "$USER_REQUIREEMAIL" != true ]; then
225 if [ ! "$uname" ]; then
226 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
227 elif user_nameexist "$uname"; then
228 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
229 elif [ ! "$pw" ]; then
230 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
231 elif [ "$pw" != "$pwconfirm" ]; then
232 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
233 elif new_user "$uid" uname="$uname" status=active email="$email" password="$pw" expire="$((_DATE + 86400 * 730))"; then
235 SESSION_BIND user_id "$uid"
237 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
239 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
245 local uid="$(timeid)"
246 local email="$(POST email |user_checkemail)"
247 local message="$(POST message)"
249 if [ ! "email" ]; then
250 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
251 elif user_emailexist "$email"; then
252 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
253 elif new_user "$uid" status=pending email="$email" expire="$((_DATE + 86400))"; then
254 debug "Sending Invitation Link:" \
255 "https://${HTTP_HOST}${BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
256 sendmail -t -f "$MAILFROM" <<-EOF
259 Subject: You have been invited to ${HTTP_HOST%:*}
261 ${USER_NAME:-Someone} has offered an invitation to this email address.
265 You can create your account using this link:
267 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
269 This registration link will expire after 24 hours.
271 If you do not know what this is about, then someone else probably
272 entered your email address by accident. In this case you shoud
273 simply ignore this message and we will remove your email address from
274 our database within the next day.
276 This is an automatic email. Any direct reply will not be received.
277 Your Account Registration Robot.
279 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
281 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
288 local uid="$(POST uid |checkid || printf invalid)"
289 local signature="$(POST signature)"
290 local uname="$(POST uname |user_checkname)"
291 local pwsalt="$(randomid)"
292 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
296 if [ "$signature" != "$(session_mac "$uid")" ]; then
297 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
298 elif [ ! "$uname" ]; then
299 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
300 elif user_nameexist "$uname"; then
301 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
302 elif [ ! "$pw" ]; then
303 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
304 elif [ "$pw" != "$pwconfirm" ]; then
305 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
306 elif [ "$USER_STATUS" != pending -o \! "$USER_EXPIRE" -gt "$_DATE" ]; then
307 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
308 elif update_user "$USER_ID" uname="$uname" status=active password="$pw"; then
310 SESSION_BIND user_id "$USER_ID"
311 REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
313 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
319 # keep logged in - device cookie?
320 # initialize new session!
321 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
322 local uname="$(POST uname |STRING)" pw="$(POST pw)"
324 [ -f "$user_db" -a -r "$user_db" ] \
325 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
326 if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
327 if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
329 SESSION_BIND user_id "$UID"
330 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
334 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
338 # destroy cookie, destroy session
342 SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
343 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
358 read_user "$(SESSION_VAR user_id)"
359 [ "$USER_STATUS" -a "$USER_STATUS" != active ] && eval $UNSET_USER
361 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
362 user_register) user_register ;;
363 user_confirm) user_confirm ;;
364 user_invite) user_invite ;;
365 user_login) user_login ;;
366 user_logout) user_logout ;;
376 if [ "$(GET user_confirm)" ]; then
378 elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
380 [div #user_register .disabled
381 User Registration is disabled.
384 elif [ "$USER_REQUIREEMAIL" = true ]; then
386 [form #user_register .registeremail method=POST
387 [p We will send an activation mail to your email address.
388 You can continue the signup process when you click on the
389 activation link in this email.]
390 [input type=email name=email placeholder="Email"]
391 [submit "action" "user_register" Sign Up]
394 elif [ "$USER_REQUIREEMAIL" != true ]; then
396 [form #user_register .registername method=POST
397 [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]
398 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
399 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
400 [submit "action" "user_register" Sign Up]
407 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
408 local user_confirm="$(GET user_confirm)"
409 local uid="${user_confirm% *}" signature="${user_confirm#* }"
411 if [ "$signature" = "$(session_mac "$uid")" ]; then
412 read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
413 $(grep "^${uid} " "$user_db")
415 if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
417 [form #user_confirm method=POST
418 [input type=hidden name=uid value="${uid}"]
419 [input type=hidden name=signature value="${signature}"]
420 [input disabled=disabled value="$(HTML "$EMAIL")"]
421 [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]
422 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
423 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
424 [submit "action" "user_confirm" Finish Registration]
429 [div #user_confirm .expired
430 [p This activation link is not valid anymore.]
436 [div #user_confirm .invalid
437 [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.]
444 if [ "$(GET user_confirm)" ]; then
446 elif [ "$USER_ID" ]; then
448 [form #user_invite method=POST
449 [input placeholder="Email Recipient" name=email autocomplete=off]
450 [textarea name="message" placeholder="Message to recipient" . ]
451 [submit "action" "user_invite" Send Invitation]
456 [div #user_invite .notallowed
457 Only registered users may send an invitation to another user.
464 if [ ! "$USER_ID" ]; then
466 [form #user_login .login method=POST
467 [input name=uname placeholder="Username or Email" autocomplete=off]
468 [input type=password name=pw placeholder="Passphrase"]
469 [submit "action" "user_login" Login]
472 elif [ "$USER_ID" ]; then
474 [form #user_login .logout method=POST
475 [p Logged in as [span . $(HTML ${USER_NAME})]]
476 [submit "action" "user_logout" Logout]