]> git.plutz.net Git - rawnet/blob - users.sh
Squashed 'cgilite/' changes from 7459611..af27357
[rawnet] / 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 USER_REGISTRATION="${USER_REGISTRATION:-true}"
10 USER_REQUIREEMAIL="${USER_REQUIREEMAIL:-true}"
11
12 HTTP_HOST="$(HEADER Host)"
13 MAILFROM="${MAILDOMAIN:-noreply@${HTTP_HOST%:*}}"
14
15 user_db="${_DATA}/users.db"
16 unset USER_ID USER_NAME USER_EMAIL
17
18 # USER DB
19 # UID   UNAME   STATUS (pending|active|deleted) EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
20
21 user_init(){
22    local user_id="$(SESSION_VAR user_id)"
23    local UID    UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
24    [ "$user_id" ] \
25    && read -r UID       UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE <<-EOF
26         $(grep "^${user_id}     " "$user_db")
27         EOF
28    [ "$STATUS" -a "$EXPIRE" ] \
29    && if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" ]; then
30      USER_ID="$UID"
31      USER_NAME="$(UNSTRING "$UNAME")"
32      USER_EMAIL="$(UNSTRING "$EMAIL")"
33    fi
34 }
35
36 user_checkname(){
37   { [ $# -gt 0 ] && printf %s "$*" || cat; } \
38   | sed -nE '
39     :X; $!{N;bX;}
40     s;[ \t\r\n]+; ;g;
41     s;^ ;;; s; $;;;
42     /@/d;
43     /^[a-zA-Z][a-zA-Z0-9 -~]{2,127}$/!d;
44     p;
45     '
46 }
47
48 user_checkemail(){
49   { [ $# -gt 0 ] && printf %s "$*" || cat; } \
50   | sed -nE '
51     # W3C recommended email regex
52     # https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
53     /^[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;
54     '
55 }
56
57 user_nameexist(){
58   local uname="$(STRING "$1")"
59   local UID     UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
60   [ -f "$user_db" -a -r "$user_db" ] \
61   && while read -r UID  UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE; do
62     [ "$EXPIRE" -gt "$_DATE" -a "$UNAME" = "$uname" ] && return 0
63   done <"$user_db"
64   return 1
65 }
66
67 user_emailexist(){
68   local email="$(STRING "$1")"
69   local UID     UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
70   [ -f "$user_db" -a -r "$user_db" ] \
71   && while read -r UID  UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE; do
72     [ "$EXPIRE" -gt "$_DATE" -a "$EMAIL" = "$email" ] && return 0
73   done <"$user_db"
74   return 1
75 }
76
77 user_pwhash(){
78   local salt="$1" secret="$2" hash
79   hash="$(printf '%s\n%s\n' "$secret" "$salt" |sha256sum)"
80   printf '%s\n' "${hash%% *}"
81 }
82
83 user_register(){
84   # reserve account, send registration mail
85   # preliminary uid, expiration, signature
86   local uid="$(timeid)"
87   local uname="$(POST uname |user_checkname)"
88   local email="$(POST email |user_checkemail)"
89   local pwsalt="$(randomid)"
90   local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
91
92   if [ "$USER_REGISTRATION" != true ]; then
93     REDIRECT "${_BASE}${PATH_INFO}#ERROR_REGISTRATION_DISABLED"
94   fi
95
96   if   [ "$USER_REQUIREEMAIL" = true ]; then
97     if [ ! "email" ]; then
98       REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_INVALID"
99     elif user_emailexist "$email"; then
100       REDIRECT "${_BASE}${PATH_INFO}#ERROR_EMAIL_EXISTS"
101     elif LOCK "$user_db"; then
102       printf '%s        \\      pending %s      \\      \\      %i      \\      \\\n' \
103              "$uid" "$(STRING "$email")" "$(( $_DATE + 86400 ))" \
104              >>"$user_db"
105       RELEASE "$user_db"
106       sendmail -t -f "$MAILFROM" <<-EOF
107         From: ${MAILFROM}
108         To: ${email}
109         Subject: Your account registration at ${HTTP_HOST%:*}
110
111         Someone tried to sign up for a user account using this email address.
112
113         You can activate your account using this link:
114
115             https://${HTTP_HOST%:*}/${_BASE}${PATH_INFO}?user_confirm=${uid}+$(session_mac "$uid")
116
117         This registration link will expire after 24 hours.
118
119         If you did not request an account at ${HTTP_HOST%:*}, then someone else
120         probably entered your email address by accident. In this case you shoud
121         simply ignore this message and we will remove your email address from
122         our database within the next day.
123
124         This is an automatic email. Any direct reply will not be received.
125         Your Account Registration Robot.
126         EOF
127       REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
128     else
129       REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
130     fi
131
132   elif [ "$USER_REQUIREEMAIL" != true ]; then
133     if [ ! "$uname" ]; then
134       REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_INVALID"
135     elif user_nameexist "$uname"; then
136       REDIRECT "${_BASE}${PATH_INFO}#ERROR_UNAME_EXISTS"
137     elif [ ! "$pw" ]; then
138       REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_EMPTYTOOSHORT"
139     elif [ "$pw" != "$pwconfirm" ]; then
140       REDIRECT "${_BASE}${PATH_INFO}#ERROR_PW_MISMATCH"
141     elif LOCK "$user_db"; then
142       printf '%s        %s      active  %s      %s      %s      %i      \\      \\\n' \
143              "$uid" "$(STRING "$uname")" "$(STRING "$email")" \
144              "$pwsalt" "$(user_pwhash "$pwsalt" "$pw")" \
145              "$(( $_DATE + 86400 * 730 ))" \
146              >>"$user_db"
147       RELEASE "$user_db"
148
149       SESSION_COOKIE new
150       SESSION_BIND user_id "$uid"
151
152       REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
153     else
154       REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
155     fi
156   fi
157 }
158
159 user_confirm(){
160   # enable account
161   local UID     UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
162   local uid="$(POST uid |checkid)"
163   local signature="$(POST signature)"
164   local uname="$(POST uname |user_checkname)"
165   local pwsalt="$(randomid)"
166   local pw="$(POST pw |grep -m1 -xE '.{6,}' )" pwconfirm="$(POST pwconfirm)"
167
168   if [ "$signature" != "$(session_mac "$uid")" ]; then
169     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
170   elif [ ! "$uname" ]; then
171     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_INVALID"
172   elif user_nameexist "$uname"; then
173     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_UNAME_EXISTS"
174   elif [ ! "$pw" ]; then
175     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_EMPTYTOOSHORT"
176   elif [ "$pw" != "$pwconfirm" ]; then
177     REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_PW_MISMATCH"
178   elif LOCK "$user_db"; then
179     read -r UID UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE <<-EOF
180         $(grep "^${uid} " "$user_db")
181         EOF
182
183     if [ "$STATUS" != pending -o "$EXPIRE" -le "$_DATE" ]; then
184       RELEASE "$user_db"
185       REDIRECT "${_BASE}${PATH_INFO}?${QUERY_STRING}#ERROR_LINK_INVALID"
186     else
187       printf '%s        %s      active  %s      %s      %s      %i      %s      %s\n' \
188              "$UID" "$(STRING "$uname")" "$EMAIL" \
189              "$pwsalt" "$(user_pwhash "$pwsalt" "$pw")" \
190              "$(( $_DATE + 86400 * 730 ))" "$DEVICES" "$FUTUREUSE" \
191              >"${user_db}.$$"
192       grep -v "^${uid}  " "$user_db" >>"${user_db}.$$"
193       mv "${user_db}.$$" "${user_db}"
194       RELEASE "$user_db"
195
196       SESSION_COOKIE new
197       SESSION_BIND user_id "$UID"
198       REDIRECT "${_BASE}${PATH_INFO}#USER_REGISTER_CONFIRM"
199     fi
200   else
201     REDIRECT "${_BASE}${PATH_INFO}#ERROR_USER_NOLOCK"
202   fi
203 }
204
205 user_login(){
206   # set cookie
207   # keep logged in - device cookie?
208   # initialize new session!
209   local UID     UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
210   local uname="$(POST uname |STRING)" pw="$(POST pw)"
211
212   [ -f "$user_db" -a -r "$user_db" ] \
213   && while read -r UID  UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE; do
214     if [ "$UNAME" = "$uname" -o "$EMAIL" = "$uname" ]; then
215       if [ "$STATUS" = active -a "$EXPIRE" -gt "$_DATE" -a "$PWHASH" = "$(user_pwhash "$PWSALT" "$pw")" ]; then
216         SESSION_COOKIE new
217         SESSION_BIND user_id "$UID"
218         REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_IN"
219       fi
220     fi
221   done <"$user_db"
222   REDIRECT "${_BASE}${PATH_INFO}#ERROR_INVALID_LOGIN"
223 }
224
225 user_logout(){
226   # destroy cookie, destroy session
227   # keep device cookie
228   new_session
229   SESSION_COOKIE new
230   SET_COOKIE 0 user_id="" Path="/${_BASE#/}" SameSite=Strict HttpOnly
231   REDIRECT "${_BASE}${PATH_INFO}#USER_LOGGED_OUT"
232 }
233
234 user_update(){
235   # passphrase, email
236   :
237 }
238 user_recover(){
239   # send recover link
240   :
241 }
242 user_disable(){
243   :
244 }
245
246 user_init
247
248 [ "$REQUEST_METHOD" = POST ] && case "$(POST action)" in
249   user_register) user_register ;;
250   user_confirm)  user_confirm ;;
251   user_login)    user_login ;;
252   user_logout)   user_logout ;;
253   user_update)
254     :;;
255   user_recover)
256     :;;
257   user_disable)
258     :;;
259 esac
260
261 w_user_register(){
262   if [ "$(GET user_confirm)" ]; then
263     w_user_confirm
264   elif [ "$USER_REGISTRATION" != true ]; then
265     cat <<-EOF
266         [div #user_register .disabled
267         User Registration is disabled.
268         ]
269         EOF
270   elif [ "$USER_REQUIREEMAIL" = true ]; then
271     cat <<-EOF
272         [form #user_register .registeremail method=POST
273           [p We will send an activation mail to your email address.
274             You can continue the signup process when you click on the
275             activation link in this email.]
276           [input type=email name=email placeholder="Email"]
277           [submit "action" "user_register" Sign Up]
278         ]
279         EOF
280   elif [ "$USER_REQUIREEMAIL" != true ]; then
281     cat <<-EOF
282         [form #user_register .registername method=POST
283           [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]
284           [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
285           [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
286           [submit "action" "user_register" Sign Up]
287         ]
288         EOF
289   fi
290 }
291
292 w_user_confirm(){
293   local UID     UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE
294   local user_confirm="$(GET user_confirm)"
295   local uid="${user_confirm% *}" signature="${user_confirm#* }"
296
297   if [ "$signature" = "$(session_mac "$uid")" ]; then
298     read -r UID UNAME   STATUS  EMAIL   PWSALT  PWHASH  EXPIRE  DEVICES FUTUREUSE <<-EOF
299         $(grep "^${uid} " "$user_db")
300         EOF
301     if [ "$STATUS" = pending -a "$EXPIRE" -gt "$_DATE" ]; then
302       cat <<-EOF
303         [form #user_confirm method=POST
304           [input type=hidden name=uid value="${uid}"]
305           [input type=hidden name=signature value="${signature}"]
306           [input disabled=disabled value="$(HTML "$EMAIL")"]
307           [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]
308           [input type=password name=pw placeholder="Choose Passphrase" pattern=".{6,}"]
309           [input type=password name=pwconfirm placeholder="Confirm Passphrase" pattern=".{6,}"]
310           [submit "action" "user_confirm" Finish Registration]
311         ]
312         EOF
313     else
314       cat <<-EOF
315         [div #user_confirm .expired
316           [p This activation link is not valid anymore.]
317         ]
318         EOF
319     fi
320   else
321     cat <<-EOF
322         [div #user_confirm .invalid
323           [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.]
324         ]
325         EOF
326   fi
327 }
328
329 w_user_login(){
330   if [ ! "$USER_ID" ]; then
331     cat <<-EOF
332         [form #user_login .login method=POST
333           [input name=uname placeholder="Username or Email" autocomplete=off]
334           [input type=password name=pw placeholder="Passphrase"]
335           [submit "action" "user_login" Login]
336         ]
337         EOF
338   elif [ "$USER_ID" ]; then
339     cat <<-EOF
340         [form #user_login .logout method=POST
341           [p Logged in as [span . $(HTML ${USER_NAME})]]
342           [submit "action" "user_logout" Logout]
343         ]
344         EOF
345   fi
346 }