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