# stuser
**User registration, login and associated ecosystem for Streamlit**
stuser is a package that allows you to create a user registration and
login system for your Streamlit application. It is meant to be a robust
way to allow users to interact with your website securely. It has
pre-defined integrations for databases and email, and specifically defines
methods for use with GCP BigQuery and SendGrid, respectively. It also
allows for user-defined methods to interact with other databases and email
services.
## Installation
[![PyPI](https://img.shields.io/pypi/v/stuser)](https://pypi.org/project/stuser/)
stuser can be installed via pip from [PyPI](https://pypi.org/project/stuser/):
```python
pip install stuser
```
or with the optional dependencies:
```python
pip install stuser[google,sendgrid]
```
## Contact
I would like this package to be as useful as possible, so please feel free
to reach out with any questions, comments or if you would like to
contribute. You can reach me at
[alex.melesko@msquaredds.com](mailto:alex.melesko@msquaredds.com).
## Template
The stuser package ships with a folder called 'template' that contains
example code for how to use the package. The template is a full example
that includes all the steps needed to create a streamlit web app.
- The file main.py is the entry point.
- The file SendPreauthCodes.py creates a tab that allows you to enter
emails and send preauthorization codes.
- The file EmailVerification.py creates a tab that allows you to verify
emails when a user clicks on a link in their email.
- The file Forms.py creates a tab that allows users to register, login,
retrieve a forgotten username (with forgot_username), get a new password
(with forgot_password), update their user info (with update_user_info)
and logout.
Note that many of the inputs to the methods will need to updated to
reflect the specifics of your project. This includes database info, such
as project, dataset, table and column names. It also includes website name
and email in the class instantiation and the verification url in the
register_user method.
Some of the credentials inputs that are predefined here are based on
st.secrets, which is a feature that allows you to store sensitive
information in a separate location when hosting your app on Streamlit.io.
The separate location is a TOML-type file that would have you store
'BIGQUERY' and 'SENDGRID' credentials so they can be accessed here.
## Full Example
### Understand Your Options
- Will you require users to be preauthorized before they can sign up? This
is useful if you want to control who can access your application.
- Do you want users to validate their email address upon registration?
This helps make sure the user actually has access to the email they
are using.
- Will you lock users out after a certain number of failed login
attempts? This is a security feature to prevent brute force attacks.
### Define User Info Storage
The first step is to define where the user info will be stored. You can
define your own storage locations and methods, but if using the
predefined BigQuery option, your dataset and tables can look like this:
![](https://github.com/msquaredds/StUser/blob/main/images/BQTables.png?raw=true)
This assumes that you are using all of the options available
(preauthorization, email validation, and lockout after failed attempts).
The name of the dataset and tables can vary and you can incorporate tables
into different datasets if you like, but the type of info you will store
should be the same. The _register tables are for registering when a
preauthorization code is required.
Table columns:
- incorrect_attempts:
- username: STRING
- datetime: DATETIME
- incorrect_attempts_register:
- email: STRING
- datetime: DATETIME
- locked_info:
- username: STRING
- locked_time: DATETIME
- unlocked_time: DATETIME
- locked_info_register:
- email: STRING
- locked_time: DATETIME
- preauthorization_codes:
- email: STRING
- code: STRING
- user_credentials:
- username: STRING
- email: STRING
- password: STRING
- datetime: DATETIME
- email_code: STRING
- email_verified: BOOLEAN
### Error Handling
The package is designed to handle known errors by saving them to
session states (st.session_state). It will categorize the error as either
'user_errors' or 'dev_errors'. These categories are dictionaries where
the set of keys are the form names (such as 'login') and the values are
the error messages. You can access these errors by using the following
syntax. Each form also has a display_error input that will automatically
display any errors above the form (after it is run) and below the form
(while it is being created).
```python
from stuser import ErrorHandling as sterr
sterr.display_error('dev_errors', 'login')
```
### Send Preauthorization Codes
If preauthorization is required, you will need to send the preauthorized
users an email with a code that they can use to register. That way someone
cannot just brute force attempt to register with emails they think would
work. This function sends an email with the code and saves the email/code
combo to a database. It should be run separately from your streamlit app.
Verification is the class that handles the verification process, both the
preauthorization codes and email verification.
Notes:
- You can have multiple emails in a list to send to multiple users at
the same time.
- bq_creds (in code_store_args) should be a dictionary with the
credentials for a BigQuery service account. You can add a service
account at IAM & Admin -> Service Accounts. It should be permissioned
to BigQuery Data Editor, BigQuery Job User and BIgQuery Read Session
User. The fields you should have in bq_creds are: type, project_id,
private_key_id, private_key, client_email, client_id, auth_uri,
token_uri, auth_provider_x509_cert_url, client_x509_cert_url and
universe_domain.
- email_inputs are used in the email body to let the user know where the
email is coming from.
- You will need to have registered with SendGrid and have an API key to
include in email_creds (sendgrid_api_key) - the key is a string.
```python
verifier = stuser.Verification()
verifier.preauthorization_code(
email=["test_user1@gmail.com",
"test_user2@gmail.com"],
code_store_function='bigquery',
code_store_args={
'bq_creds': {bq_creds},
'project': 'project', # project name (string)
'dataset': 'test_credentials', # whatever you called your dataset
'table_name': 'preauthorization_codes', # whatever you called your table
'email_col': 'email', # whatever you called your email column
'code_col': 'code'}, # whatever you called your code column
email_function='sendgrid',
email_inputs={
'website_name': 'PyPI',
'website_email': 'hello@pypi.com'},
email_creds={'sendgrid_api_key': 'sendgrid_api_key'})
```
### Pull Existing Users, Emails and Preauthorized Users
The existing usernames and emails are used to make sure that a new user
is not trying to register with a username or email that already exists.
The preauthorized users are used to make sure that only certain users
can register. Preauthorization is optional.
The existing usernames and emails must be loaded as lists to session
states, as that is how they are accessed by the package. It allows these
session states to be updated once a new user is added, so that subsequent
adds will take into account the new users. The preauthorized users are
also loaded as a list to a session state, since they can then be removed
once the user has registered.
Below, we rely on the BQTools class to pull the usernames. This class is
used internally in the package, but can be useful for pulling BigQuery
data in general.
```python
import streamlit as st
import stuser
db_engine = stuser.BQTools()
usernames_indicator, saved_auth_usernames = (
db_engine.pull_full_column_bigquery(
bq_creds ={bq_creds},
project = 'project', # project name (string)
dataset = 'test_credentials', # whatever you called your dataset
table_name = 'user_credentials', # whatever you called your table
target_col = 'username')) # whatever you called your username column
if usernames_indicator == 'dev_errors':
st.error(saved_auth_usernames)
auth_usernames = []
elif usernames_indicator == 'user_errors':
st.error("No usernames found")
auth_usernames = []
else:
auth_usernames = list(saved_auth_usernames.values)
if 'stuser_usernames' not in st.session_state:
st.session_state['stuser_usernames'] = auth_usernames
```
The same pattern can be used to pull the emails and preauthorized users.
### Define the Forms Object
Now that all the pre-work is done, we can instantiate the Forms.
Notes:
- The usernames, emails and preauthorized session state names should match
those used above.
- The email and save_pull inputs can be input here to reduce the number of
arguments needed in the Forms. This can only be done if using a
predefined type (sendgrid and/or bigquery). As long as the email and
save_pull inputs are the same throughout, they will not need to be
repeated. However, some additional inputs may be needed in the
individual widgets. For example, we don't have the table or columns
defined in save_pull_args here since those usually vary by Form. Note
that if any arguments are entered here and in the Forms, those in the
Forms will override these.
- If you do not want to use the data saving or data pulling functions or
email functions, you can ignore those inputs here. Similarly, if you
want to use those functions in some places but not others, you can
ignore them here and then input them in the individual widgets.
```python
try:
stuser_forms = stuser.Forms(
usernames_session_state='stuser_usernames',
emails_session_state='stuser_emails',
user_credentials_session_state='stuser_user_credentials',
preauthorized_session_state='stuser_preauthorized',
email_function='sendgrid',
email_inputs={
'website_name': 'PyPI',
'website_email': 'hello@pypi.com'},
email_creds={'sendgrid_api_key': 'sendgrid_api_key'},
save_pull_function='bigquery',
save_pull_args={
'bq_creds': {bq_creds},
'project': 'project', # project name (string)
'dataset': 'test_credentials'}) # whatever you called your dataset
except ValueError as e:
# there are only dev errors for class instantiation and they
# wouldn't need to show up ahead of time, just if they occur
# during instantiation
sterr.display_error('dev_errors', 'class_instantiation')
st.stop()
```
### Create a User Registration Form
Now that the Forms object is created, we can use it to create the user
registration form. This form will gather the email, username and password
(and optionally a preauthorization code). Then, optionally depending on
your inputs, it will save the credentials and send an email (which can
optionally have a verification code to verify the email).
Notes:
- For forms, the errors might be displayed after the form is submitted, so
we want them above the form but also below the form if the errors
happen while the form is being created. The final input in
display_error (False below) lets the function know that it is not the
first time this error is potentially being displayed, so it will not
re-show an already displayed error.
- The verification url is where the user will be sent to verify their
email address. This is the only input we need this for the email
section since the rest was defined in the class instantiation. The
email will include the verification url with the email and code as
query parameters.
- cred_save_args, auth_code_pull_args, all_locked_args and
all_incorrect_attempts_args are derived from the save_pull_args in the
class instantiation, but we need to add the table names and columns.
- We use the all_locked_args and all_incorrect_attempts_args to pass in
the arguments for the locking functions and incorrect attempts
functions. This is the most efficient way, but the register_user
function has additional variables that allow for more granular control
if you want to save or pull differently for each step of the process.
For example, if you wanted to save and pull in a different way than
we have defined, you could do that with the additional variables.
```python
stuser_forms.register_user(
'main',
preauthorization=True,
verify_email=True,
email_inputs={'verification_url': 'verification_url'}, # whatever your verification url is
cred_save_args={'table_name': 'user_credentials'},
auth_code_pull_args={
'table_name': 'preauthorization_codes', # whatever you called your table
'email_col': 'email', # whatever you called your email column
'auth_code_col': 'code'}, # whatever you called your authorization code column
incorrect_attempts=10,
locked_hours=24,
all_locked_args={
'table_name': 'locked_info_register', # whatever you called your table
'email_col': 'email', # whatever you called your email column
'locked_time_col': 'locked_time'}, # whatever you called your locked time column
all_incorrect_attempts_args= {
'table_name': 'incorrect_attempts_register', # whatever you called your table
'email_col': 'email', # whatever you called your email column
'datetime_col': 'datetime'}) # whatever you called your datetime column
```
![](https://github.com/msquaredds/StUser/blob/main/images/RegisterUser.JPG?raw=true)
### Verify User Email
If you have chosen to require email verification (verify_email=True in
register_user), you will need to create a webpage that can handle the
verification. This is a simple example of how you might do that.
Note that in email_code_pull_function, you are pulling the email code that
was saved in register_user. Therefore, it should look at whatever table
you saved to there. If using 'bigquery', that is the same table where your
credentials are stored. The verified_store_function is where you will save
whether the email is verified. Here we save it to the credentials table,
which makes it easier to check when a user is logging in.
The KeyError below is for handling when the website does not have the
query parameters in the url as expected.
```python
verifier = stuser.Verification()
try:
verifier.verify_email(
email_code_pull_function='bigquery',
email_code_pull_args={
'bq_creds': {bq_creds},
'project': 'project',
'dataset': 'test_credentials',
'table_name': 'user_credentials',
'email_col': 'email',
'email_code_col': 'email_code'},
verified_store_function='bigquery',
verified_store_args={
'bq_creds': {bq_creds},
'project': 'project',
'dataset': 'test_credentials',
'table_name': 'user_credentials',
'email_col': 'email',
'verified_col': 'email_verified',
'datetime_col': 'datetime'})
# let the user know if there's a key error and they don't have the
# correct URL parameters
except KeyError as ke:
st.error("The expected email and authorization code are not "
"present. Please make sure you use the link from "
"the email you were sent.")
except Exception as e:
st.error(e)
if ('stuser' in st.session_state and 'email_verified' in
st.session_state.stuser and st.session_state.stuser[
'email_verified']):
st.success("Email Verified!\n\n"
"You can now login and use the website.")
elif ('stuser' in st.session_state and 'email_verified' in
st.session_state.stuser and not st.session_state.stuser[
'email_verified']):
st.error("Email Code incorrect, please try again or contact your "
"administrator.")
```
### Login
Now that we have a user, we can create a login form. This will ask for the
user's username and password, checking that they are correct (and
that the email is verified if required). If the user has too many failed
login attempts, they will be locked out for a certain amount of time.
The first step is to check whether the user is already authorized. If they
are, we can skip the login form and go straight to the main page. We have
the authorization check as separate, so that it can be used on each page
without having to call the login form if it isn't necessary.
Notes:
- password_pull_args, all_locked_args and all_incorrect_attempts_args are
derived from the save_pull_args in the class instantiation, but we
need to add the table names and columns.
- We use the all_locked_args and all_incorrect_attempts_args to pass in
the arguments for the locking functions and incorrect attempts
functions. This is the most efficient way, but the login
function has additional variables that allow for more granular control
if you want to save or pull differently for each step of the process.
For example, if you wanted to save and pull in a different way than
we have defined, you could do that with the additional variables.
```python
if not stuser_forms.check_authentication_status():
stuser_forms.login(
location='main',
check_email_verification=True,
password_pull_args={
'table_name': 'user_credentials',
'username_col': 'username',
'password_col': 'password',
'email_verification_col': 'email_verified'},
incorrect_attempts=10,
locked_hours=24,
all_locked_args={
'table_name': 'locked_info',
'username_col': 'username',
'locked_time_col': 'locked_time',
'unlocked_time_col': 'unlocked_time'},
all_incorrect_attempts_args={
'table_name': 'incorrect_attempts',
'username_col': 'username',
'datetime_col': 'datetime'})
```
![](https://github.com/msquaredds/StUser/blob/main/images/Login.JPG?raw=true)
### Forgot Username
If a user forgets their username, they can enter their email address and
receive an email with their username.
Notes:
- username_pull_args is derived from the save_pull_args in the class
instantiation, but we need to add the table names and columns.
- The email inputs were all handled in the class instantiation, so we
don't need to add them here.
```python
stuser_forms.forgot_username(
location='main',
username_pull_args={
'table_name': 'user_credentials',
'email_col': 'email',
'username_col': 'username'})
```
![](https://github.com/msquaredds/StUser/blob/main/images/ForgotUsername.JPG?raw=true)
### Forgot Password
If a user forgets their password, they can enter their username and
receive an email with a new, secure password.
Notes:
- username_pull_args and password_store_args are derived from the
save_pull_args in the class instantiation, but we need to add the
table names and columns.
- The email inputs were all handled in the class instantiation, so we
don't need to add them here.
```python
stuser_forms.forgot_password(
location='main',
username_pull_args={
'table_name': 'user_credentials',
'email_col': 'email',
'username_col': 'username'},
password_store_args={
'table_name': 'user_credentials',
'username_col': 'username',
'password_col': 'password',
'datetime_col': 'datetime'})
```
![](https://github.com/msquaredds/StUser/blob/main/images/ForgotPassword.JPG?raw=true)
### Update User Info
If a user wants to update their username, password or email, they can do
so here. Usually, they will have to be logged in first to access this
form.
Notes:
- info_pull_args and info_store_args are derived from the save_pull_args
in the class instantiation, but we need to add the table names and
columns.
- The email inputs were all handled in the class instantiation, so we
don't need to add them here.
- store_new_info is a string or list of strings that tells us which of the
new info to store in a session state. So in our example, only an
updated email would be put into a session state, whereas an updated
username or password would not.
```python
if stuser_forms.check_authentication_status():
stuser_forms.update_user_info(
location='main',
info_pull_args={
'table_name': 'user_credentials',
'col_map': {'email': 'email',
'username': 'username',
'password': 'password'}},
info_store_args={
'table_name': 'user_credentials',
'col_map': {'email': 'email',
'username': 'username',
'password': 'password',
'datetime': 'datetime'}},
store_new_info='email')
```
![](https://github.com/msquaredds/StUser/blob/main/images/UpdateUserInfo.JPG?raw=true)
### Logout
Finally, once a user is logged in, we can log them out.
```python
stuser_forms.logout()
```
![](https://github.com/msquaredds/StUser/blob/main/images/Logout.JPG?raw=true)
## Credit
This package was originally forked from [Streamlit-Authenticator](
https://github.com/mkhorasani/Streamlit-Authenticator) and so some credit
must go to the original author - Mohammad Khorasani. That is why you might
see some additional contributors in the GitHub repo for StUser. Note that
the Streamlit-Authenticator package was under an Apache license at the
time and while some of the outline was used, the code was completely
rewritten.
Raw data
{
"_id": null,
"home_page": null,
"name": "stuser",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": null,
"keywords": "Python, Streamlit, Authentication, Components",
"author": null,
"author_email": "Alex Melesko <alex.melesko@msquaredds.com>",
"download_url": null,
"platform": null,
"description": "# stuser\r\n\r\n**User registration, login and associated ecosystem for Streamlit**\r\n\r\nstuser is a package that allows you to create a user registration and\r\nlogin system for your Streamlit application. It is meant to be a robust\r\nway to allow users to interact with your website securely. It has\r\npre-defined integrations for databases and email, and specifically defines\r\nmethods for use with GCP BigQuery and SendGrid, respectively. It also\r\nallows for user-defined methods to interact with other databases and email\r\nservices.\r\n\r\n## Installation\r\n\r\n[![PyPI](https://img.shields.io/pypi/v/stuser)](https://pypi.org/project/stuser/)\r\n\r\nstuser can be installed via pip from [PyPI](https://pypi.org/project/stuser/):\r\n\r\n```python\r\npip install stuser\r\n```\r\n\r\nor with the optional dependencies:\r\n\r\n```python\r\npip install stuser[google,sendgrid]\r\n```\r\n\r\n## Contact\r\n\r\nI would like this package to be as useful as possible, so please feel free\r\nto reach out with any questions, comments or if you would like to\r\ncontribute. You can reach me at\r\n[alex.melesko@msquaredds.com](mailto:alex.melesko@msquaredds.com).\r\n\r\n## Template\r\n\r\nThe stuser package ships with a folder called 'template' that contains\r\nexample code for how to use the package. The template is a full example\r\nthat includes all the steps needed to create a streamlit web app.\r\n- The file main.py is the entry point.\r\n- The file SendPreauthCodes.py creates a tab that allows you to enter\r\n emails and send preauthorization codes.\r\n- The file EmailVerification.py creates a tab that allows you to verify\r\n emails when a user clicks on a link in their email.\r\n- The file Forms.py creates a tab that allows users to register, login,\r\n retrieve a forgotten username (with forgot_username), get a new password\r\n (with forgot_password), update their user info (with update_user_info)\r\n and logout.\r\n\r\nNote that many of the inputs to the methods will need to updated to\r\nreflect the specifics of your project. This includes database info, such\r\nas project, dataset, table and column names. It also includes website name\r\nand email in the class instantiation and the verification url in the\r\nregister_user method.\r\n\r\nSome of the credentials inputs that are predefined here are based on\r\nst.secrets, which is a feature that allows you to store sensitive\r\ninformation in a separate location when hosting your app on Streamlit.io.\r\nThe separate location is a TOML-type file that would have you store\r\n'BIGQUERY' and 'SENDGRID' credentials so they can be accessed here.\r\n\r\n## Full Example\r\n\r\n### Understand Your Options\r\n\r\n- Will you require users to be preauthorized before they can sign up? This\r\n is useful if you want to control who can access your application.\r\n- Do you want users to validate their email address upon registration?\r\n This helps make sure the user actually has access to the email they\r\n are using.\r\n- Will you lock users out after a certain number of failed login\r\n attempts? This is a security feature to prevent brute force attacks.\r\n\r\n### Define User Info Storage\r\n\r\nThe first step is to define where the user info will be stored. You can\r\ndefine your own storage locations and methods, but if using the\r\npredefined BigQuery option, your dataset and tables can look like this:\r\n\r\n![](https://github.com/msquaredds/StUser/blob/main/images/BQTables.png?raw=true)\r\n\r\nThis assumes that you are using all of the options available\r\n(preauthorization, email validation, and lockout after failed attempts).\r\nThe name of the dataset and tables can vary and you can incorporate tables\r\ninto different datasets if you like, but the type of info you will store\r\nshould be the same. The _register tables are for registering when a\r\npreauthorization code is required.\r\n\r\nTable columns:\r\n- incorrect_attempts:\r\n - username: STRING\r\n - datetime: DATETIME\r\n- incorrect_attempts_register:\r\n - email: STRING\r\n - datetime: DATETIME\r\n- locked_info:\r\n - username: STRING\r\n - locked_time: DATETIME\r\n - unlocked_time: DATETIME\r\n- locked_info_register:\r\n - email: STRING\r\n - locked_time: DATETIME\r\n- preauthorization_codes:\r\n - email: STRING\r\n - code: STRING\r\n- user_credentials:\r\n - username: STRING\r\n - email: STRING\r\n - password: STRING\r\n - datetime: DATETIME\r\n - email_code: STRING\r\n - email_verified: BOOLEAN\r\n\r\n### Error Handling\r\n\r\nThe package is designed to handle known errors by saving them to\r\nsession states (st.session_state). It will categorize the error as either\r\n'user_errors' or 'dev_errors'. These categories are dictionaries where\r\nthe set of keys are the form names (such as 'login') and the values are \r\nthe error messages. You can access these errors by using the following\r\nsyntax. Each form also has a display_error input that will automatically\r\ndisplay any errors above the form (after it is run) and below the form\r\n(while it is being created).\r\n\r\n```python\r\nfrom stuser import ErrorHandling as sterr\r\nsterr.display_error('dev_errors', 'login')\r\n```\r\n\r\n### Send Preauthorization Codes\r\n\r\nIf preauthorization is required, you will need to send the preauthorized\r\nusers an email with a code that they can use to register. That way someone\r\ncannot just brute force attempt to register with emails they think would\r\nwork. This function sends an email with the code and saves the email/code\r\ncombo to a database. It should be run separately from your streamlit app.\r\n\r\nVerification is the class that handles the verification process, both the\r\npreauthorization codes and email verification.\r\n\r\nNotes:\r\n- You can have multiple emails in a list to send to multiple users at\r\n the same time.\r\n- bq_creds (in code_store_args) should be a dictionary with the\r\n credentials for a BigQuery service account. You can add a service\r\n account at IAM & Admin -> Service Accounts. It should be permissioned\r\n to BigQuery Data Editor, BigQuery Job User and BIgQuery Read Session\r\n User. The fields you should have in bq_creds are: type, project_id,\r\n private_key_id, private_key, client_email, client_id, auth_uri,\r\n token_uri, auth_provider_x509_cert_url, client_x509_cert_url and\r\n universe_domain.\r\n- email_inputs are used in the email body to let the user know where the \r\n email is coming from.\r\n- You will need to have registered with SendGrid and have an API key to\r\n include in email_creds (sendgrid_api_key) - the key is a string.\r\n\r\n```python\r\nverifier = stuser.Verification()\r\nverifier.preauthorization_code(\r\n email=[\"test_user1@gmail.com\",\r\n \"test_user2@gmail.com\"],\r\n code_store_function='bigquery',\r\n code_store_args={\r\n 'bq_creds': {bq_creds},\r\n 'project': 'project', # project name (string)\r\n 'dataset': 'test_credentials', # whatever you called your dataset\r\n 'table_name': 'preauthorization_codes', # whatever you called your table\r\n 'email_col': 'email', # whatever you called your email column\r\n 'code_col': 'code'}, # whatever you called your code column\r\n email_function='sendgrid',\r\n email_inputs={\r\n 'website_name': 'PyPI',\r\n 'website_email': 'hello@pypi.com'},\r\n email_creds={'sendgrid_api_key': 'sendgrid_api_key'})\r\n```\r\n\r\n### Pull Existing Users, Emails and Preauthorized Users\r\n\r\nThe existing usernames and emails are used to make sure that a new user\r\nis not trying to register with a username or email that already exists.\r\nThe preauthorized users are used to make sure that only certain users\r\ncan register. Preauthorization is optional.\r\n\r\nThe existing usernames and emails must be loaded as lists to session\r\nstates, as that is how they are accessed by the package. It allows these\r\nsession states to be updated once a new user is added, so that subsequent\r\nadds will take into account the new users. The preauthorized users are\r\nalso loaded as a list to a session state, since they can then be removed\r\nonce the user has registered.\r\n\r\nBelow, we rely on the BQTools class to pull the usernames. This class is\r\nused internally in the package, but can be useful for pulling BigQuery\r\ndata in general.\r\n\r\n```python\r\nimport streamlit as st\r\n\r\nimport stuser\r\n\r\ndb_engine = stuser.BQTools()\r\nusernames_indicator, saved_auth_usernames = (\r\n db_engine.pull_full_column_bigquery(\r\n bq_creds ={bq_creds},\r\n project = 'project', # project name (string)\r\n dataset = 'test_credentials', # whatever you called your dataset\r\n table_name = 'user_credentials', # whatever you called your table\r\n target_col = 'username')) # whatever you called your username column\r\nif usernames_indicator == 'dev_errors':\r\n st.error(saved_auth_usernames)\r\n auth_usernames = []\r\nelif usernames_indicator == 'user_errors':\r\n st.error(\"No usernames found\")\r\n auth_usernames = []\r\nelse:\r\n auth_usernames = list(saved_auth_usernames.values)\r\nif 'stuser_usernames' not in st.session_state:\r\n st.session_state['stuser_usernames'] = auth_usernames\r\n```\r\n\r\nThe same pattern can be used to pull the emails and preauthorized users.\r\n\r\n### Define the Forms Object\r\n\r\nNow that all the pre-work is done, we can instantiate the Forms.\r\n\r\nNotes:\r\n- The usernames, emails and preauthorized session state names should match\r\n those used above.\r\n- The email and save_pull inputs can be input here to reduce the number of\r\n arguments needed in the Forms. This can only be done if using a\r\n predefined type (sendgrid and/or bigquery). As long as the email and\r\n save_pull inputs are the same throughout, they will not need to be\r\n repeated. However, some additional inputs may be needed in the\r\n individual widgets. For example, we don't have the table or columns\r\n defined in save_pull_args here since those usually vary by Form. Note\r\n that if any arguments are entered here and in the Forms, those in the\r\n Forms will override these.\r\n- If you do not want to use the data saving or data pulling functions or\r\n email functions, you can ignore those inputs here. Similarly, if you\r\n want to use those functions in some places but not others, you can\r\n ignore them here and then input them in the individual widgets. \r\n\r\n```python\r\ntry:\r\n stuser_forms = stuser.Forms(\r\n usernames_session_state='stuser_usernames',\r\n emails_session_state='stuser_emails',\r\n user_credentials_session_state='stuser_user_credentials',\r\n preauthorized_session_state='stuser_preauthorized',\r\n email_function='sendgrid',\r\n email_inputs={\r\n 'website_name': 'PyPI',\r\n 'website_email': 'hello@pypi.com'},\r\n email_creds={'sendgrid_api_key': 'sendgrid_api_key'},\r\n save_pull_function='bigquery',\r\n save_pull_args={\r\n 'bq_creds': {bq_creds},\r\n 'project': 'project', # project name (string)\r\n 'dataset': 'test_credentials'}) # whatever you called your dataset\r\nexcept ValueError as e:\r\n # there are only dev errors for class instantiation and they\r\n # wouldn't need to show up ahead of time, just if they occur\r\n # during instantiation\r\n sterr.display_error('dev_errors', 'class_instantiation')\r\n st.stop()\r\n```\r\n\r\n### Create a User Registration Form\r\n\r\nNow that the Forms object is created, we can use it to create the user\r\nregistration form. This form will gather the email, username and password\r\n(and optionally a preauthorization code). Then, optionally depending on\r\nyour inputs, it will save the credentials and send an email (which can\r\noptionally have a verification code to verify the email).\r\n\r\nNotes:\r\n- For forms, the errors might be displayed after the form is submitted, so\r\n we want them above the form but also below the form if the errors\r\n happen while the form is being created. The final input in\r\n display_error (False below) lets the function know that it is not the\r\n first time this error is potentially being displayed, so it will not\r\n re-show an already displayed error.\r\n- The verification url is where the user will be sent to verify their\r\n email address. This is the only input we need this for the email\r\n section since the rest was defined in the class instantiation. The\r\n email will include the verification url with the email and code as\r\n query parameters.\r\n- cred_save_args, auth_code_pull_args, all_locked_args and\r\n all_incorrect_attempts_args are derived from the save_pull_args in the\r\n class instantiation, but we need to add the table names and columns.\r\n- We use the all_locked_args and all_incorrect_attempts_args to pass in\r\n the arguments for the locking functions and incorrect attempts\r\n functions. This is the most efficient way, but the register_user\r\n function has additional variables that allow for more granular control\r\n if you want to save or pull differently for each step of the process.\r\n For example, if you wanted to save and pull in a different way than\r\n we have defined, you could do that with the additional variables.\r\n\r\n```python\r\nstuser_forms.register_user(\r\n 'main',\r\n preauthorization=True,\r\n verify_email=True,\r\n email_inputs={'verification_url': 'verification_url'}, # whatever your verification url is\r\n cred_save_args={'table_name': 'user_credentials'},\r\n auth_code_pull_args={\r\n 'table_name': 'preauthorization_codes', # whatever you called your table\r\n 'email_col': 'email', # whatever you called your email column\r\n 'auth_code_col': 'code'}, # whatever you called your authorization code column\r\n incorrect_attempts=10,\r\n locked_hours=24,\r\n all_locked_args={\r\n 'table_name': 'locked_info_register', # whatever you called your table\r\n 'email_col': 'email', # whatever you called your email column\r\n 'locked_time_col': 'locked_time'}, # whatever you called your locked time column\r\n all_incorrect_attempts_args= {\r\n 'table_name': 'incorrect_attempts_register', # whatever you called your table\r\n 'email_col': 'email', # whatever you called your email column\r\n 'datetime_col': 'datetime'}) # whatever you called your datetime column\r\n```\r\n\r\n![](https://github.com/msquaredds/StUser/blob/main/images/RegisterUser.JPG?raw=true)\r\n\r\n### Verify User Email\r\n\r\nIf you have chosen to require email verification (verify_email=True in\r\nregister_user), you will need to create a webpage that can handle the\r\nverification. This is a simple example of how you might do that.\r\n\r\nNote that in email_code_pull_function, you are pulling the email code that\r\nwas saved in register_user. Therefore, it should look at whatever table\r\nyou saved to there. If using 'bigquery', that is the same table where your\r\ncredentials are stored. The verified_store_function is where you will save\r\nwhether the email is verified. Here we save it to the credentials table,\r\nwhich makes it easier to check when a user is logging in.\r\n\r\nThe KeyError below is for handling when the website does not have the\r\nquery parameters in the url as expected.\r\n\r\n```python\r\nverifier = stuser.Verification()\r\ntry:\r\n verifier.verify_email(\r\n email_code_pull_function='bigquery',\r\n email_code_pull_args={\r\n 'bq_creds': {bq_creds},\r\n 'project': 'project',\r\n 'dataset': 'test_credentials',\r\n 'table_name': 'user_credentials',\r\n 'email_col': 'email',\r\n 'email_code_col': 'email_code'},\r\n verified_store_function='bigquery',\r\n verified_store_args={\r\n 'bq_creds': {bq_creds},\r\n 'project': 'project',\r\n 'dataset': 'test_credentials',\r\n 'table_name': 'user_credentials',\r\n 'email_col': 'email',\r\n 'verified_col': 'email_verified',\r\n 'datetime_col': 'datetime'})\r\n# let the user know if there's a key error and they don't have the\r\n# correct URL parameters\r\nexcept KeyError as ke:\r\n st.error(\"The expected email and authorization code are not \"\r\n \"present. Please make sure you use the link from \"\r\n \"the email you were sent.\")\r\nexcept Exception as e:\r\n st.error(e)\r\n\r\nif ('stuser' in st.session_state and 'email_verified' in\r\n st.session_state.stuser and st.session_state.stuser[\r\n 'email_verified']):\r\n st.success(\"Email Verified!\\n\\n\"\r\n \"You can now login and use the website.\")\r\nelif ('stuser' in st.session_state and 'email_verified' in\r\n st.session_state.stuser and not st.session_state.stuser[\r\n 'email_verified']):\r\n st.error(\"Email Code incorrect, please try again or contact your \"\r\n \"administrator.\")\r\n```\r\n\r\n### Login\r\n\r\nNow that we have a user, we can create a login form. This will ask for the\r\nuser's username and password, checking that they are correct (and\r\nthat the email is verified if required). If the user has too many failed\r\nlogin attempts, they will be locked out for a certain amount of time.\r\n\r\nThe first step is to check whether the user is already authorized. If they\r\nare, we can skip the login form and go straight to the main page. We have\r\nthe authorization check as separate, so that it can be used on each page\r\nwithout having to call the login form if it isn't necessary.\r\n\r\nNotes:\r\n- password_pull_args, all_locked_args and all_incorrect_attempts_args are\r\n derived from the save_pull_args in the class instantiation, but we \r\n need to add the table names and columns.\r\n- We use the all_locked_args and all_incorrect_attempts_args to pass in\r\n the arguments for the locking functions and incorrect attempts\r\n functions. This is the most efficient way, but the login\r\n function has additional variables that allow for more granular control\r\n if you want to save or pull differently for each step of the process.\r\n For example, if you wanted to save and pull in a different way than\r\n we have defined, you could do that with the additional variables.\r\n\r\n```python\r\nif not stuser_forms.check_authentication_status():\r\n stuser_forms.login(\r\n location='main',\r\n check_email_verification=True,\r\n password_pull_args={\r\n 'table_name': 'user_credentials',\r\n 'username_col': 'username',\r\n 'password_col': 'password',\r\n 'email_verification_col': 'email_verified'},\r\n incorrect_attempts=10,\r\n locked_hours=24,\r\n all_locked_args={\r\n 'table_name': 'locked_info',\r\n 'username_col': 'username',\r\n 'locked_time_col': 'locked_time',\r\n 'unlocked_time_col': 'unlocked_time'},\r\n all_incorrect_attempts_args={\r\n 'table_name': 'incorrect_attempts',\r\n 'username_col': 'username',\r\n 'datetime_col': 'datetime'})\r\n```\r\n\r\n![](https://github.com/msquaredds/StUser/blob/main/images/Login.JPG?raw=true)\r\n\r\n### Forgot Username\r\n\r\nIf a user forgets their username, they can enter their email address and\r\nreceive an email with their username.\r\n\r\nNotes:\r\n- username_pull_args is derived from the save_pull_args in the class\r\n instantiation, but we need to add the table names and columns.\r\n- The email inputs were all handled in the class instantiation, so we\r\n don't need to add them here.\r\n\r\n```python\r\nstuser_forms.forgot_username(\r\n location='main',\r\n username_pull_args={\r\n 'table_name': 'user_credentials',\r\n 'email_col': 'email',\r\n 'username_col': 'username'})\r\n```\r\n\r\n![](https://github.com/msquaredds/StUser/blob/main/images/ForgotUsername.JPG?raw=true)\r\n\r\n### Forgot Password\r\n\r\nIf a user forgets their password, they can enter their username and\r\nreceive an email with a new, secure password.\r\n\r\nNotes:\r\n- username_pull_args and password_store_args are derived from the\r\n save_pull_args in the class instantiation, but we need to add the\r\n table names and columns.\r\n- The email inputs were all handled in the class instantiation, so we\r\n don't need to add them here.\r\n\r\n```python\r\nstuser_forms.forgot_password(\r\n location='main',\r\n username_pull_args={\r\n 'table_name': 'user_credentials',\r\n 'email_col': 'email',\r\n 'username_col': 'username'},\r\n password_store_args={\r\n 'table_name': 'user_credentials',\r\n 'username_col': 'username',\r\n 'password_col': 'password',\r\n 'datetime_col': 'datetime'})\r\n```\r\n\r\n![](https://github.com/msquaredds/StUser/blob/main/images/ForgotPassword.JPG?raw=true)\r\n\r\n### Update User Info\r\n\r\nIf a user wants to update their username, password or email, they can do\r\nso here. Usually, they will have to be logged in first to access this\r\nform.\r\n\r\nNotes:\r\n- info_pull_args and info_store_args are derived from the save_pull_args\r\n in the class instantiation, but we need to add the table names and\r\n columns.\r\n- The email inputs were all handled in the class instantiation, so we\r\n don't need to add them here.\r\n- store_new_info is a string or list of strings that tells us which of the\r\n new info to store in a session state. So in our example, only an\r\n updated email would be put into a session state, whereas an updated\r\n username or password would not.\r\n\r\n```python\r\nif stuser_forms.check_authentication_status():\r\n stuser_forms.update_user_info(\r\n location='main',\r\n info_pull_args={\r\n 'table_name': 'user_credentials',\r\n 'col_map': {'email': 'email',\r\n 'username': 'username',\r\n 'password': 'password'}},\r\n info_store_args={\r\n 'table_name': 'user_credentials',\r\n 'col_map': {'email': 'email',\r\n 'username': 'username',\r\n 'password': 'password',\r\n 'datetime': 'datetime'}},\r\n store_new_info='email')\r\n```\r\n\r\n![](https://github.com/msquaredds/StUser/blob/main/images/UpdateUserInfo.JPG?raw=true)\r\n\r\n### Logout\r\n\r\nFinally, once a user is logged in, we can log them out.\r\n\r\n```python\r\nstuser_forms.logout()\r\n```\r\n\r\n![](https://github.com/msquaredds/StUser/blob/main/images/Logout.JPG?raw=true)\r\n\r\n## Credit\r\nThis package was originally forked from [Streamlit-Authenticator](\r\nhttps://github.com/mkhorasani/Streamlit-Authenticator) and so some credit\r\nmust go to the original author - Mohammad Khorasani. That is why you might\r\nsee some additional contributors in the GitHub repo for StUser. Note that\r\nthe Streamlit-Authenticator package was under an Apache license at the\r\ntime and while some of the outline was used, the code was completely\r\nrewritten.\r\n\r\n",
"bugtrack_url": null,
"license": "Copyright 2024 M Squared Data Science LLC Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.",
"summary": "User registration, login and associated ecosystem for Streamlit",
"version": "0.0.3",
"project_urls": {
"repository": "https://github.com/msquaredds/StUser"
},
"split_keywords": [
"python",
" streamlit",
" authentication",
" components"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "151331ea2b29aa033e60a89a863559548d4230ea305063a398745ff2611345bd",
"md5": "46a3d8b75d893fcace08472abd14da74",
"sha256": "3e1cbbc0ba865701be64c69509b7e749a7ebb1d0774965b07a85e1435d06835d"
},
"downloads": -1,
"filename": "stuser-0.0.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "46a3d8b75d893fcace08472abd14da74",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 64778,
"upload_time": "2025-01-02T20:34:25",
"upload_time_iso_8601": "2025-01-02T20:34:25.221384Z",
"url": "https://files.pythonhosted.org/packages/15/13/31ea2b29aa033e60a89a863559548d4230ea305063a398745ff2611345bd/stuser-0.0.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-01-02 20:34:25",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "msquaredds",
"github_project": "StUser",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "stuser"
}