IsaacKerson commited on
Commit
7927df4
1 Parent(s): 08d1848

add login page and components

Browse files
Files changed (5) hide show
  1. app.py +1 -0
  2. config.yaml +7 -0
  3. pages/login.py +32 -0
  4. pages/streamlit_authenticator.py +265 -0
  5. requirements.txt +4 -1
app.py CHANGED
@@ -16,6 +16,7 @@ st.markdown("# Quiz Maker")
16
  # Add all your application here
17
  app.add_page("Quiz", quiz.app)
18
  app.add_page("Join", join.app)
 
19
  app.add_page("Upload", upload.app)
20
  app.add_page("View", view.app)
21
 
16
  # Add all your application here
17
  app.add_page("Quiz", quiz.app)
18
  app.add_page("Join", join.app)
19
+ app.add_page("Login", login.app)
20
  app.add_page("Upload", upload.app)
21
  app.add_page("View", view.app)
22
 
config.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ cookie:
2
+ name: 'some_cookie_name'
3
+ key: 'some_signature_key'
4
+ credentials:
5
+ names: ['John Smith', 'Rebecca Briggs']
6
+ usernames: ['jsmith', 'rbriggs']
7
+ passwords: ['123', '456']
pages/login.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit_authenticator as stauth
3
+ from yaml.loader import SafeLoader
4
+
5
+ def app():
6
+ with open('../config.yaml') as file:
7
+ config = yaml.load(file, Loader=SafeLoader)
8
+
9
+ hashed_passwords = stauth.Hasher(config['credentials']['passwords']).generate()
10
+
11
+ authenticator = stauth.Authenticate(
12
+ config['credentials']['names'],
13
+ config['credentials']['usernames'],
14
+ hashed_passwords,
15
+ config['cookie']['name'],
16
+ config['cookie']['key'],
17
+ cookie_expiry_days=30
18
+ )
19
+
20
+ # Alternatively you use st.session_state['name'] and
21
+ # st.session_state['authentication_status'] to access the name and
22
+ # authentication_status.
23
+
24
+ stauth.authenticator.login('Login', 'main')
25
+
26
+ if st.session_state['authentication_status']:
27
+ st.write('Welcome *%s*' % (st.session_state['name']))
28
+ st.title('Some content')
29
+ elif st.session_state['authentication_status'] == False:
30
+ st.error('Username/password is incorrect')
31
+ elif st.session_state['authentication_status'] == None:
32
+ st.warning('Please enter your username and password')
pages/streamlit_authenticator.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import jwt
2
+ import yaml
3
+ import bcrypt
4
+ import streamlit as st
5
+ from yaml.loader import SafeLoader
6
+ from datetime import datetime, timedelta
7
+ import extra_streamlit_components as stx
8
+
9
+ # _RELEASE = True
10
+
11
+ class Hasher:
12
+ def __init__(self, passwords):
13
+ """Create a new instance of "Hasher".
14
+ Parameters
15
+ ----------
16
+ passwords: list
17
+ The list of plain text passwords to be hashed.
18
+ Returns
19
+ -------
20
+ list
21
+ The list of hashed passwords.
22
+ """
23
+ self.passwords = passwords
24
+
25
+ def hash(self, password):
26
+ """
27
+ Parameters
28
+ ----------
29
+ password: str
30
+ The plain text password to be hashed.
31
+ Returns
32
+ -------
33
+ str
34
+ The hashed password.
35
+ """
36
+ return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
37
+
38
+ def generate(self):
39
+ """
40
+ Returns
41
+ -------
42
+ list
43
+ The list of hashed passwords.
44
+ """
45
+ hashedpw = []
46
+
47
+ for password in self.passwords:
48
+ hashedpw.append(self.hash(password))
49
+ return hashedpw
50
+
51
+ class Authenticate:
52
+ def __init__(self, names, usernames, passwords, cookie_name, key, cookie_expiry_days=30):
53
+ """Create a new instance of "Authenticate".
54
+ Parameters
55
+ ----------
56
+ names: list
57
+ The list of names of users.
58
+ usernames: list
59
+ The list of usernames in the same order as names.
60
+ passwords: list
61
+ The list of hashed passwords in the same order as names.
62
+ cookie_name: str
63
+ The name of the JWT cookie stored on the client's browser for passwordless reauthentication.
64
+ key: str
65
+ The key to be used for hashing the signature of the JWT cookie.
66
+ cookie_expiry_days: int
67
+ The number of days before the cookie expires on the client's browser.
68
+ Returns
69
+ -------
70
+ str
71
+ Name of authenticated user.
72
+ boolean
73
+ The status of authentication, None: no credentials entered, False: incorrect credentials, True: correct credentials.
74
+ str
75
+ Username of authenticated user.
76
+ """
77
+ self.names = names
78
+ self.usernames = usernames
79
+ self.passwords = passwords
80
+ self.cookie_name = cookie_name
81
+ self.key = key
82
+ self.cookie_expiry_days = cookie_expiry_days
83
+ self.cookie_manager = stx.CookieManager()
84
+
85
+ if 'name' not in st.session_state:
86
+ st.session_state['name'] = None
87
+ if 'authentication_status' not in st.session_state:
88
+ st.session_state['authentication_status'] = None
89
+ if 'username' not in st.session_state:
90
+ st.session_state['username'] = None
91
+ if 'logout' not in st.session_state:
92
+ st.session_state['logout'] = None
93
+
94
+ def token_encode(self):
95
+ """
96
+ Returns
97
+ -------
98
+ str
99
+ The JWT cookie for passwordless reauthentication.
100
+ """
101
+ return jwt.encode({'name':st.session_state['name'],
102
+ 'username':st.session_state['username'],
103
+ 'exp_date':self.exp_date}, self.key, algorithm='HS256')
104
+
105
+ def token_decode(self):
106
+ """
107
+ Returns
108
+ -------
109
+ str
110
+ The decoded JWT cookie for passwordless reauthentication.
111
+ """
112
+ try:
113
+ return jwt.decode(self.token, self.key, algorithms=['HS256'])
114
+ except:
115
+ return False
116
+
117
+ def exp_date(self):
118
+ """
119
+ Returns
120
+ -------
121
+ str
122
+ The JWT cookie's expiry timestamp in Unix epoch.
123
+ """
124
+ return (datetime.utcnow() + timedelta(days=self.cookie_expiry_days)).timestamp()
125
+
126
+ def check_pw(self):
127
+ """
128
+ Returns
129
+ -------
130
+ boolean
131
+ The validation state for the input password by comparing it to the hashed password on disk.
132
+ """
133
+ return bcrypt.checkpw(self.password.encode(), self.passwords[self.index].encode())
134
+
135
+ def login(self, form_name, location='main'):
136
+ """Create a new instance of "authenticate".
137
+ Parameters
138
+ ----------
139
+ form_name: str
140
+ The rendered name of the login form.
141
+ location: str
142
+ The location of the login form i.e. main or sidebar.
143
+ Returns
144
+ -------
145
+ str
146
+ Name of authenticated user.
147
+ boolean
148
+ The status of authentication, None: no credentials entered, False: incorrect credentials, True: correct credentials.
149
+ str
150
+ Username of authenticated user.
151
+ """
152
+ if location not in ['main', 'sidebar']:
153
+ raise ValueError("Location must be one of 'main' or 'sidebar'")
154
+
155
+ if not st.session_state['authentication_status']:
156
+ self.token = self.cookie_manager.get(self.cookie_name)
157
+ if self.token is not None:
158
+ self.token = self.token_decode()
159
+ if self.token is not False:
160
+ if not st.session_state['logout']:
161
+ if self.token['exp_date'] > datetime.utcnow().timestamp():
162
+ st.session_state['name'] = self.token['name']
163
+ st.session_state['authentication_status'] = True
164
+ st.session_state['username'] = self.token['username']
165
+
166
+ if st.session_state['authentication_status'] != True:
167
+ if location == 'main':
168
+ login_form = st.form('Login')
169
+ elif location == 'sidebar':
170
+ login_form = st.sidebar.form('Login')
171
+
172
+ login_form.subheader(form_name)
173
+ self.username = login_form.text_input('Username')
174
+ st.session_state['username'] = self.username
175
+ self.password = login_form.text_input('Password', type='password')
176
+
177
+ if login_form.form_submit_button('Login'):
178
+ self.index = None
179
+ for i in range(0, len(self.usernames)):
180
+ if self.usernames[i] == self.username:
181
+ self.index = i
182
+ if self.index is not None:
183
+ try:
184
+ if self.check_pw():
185
+ st.session_state['name'] = self.names[self.index]
186
+ self.exp_date = self.exp_date()
187
+ self.token = self.token_encode()
188
+ self.cookie_manager.set(self.cookie_name, self.token,
189
+ expires_at=datetime.now() + timedelta(days=self.cookie_expiry_days))
190
+ st.session_state['authentication_status'] = True
191
+ else:
192
+ st.session_state['authentication_status'] = False
193
+ except Exception as e:
194
+ print(e)
195
+ else:
196
+ st.session_state['authentication_status'] = False
197
+
198
+ return st.session_state['name'], st.session_state['authentication_status'], st.session_state['username']
199
+
200
+ def logout(self, button_name, location='main'):
201
+ """Creates a logout button.
202
+ Parameters
203
+ ----------
204
+ button_name: str
205
+ The rendered name of the logout button.
206
+ location: str
207
+ The location of the logout button i.e. main or sidebar.
208
+ """
209
+ if location not in ['main', 'sidebar']:
210
+ raise ValueError("Location must be one of 'main' or 'sidebar'")
211
+
212
+ if location == 'main':
213
+ if st.button(button_name):
214
+ self.cookie_manager.delete(self.cookie_name)
215
+ st.session_state['logout'] = True
216
+ st.session_state['name'] = None
217
+ st.session_state['username'] = None
218
+ st.session_state['authentication_status'] = None
219
+ elif location == 'sidebar':
220
+ if st.sidebar.button(button_name):
221
+ self.cookie_manager.delete(self.cookie_name)
222
+ st.session_state['logout'] = True
223
+ st.session_state['name'] = None
224
+ st.session_state['username'] = None
225
+ st.session_state['authentication_status'] = None
226
+
227
+ # if not _RELEASE:
228
+ # with open('../config.yaml') as file:
229
+ # config = yaml.load(file, Loader=SafeLoader)
230
+
231
+ # hashed_passwords = Hasher(config['credentials']['passwords']).generate()
232
+
233
+ # authenticator = Authenticate(
234
+ # config['credentials']['names'],
235
+ # config['credentials']['usernames'],
236
+ # hashed_passwords,
237
+ # config['cookie']['name'],
238
+ # config['cookie']['key'],
239
+ # cookie_expiry_days=30
240
+ # )
241
+
242
+ # name, authentication_status, username = authenticator.login('Login', 'main')
243
+
244
+ # if authentication_status:
245
+ # authenticator.logout('Logout', 'main')
246
+ # st.write('Welcome *%s*' % (name))
247
+ # st.title('Some content')
248
+ # elif authentication_status == False:
249
+ # st.error('Username/password is incorrect')
250
+ # elif authentication_status == None:
251
+ # st.warning('Please enter your username and password')
252
+
253
+ # Alternatively you use st.session_state['name'] and
254
+ # st.session_state['authentication_status'] to access the name and
255
+ # authentication_status.
256
+
257
+ #authenticator.login('Login', 'main')
258
+
259
+ #if st.session_state['authentication_status']:
260
+ # st.write('Welcome *%s*' % (st.session_state['name']))
261
+ # st.title('Some content')
262
+ #elif st.session_state['authentication_status'] == False:
263
+ # st.error('Username/password is incorrect')
264
+ #elif st.session_state['authentication_status'] == None:
265
+ # st.warning('Please enter your username and password')
requirements.txt CHANGED
@@ -1,3 +1,6 @@
1
  pysqlite3
2
  openpyxl
3
- streamlit-authenticator
 
 
 
1
  pysqlite3
2
  openpyxl
3
+ extra-streamlit-components
4
+ bcrypt
5
+ PyJWT
6
+ PyYAML