Coder Social home page Coder Social logo

flask-security-sandbox's Introduction

this tutorial is from here: https://www.geeksforgeeks.org/flask-role-based-access-control/

reminders on how to work on this project:

ToDos:

  1. currently working on--> add a notary logbook feature with an editable table for Traditional Notary and Electronic Notary users.
    1. COMPLETE-->This will require updating the User model for registered users who are either Traditional Notaries or Electronic Notaries to include their commission details: commission_holder_name, commission_number_uid, commissioned_county, commission_type_traditional_or_electronic, term_issue_date and term_expiration_date.
    2. COMPLETE-->UPDATE PSQL User MODEL: once a user registers as a notary and successfully authenticates using NYSDOS notary credentials those credentials need to be added to the user's profile in the database.
    3. Add editable_logbook.py and editable_logbook.html to the front end.
    4. create notary_logbook models for database and join it with the User model.
    5. Add CRUD operations for logbook
    6. Add logic that checks if a user has already registered with the notary credentials.
  2. Add "Forgot Username" and "Forgot Password" feature using flask-security & flask-mailman

running the application:

  1. There is a virtual environment already set up for this project so you need to activate it:

source venv/bin/activate

  1. In Flask, the FLASK_APP environment variable is used to specify how to load your application. It can be set to a python file, a module, or a package. In this case, it's being set to the app.py file, which is presumably where your Flask application is defined.

When you run flask run, Flask uses the value of FLASK_APP to find your application and run it. If FLASK_APP is not set, Flask won't know where your application is and won't be able to run it. So before you run the application you must run the command:

export FLASK_APP=app.py

  1. Now you can run the application:

flask run --debugger

psql and the database:

  1. Since this is a Unix-based system you must activate the PostgreSQL service with the command: NB--> be prepared to enter your psql password

sudo service postgresql start

  1. To log in to your PSQL account you must use the psql command with the -U option followed by your username:

psql -U your_username

  1. Helpful "general" psql commands:
  • \l : list all your databases.
  • \c nysdos_notaries_test : Connect to a specific database.
  • \dt : List all tables in the current database.
  • \d : table_name: Show the structure of a specific table.
  • \du : List all users.
  • \h : Get a help on syntax of SQL commands.
  • \? : Lists all psql slash commands.
  • \q : Quit psql.
  1. Helpful psql commands to run once you are connected to a database:
  • \dt: List all tables in the current database.
  • \d table_name : Show the structure of a specific table. Replace table_name with the name of the table you want to inspect.
  • \dn : List all schemas in the current database.
  • \df : List all functions in the current database.
  • dv : List all views in the current database.
  • \x : Toggle expanded display. This can make the output of some commands easier to read.
  • \a : Toggle between unaligned and aligned output mode.
  • \timing : Toggle timing of commands. When on, psql will show how long each command takes to execute.
  • \i filename : Execute commands from a file. Replace filename with the name of the file you want to execute commands from.
  • \o filename : Send all query results to a file. Replace filename with the name of the file you want to send results to.
  • \q : Quit psql.

notes to refactor from school use case to my use case:

  1. change roles from Admin, Teacher, Staff and Student to Admin, Principal, Traditional Notary and Electronic Notary
  2. files to be refactored:
    • app.py
      • copy app.py and save it as app_copy.py in case you want to start the refactor over again.
      • you will have to add logic to the def signin() route where if the user picks "traditioanal notary" or "electronic notary" then a second signin screen shows up that asks for their commission details: full name, commission id, commissioned county and commission start and expiration dates.
      • you don't have to change the roles_users table, User Class or Role Class.
      • you don't have to change the def signin() route.
    • index.html
      • save original file as "index_notes.html" in case you need to refer to it later.
      • replace old roles with new roles and new routes.
    • signup.html
      • replace old roles with new roles. The value matches up to the ids as defined in the create_roles.py file (I think).
    • mydetails.html
      • change to myaccount.html and this page will essentially be an index of all the account subcategories that will vary depending on the user's role. For now implement the following subcategories:
        • admins, principals, trad notaries and e-notaries:
          • contactinfo.html: first name, last name, email, phone number
          • contactaddress.html: street address line 1, street address line 2, City, State, Zip code
          • billing.html: embed a Stripe API component here which should include first name on cc, last name on cc, cc number, expiration, cvv, billing address line 1, billing address line 2, billing address City, billing address State, billing address Zip code.
          • scheduling.html: embed a booking component here that uses a calendar and has bookling logic. Maybe include a preferred contact method (email vs phone) and a public notes section.
          • mynotaries.html: redirect to the /Explore route that has the table and CRUD operations or maybe embed the table with CRUD operations into this page.
          • mydocuments.html: embed a table with file upload capability. this will require setting up firebase document account to the user.
          • e-signature.html: embed the docusign test feature here,
          • backgroundinfo.html: this will also use Firebase to store the avatar image. Other info can be stored here such as teleconference options, languages spoken, specializations, certifications, current employer & title, etc. This may be a good placehold for if a user wants to incorporate their LinkedIn information.
    • signin.html
      • unchanged
    • staff.html, students.html, teachers.html, teacherslounge.html
      • depracated; no longer need but keep as a reference
    • create_roles.py
      • you will have to refactor this by changing the role names and you may want to change the ids from integers to text but this may have a negligible impact on the final code post refactor.

fake users login credentials

enotary user test #1: [email protected] abc123

enotary user test #2: [email protected] abc123

enotary user test #3: [email protected] abc123

principal user test#1 [email protected] abc123

principal user test#2-post notaryauth route refactor on 5-15-24: [email protected] abc123

enotary user test#1-post notaryauth route refactor on 5-15-24: [email protected] abc123 [created with real notary credentials below]

enotary user test#2-post notaryauth route refactor on 5-15-24: [email protected] abc123 [created with real notary credentials below]

trad_notary user test#1-post notaryauth route refactor on 5-15-24v2(second session):

[email protected] asdgfadfhassdfgadfh CHAK C CHONG 02CH5009104 Putnam Traditional 03/08/2023 03/08/2027

trad_notary user test#2-post notaryauth route refactor on 5-15-24v2(second session): --> fringe case where the commission id only has 10 characters but is 11 charaters long [email protected] abc123 KAREN M D'ANGELO 01 D6390284 Monroe Traditional 04/15/2023 04/15/2027

trad_notary user test#3-post notaryauth route refactor on 5-15-24v3(second session): --> fringe case where the commission id only has 10 characters but is 11 charaters long [email protected] abc123 GRACE PRUNO 01 P4603811 Suffolk Traditional 01/31/2023 01/31/2027

https://data.ny.gov/resource/rwbv-mz6z.json?commission_number_uid=01 P4603811

trad_notary user test#4-post notaryauth route refactor on 5-15-24v3(second session): --> not a fringe case [email protected] abc123 RASHAD SHARIF 01SH6313188 Rockland Traditional 01/20/2023 01/20/2027

if you go to the data.ny.gov website and search for records that contain a sapce " " using the 'contains' query term you get 236 records that are 11 characters long but only contain 10 characters and a space in the middle of the textstring. Querying these records is problematic from the API perspective and even copying and pasting the commission holder's name in the URL doesn't always work.

e.g. these five GET requests return an empty [ ]

https://data.ny.gov/resource/rwbv-mz6z.json?commission_holder_name=GRACE PRUNO

https://data.ny.gov/resource/rwbv-mz6z.json?commission_holder_name=JOHN A OWENS JR

https://data.ny.gov/resource/rwbv-mz6z.json?commission_holder_name=CAROL A BLAKE

https://data.ny.gov/resource/rwbv-mz6z.json?commission_holder_name=KAREN M D'ANGELO

https://data.ny.gov/resource/rwbv-mz6z.json?commission_holder_name=MICHELE A GREEN HOSANG

while https://data.ny.gov/resource/rwbv-mz6z.json?commission_holder_name=COSTA-TRAHAN CYNTHIA A returns:

[
  {
    "commission_holder_name": "COSTA-TRAHAN CYNTHIA A",
    "commission_number_uid": "02A 5074644",
    "business_address_1_if_available": "COSTA & ASSOCIATES",
    "business_address_2_if_available": "56 N. BROADWAY",
    "business_city_if_available": "NYACK",
    "business_state_if_available": "NY",
    "business_zip_if_available": "10960",
    "commissioned_county": "Rockland",
    "commission_type_traditional_or_electronic": "Traditional",
    "term_issue_date": "2023-05-17T00:00:00.000",
    "term_expiration_date": "2027-05-17T00:00:00.000",
    "georeference": {
      "type": "Point",
      "coordinates": [
        -73.9249517,
        41.0917555
      ]
    }
  }
]

my public credentials

use this fake default to log in as "a Principal user" NB--> 5/17/24 registration works fine

[email protected] abc123

[email protected] abc123

[email protected] abc123

ARTHUR John HAUSER 01HA0018990 Kings Electronic 12/21/2023 12/21/2027

Debugging notes from issue with Flask session and Flask_Security's SQLAlchemySessionUserDatastore and SQLAlchemyUserDatastore

  1. Flask's session object: This is a way to store information specific to a user from one request to the next. It's a dictionary-like object that uses a signed cookie to keep the session secure. In your code, you're using the session to store the email, password, and role_id of a user during the signup process. This data is then available to you in subsequent requests, even if they happen in a different route, like /notaryauth.

  2. SQLAlchemySessionUserDatastore and SQLAlchemyUserDatastore: These are classes provided by Flask-Security that allow you to interact with your User and Role models. They provide methods for creating users, finding users, adding roles to users, etc. In your code, you're using SQLAlchemySessionUserDatastore to create an instance of user_datastore at the start of your application. This user_datastore is then used in your /signup and /notaryauth routes to create users and add roles to them.

The SQLAlchemySessionUserDatastore is designed to work with SQLAlchemy's session-based transactions, which means it doesn't commit changes to the database immediately. Instead, it waits until you call db.session.commit(). This can be useful if you want to make multiple changes and then commit them all at once.

On the other hand, SQLAlchemyUserDatastore commits changes immediately. In your /notaryauth route, you're creating a new instance of SQLAlchemyUserDatastore and using it to create a user and add a role to them. These changes are committed to the database as soon as you call the methods.

In terms of how these are impacting your code, the Flask session is allowing you to store user data across requests, while the SQLAlchemyUserDatastore is letting you interact with your User and Role models. However, it's unusual to use both SQLAlchemySessionUserDatastore and SQLAlchemyUserDatastore in the same application. Typically, you would choose one or the other based on whether you want to use session-based transactions or not.

user_datastore variable explained in depth:

user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)

This code creates an instance of the SQLAlchemySessionUserDatastore class, which is a part of the Flask-Security extension. This class is a datastore adapter that allows Flask-Security to interact with your User and Role models using SQLAlchemy's session-based transactions.

Here's a breakdown of the parameters:

  • db.session: This is the SQLAlchemy session that will be used to interact with the database. SQLAlchemy sessions manage all the interactions with the database and keep track of all the objects you've loaded or associated with it. They also begin and control transactions.

  • User: This is your User model class. It should be a SQLAlchemy model that includes certain fields required by Flask-Security, such as a password and email.

  • Role: This is your Role model class. It should be a SQLAlchemy model that represents roles that users can have.

The SQLAlchemySessionUserDatastore instance (user_datastore) can then be used to create users, roles, and to add roles to users, among other things. It's designed to work with SQLAlchemy's session-based transactions, which means it doesn't commit changes to the database immediately. Instead, it waits until you call db.session.commit(). This can be useful if you want to make multiple changes and then commit them all at once.


Flask_Security explained

security = Security(app, user_datastore)

This code initializes the Flask-Security extension with your Flask application (app) and the SQLAlchemy session-based datastore (user_datastore).

Here's a breakdown of the parameters:

  • app: This is your Flask application instance. Flask-Security needs this to set up routes, templates, and other configuration options.

  • user_datastore: This is an instance of SQLAlchemySessionUserDatastore that you've created. It's used by Flask-Security to interact with your User and Role models.

The Security class is a part of the Flask-Security extension. It provides a number of features for security-related aspects of your application, such as user authentication, role management, password hashing, and session management.

By creating an instance of the Security class and passing in your Flask application and datastore, you're setting up Flask-Security to handle these aspects of your application. This includes setting up routes for user registration, login, logout, and more. It also includes setting up the necessary views and templates for these routes.

When you initialize Flask-Security by creating an instance of the Security class, you're essentially telling Flask-Security to set up a number of routes, views, and templates that are commonly used in web applications for user management. These include routes for user registration, login, logout, and more.

For example, Flask-Security will automatically create the following routes:

  • /login: This route is used for user login. It will display a login form to the user and handle the submission of this form to authenticate the user.

  • /logout: This route is used to log out the user. It will clear the user's session and redirect them to the login page.

  • /register: This route is used for user registration. It will display a registration form to the user and handle the submission of this form to create a new user.

  • /reset: This route is used to reset a user's password. It will display a form to the user where they can enter their email address. Flask-Security will then send them an email with a link to reset their password.

In addition to these routes, Flask-Security will also set up the necessary views and templates. The views are the functions that handle the requests to these routes, and the templates are the HTML files that define what the user sees when they visit these routes.

For example, for the /login route, Flask-Security will create a view function that handles the GET and POST requests to this route. It will also create a login.html template that defines the login form that the user sees.

All of this setup is done automatically when you create an instance of the Security class and pass in your Flask application and datastore. This saves you the time and effort of having to create these routes, views, and templates yourself.


old /notaryauth route code that was interfering with new code while refactoring to use flask-wtforms

@app.route("/signup", methods=["GET", "POST"])
def signup():
    msg = ""
    if request.method == "POST":
        user = User.query.filter_by(email=request.form["email"]).first()
        if user:
            msg = "User already exist"
            return render_template("signup.html", msg=msg)

        role = Role.query.filter_by(id=request.form["options"]).first()
        user = user_datastore.create_user(
            email=request.form["email"], password=request.form["password"]
        )
        user_datastore.add_role_to_user(user, role)
        db.session.commit()
        login_user(user)

        # Store email and password in session
        session["email"] = request.form["email"]
        session["password"] = request.form["password"]
        session["role_id"] = role.id

        if role.name in ["Traditional Notary", "Electronic Notary"]:
            session["form_data"] = request.form.to_dict()
            return redirect(url_for("signupnotary"))  # Redirect to signupnotary
        else:
            return redirect(url_for("index"))
    else:
        return render_template("signup.html", msg=msg)


@app.route("/signupnotary", methods=["GET"])
def signupnotary():
    # Render the signupnotary page
    return render_template("signupnotary.html")


@app.route("/notaryauth", methods=["GET", "POST"])
def notaryauth():
    # Retrieve form data from session
    form_data = session.get("form_data", {})
    email = form_data.get("email")
    password = form_data.get("password")
    full_name = form_data.get("full_name")
    commission_id = form_data.get("commission_id")
    commissioned_county = form_data.get("commissioned_county")

    commission_start_date_str = form_data.get("commission_start_date")
    if commission_start_date_str is not None:
        commission_start_date = datetime.strptime(commission_start_date_str, "%Y-%m-%d")
    else:
        commission_start_date = None

    commission_expiration_date_str = form_data.get("commission_expiration_date")
    if commission_expiration_date_str is not None:
        commission_expiration_date = datetime.strptime(
            commission_expiration_date_str, "%Y-%m-%d"
        )
    else:
        commission_expiration_date = None

    role_mapping = {
        3: "Traditional",
        4: "Electronic",
    }

    role_value = int(session.get("role_id", 0))
    commission_type = role_mapping.get(role_value)

    if commission_id is not None:
        commission_id_encoded = urllib.parse.quote_plus(str(commission_id))
    else:
        commission_id_encoded = None

    response = requests.get(
        "https://data.ny.gov/resource/rwbv-mz6z.json",
        params={
            "commission_holder_name": full_name,
            "commission_number_uid": commission_id_encoded,
            "commissioned_county": commissioned_county,
            "commission_type_traditional_or_electronic": commission_type,
            "term_issue_date": commission_start_date_str,
            "term_expiration_date": commission_expiration_date_str,
        },
    )
    data = response.json()
    print("Data from API: ", data)

    if not data or not isinstance(data, list) or len(data) == 0:
        return (
            jsonify({"error": "No matching data found in the API's database"}),
            400,
        )

    print(
        "Data to map to: ",
        full_name,
        commissioned_county,
        commission_start_date,
        commission_expiration_date,
    )

    if (
        data[0]["commission_holder_name"].lower() == full_name.lower()
        and data[0]["commissioned_county"].lower() == commissioned_county.lower()
        and datetime.strptime(data[0]["term_issue_date"], "%Y-%m-%dT%H:%M:%S.%f")
        == commission_start_date
        and datetime.strptime(data[0]["term_expiration_date"], "%Y-%m-%dT%H:%M:%S.%f")
        == commission_expiration_date
    ):
        user = user_datastore.create_user(email=email, password=password)
        role = user_datastore.find_role(
            "Traditional Notary"
            if data[0]["commission_type_traditional_or_electronic"] == "Traditional"
            else "Electronic Notary"
        )

        user_datastore.add_role_to_user(user, role)

        notary_credentials = NotaryCredentials(
            user_id=user.id,
            commission_holder_name=full_name,
            commission_number_uid=commission_id,
            commissioned_county=commissioned_county,
            commission_type_traditional_or_electronic=commission_type,
            term_issue_date=commission_start_date,
            term_expiration_date=commission_expiration_date,
        )
        db.session.add(notary_credentials)

        db.session.commit()
        login_user(user)
        return redirect(url_for("index"))
    else:
        return render_template(
            "signupnotary.html",
            error="The provided data does not match the API's database",
        )

notes from call with Mike @ Springboard

https://jsdoc.app/tags-example https://docs.oracle.com/javase/8/docs/api/ https://docs.python.org/3/library/pydoc.html https://medium.com/@peterkong/comparison-of-python-documentation-generators-660203ca3804

use this documentation guide to create basic documentation for the project. https://www.sphinx-doc.org/en/master/usage/quickstart.html

flask-security-sandbox's People

Contributors

ahauser16 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.