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%:*}}"
15 user_db="${_DATA}/users.db"
16 unset USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
17 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
20 # UID UNAME STATUS (pending|active|deleted) EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
22 user_id="$(SESSION_VAR user_id)"
23 if [ "$user_id" -a -f "$user_db" -a -r "$user_db" ]; then
24 read -r USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
25 USER_EXPIRE USER_DEVICES USER_FUTUREUSE <<-EOF
26 $(grep "^${user_id} " "$user_db")
28 if [ "$USER_ID" -a "$USER_STATUS" = active -a "$USER_EXPIRE" -gt "$_DATE" ]; then
29 USER_NAME="$(UNSTRING "$USER_NAME")"
30 USER_EMAIL="$(UNSTRING "$USER_EMAIL")"
31 USER_DEVICES="$(UNSTRING "$USER_DEVICES")"
32 unset USER_PWSALT USER_PWHASH
34 unset USER_ID USER_NAME USER_STATUS USER_EMAIL USER_PWSALT USER_PWHASH \
35 USER_EXPIRE USER_DEVICES USER_FUTUREUSE
41 # internal function for user update
42 local uid="$1" uname status email pwsalt pwhash expire devices futureuse
43 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
46 for arg in "$@"; do case $arg in
47 uname=*) uname="${arg#*=}";;
48 status=*) status="${arg#*=}";;
49 email=*) email="${arg#*=}";;
50 password=*) pwsalt="$(randomid)"; pwhash="$(user_pwhash "$pwsalt" "${arg#*=}")";;
51 devices=*) devices="${arg#*=}";;
54 if LOCK "$user_db"; then
55 while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES \
57 if [ "$UID" = "$uid" ]; then
58 printf '%s %s %s %s %s %s %s %s %s\n' \
59 "$uid" "$(STRING "${uname-$(UNSTRING "$UNAME")}")" \
60 "${status:-${status-${STATUS}}${status+\\}}" \
61 "${email:-${email-${EMAIL}}${email+\\}}" \
62 "${pwsalt:-${PWSALT}}" "${pwhash:-${PWHASH}}" \
63 "$((_DATE + 86400 * 730))" \
64 "$(STRING "${devices-$(UNSTRING "$DEVICES")}")" \
66 elif [ "$STATUS" = pending -a ! "$EXPIRE" -ge "$_DATE" ]; then
67 # omit expired invitations from output
70 printf '%s %s %s %s %s %s %s %s %s\n' \
71 "$UID" "$UNAME" "$STATUS" "$EMAIL" "$PWSALT" "$PWHASH" \
72 "$EXPIRE" "$DEVICES" "$FUTUREUSE"
74 done <"$user_db" >"${user_db}.$$"
75 mv -- "${user_db}.$$" "$user_db"
83 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
89 /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
95 { [ $# -gt 0 ] && printf %s "$*" || cat; } \
97 # W3C recommended email regex
98 # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
99 /^[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;
104 local uname="$(STRING "$1")"
105 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
106 [ -f "$user_db" -a -r "$user_db" ] \
107 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
108 [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
114 local email="$(STRING "$1")"
115 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
116 [ -f "$user_db" -a -r "$user_db" ] \
117 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
118 [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
124 local salt="$1" secret="$2" hash
125 hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
126 printf '%s\n' "${hash%% *}"
130 # reserve account, send registration mail
131 # preliminary uid, expiration, signature
132 local uid="$(timeid)"
133 local uname="$(POST uname |user_checkname)"
134 local email="$(POST email |user_checkemail)"
135 local pwsalt="$(randomid)"
136 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
138 if [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
139 REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
142 if [ "$USER_REQUIREEMAIL" = true ]; then
143 if [ ! "email" ]; then
144 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
145 elif user_emailexist "$email"; then
146 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
147 elif LOCK "$user_db"; then
148 printf '%s \\ pending %s \\ \\ %i \\ \\\n' \
149 "$uid" "$(STRING "$email")" "$(( $_DATE + 86400 ))" \
153 debug "Sending Activation Link:" \
154 "https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
155 sendmail -t -f "$MAILFROM" <<-EOF
158 Subject: Your account registration at ${HTTP_HOST%:*}
160 Someone tried to sign up for a user account using this email address.
162 You can activate your account using this link:
164 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
166 This registration link will expire after 24 hours.
168 If you did not request an account at ${HTTP_HOST%:*}, then someone else
169 probably entered your email address by accident. In this case you shoud
170 simply ignore this message and we will remove your email address from
171 our database within the next day.
173 This is an automatic email. Any direct reply will not be received.
174 Your Account Registration Robot.
176 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
178 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
181 elif [ "$USER_REQUIREEMAIL" != true ]; then
182 if [ ! "$uname" ]; then
183 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
184 elif user_nameexist "$uname"; then
185 REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
186 elif [ ! "$pw" ]; then
187 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
188 elif [ "$pw" != "$pwconfirm" ]; then
189 REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
190 elif LOCK "$user_db"; then
191 printf '%s %s active %s %s %s %i \\ \\\n' \
192 "$uid" "$(STRING "$uname")" "$(STRING "$email")" \
193 "$pwsalt" "$(user_pwhash "$pwsalt" "$pw")" \
194 "$(( $_DATE + 86400 * 730 ))" \
199 SESSION_BIND user_id "$uid"
201 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
203 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
209 local uid="$(timeid)"
210 local email="$(POST email |user_checkemail)"
211 local message="$(POST message)"
213 if [ ! "email" ]; then
214 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
215 elif user_emailexist "$email"; then
216 REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
217 elif LOCK "$user_db"; then
218 printf '%s \\ pending %s \\ \\ %i \\ \\\n' \
219 "$uid" "$(STRING "$email")" "$(( $_DATE + 86400 ))" \
222 debug "Sending Invitation Link:" \
223 "https://${HTTP_HOST}${BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")"
224 sendmail -t -f "$MAILFROM" <<-EOF
227 Subject: You have been invited to ${HTTP_HOST%:*}
229 ${USER_NAME:-Someone} has offered an invitation to this email address.
233 You can create your account using this link:
235 https://${HTTP_HOST}${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
237 This registration link will expire after 24 hours.
239 If you do not know what this is about, then someone else probably
240 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.
244 This is an automatic email. Any direct reply will not be received.
245 Your Account Registration Robot.
247 REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
249 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
255 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
256 local uid="$(POST uid |checkid)"
257 local signature="$(POST signature)"
258 local uname="$(POST uname |user_checkname)"
259 local pwsalt="$(randomid)"
260 local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
262 read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
263 $(grep "^${uid:-invalid} " "$user_db")
266 if [ "$signature" != "$(session_mac "$uid")" ]; then
267 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
268 elif [ ! "$uname" ]; then
269 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
270 elif user_nameexist "$uname"; then
271 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
272 elif [ ! "$pw" ]; then
273 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
274 elif [ "$pw" != "$pwconfirm" ]; then
275 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
276 elif [ "$STATUS" != pending -o \! "$EXPIRE" -gt "$_DATE" ]; then
277 REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
278 elif user_update_user "$UID" uname="$uname" status=active password="$pw"; then
280 SESSION_BIND user_id "$UID"
281 REDIRECT "${_BASE}${PATH_INFO}?user_register=confirm#USER_REGISTER_CONFIRM"
283 REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
289 # keep logged in - device cookie?
290 # initialize new session!
291 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
292 local uname="$(POST uname |STRING)" pw="$(POST pw)"
294 [ -f "$user_db" -a -r "$user_db" ] \
295 && while read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE; do
296 if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
297 if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
299 SESSION_BIND user_id "$UID"
300 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
304 REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
308 # destroy cookie, destroy session
312 SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
313 REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
328 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
329 user_register) user_register ;;
330 user_confirm) user_confirm ;;
331 user_invite) user_invite ;;
332 user_login) user_login ;;
333 user_logout) user_logout ;;
343 if [ "$(GET user_confirm)" ]; then
345 elif [ "$USER_REGISTRATION" != true -a -s "$user_db" ]; then
347 [div #user_register .disabled
348 User Registration is disabled.
351 elif [ "$USER_REQUIREEMAIL" = true ]; then
353 [form #user_register .registeremail method=POST
354 [p We will send an activation mail to your email address.
355 You can continue the signup process when you click on the
356 activation link in this email.]
357 [input type=email name=email placeholder="Email"]
358 [submit "action" "user_register" Sign Up]
361 elif [ "$USER_REQUIREEMAIL" != true ]; then
363 [form #user_register .registername method=POST
364 [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]
365 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
366 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
367 [submit "action" "user_register" Sign Up]
374 local UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE
375 local user_confirm="$(GET user_confirm)"
376 local uid="${user_confirm% *}" signature="${user_confirm#* }"
378 if [ "$signature" = "$(session_mac "$uid")" ]; then
379 read -r UID UNAME STATUS EMAIL PWSALT PWHASH EXPIRE DEVICES FUTUREUSE <<-EOF
380 $(grep "^${uid} " "$user_db")
382 if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
384 [form #user_confirm method=POST
385 [input type=hidden name=uid value="${uid}"]
386 [input type=hidden name=signature value="${signature}"]
387 [input disabled=disabled value="$(HTML "$EMAIL")"]
388 [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]
389 [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
390 [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
391 [submit "action" "user_confirm" Finish Registration]
396 [div #user_confirm .expired
397 [p This activation link is not valid anymore.]
403 [div #user_confirm .invalid
404 [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.]
411 if [ "$(GET user_confirm)" ]; then
413 elif [ "$USER_ID" ]; then
415 [form #user_invite method=POST
416 [input placeholder="Email Recipient" name=email autocomplete=off]
417 [textarea name="message" placeholder="Message to recipient" . ]
418 [submit "action" "user_invite" Send Invitation]
423 [div #user_invite .notallowed
424 Only registered users may send an invitation to another user.
431 if [ ! "$USER_ID" ]; then
433 [form #user_login .login method=POST
434 [input name=uname placeholder="Username or Email" autocomplete=off]
435 [input type=password name=pw placeholder="Passphrase"]
436 [submit "action" "user_login" Login]
439 elif [ "$USER_ID" ]; then
441 [form #user_login .logout method=POST
442 [p Logged in as [span . $(HTML ${USER_NAME})]]
443 [submit "action" "user_logout" Logout]