I'm trying to build a website that allows people to access different kinds of graphs in a dash app. I want the users to log in with a username and a password before they can access the dashboard. This is what I have thus far
from flask import Flask, flash, render_template, request, session
import os, dash
import dash_html_components as html
import dash_core_components as dcc
import flask
app = Flask(__name__)
dash_app = dash.Dash(__name__, server=app, url_base_pathname='/dash_app')
dash_app.config['suppress_callback_exceptions']=True
def index_page():
index = html.Div([
dcc.Link('Page 1', href='/page1'),
html.Br(),
dcc.Link('Page 2', href='/page2'),
html.Br(),
dcc.Link('Page 3', href='/page3'),
html.Br()
])
return index
dash_app.layout = html.Div(children=[
dcc.Location(id='url', refresh=False),
html.Div(id = 'page-content')
])
page_1 = html.Div([
html.H1('Welcome to page 1'),
index_page()
])
page_2 = html.Div([
html.H1('Welcome to page 2'),
index_page()
])
page_3 = html.Div([
html.H1('Welcome to page 3'),
index_page()
])
#dash_app.callback(
dash.dependencies.Output('page-content','children'),
[dash.dependencies.Input('url','pathname')]
)
def display_page(pathname):
if pathname == '/page1':
return page_1
if pathname == '/page2':
return page_2
if pathname == '/page3':
return page_3
else:
return index_page()
#app.route('/')
def home():
if not session.get('logged_in'):
return render_template('login.html')
else:
return flask.redirect('/dash_app')
#app.route('/login', methods=['POST'])
def do_admin_login():
if request.form['password'] == 'password' and request.form['username'] == 'admin':
session['logged_in'] = True
else:
flash('wrong password!')
return home()
if __name__ == "__main__":
app.secret_key = os.urandom(12)
app.run(debug=True, port=5000)
There are two problems that I want to solve:
With this setup, you don't have to login to access the dashboard. If, instead of entering the username and password, you go directly to http://localhost:5000/dash_app you get to see the dashboard immediately. I only want to give access to people that are logged in.
If I refresh my browser page, after clicking on one of the three page links, I get a message saying "Not Found":I don't understand why this is happening and it feels to me like this has something to with the structure of the app.
Question: How do I solve these two issues? And, more generally; does this structure fit the goal I am trying to achieve? Is this the correct way to setup a dashboard inside a flask app? (I know that this login setup is not safe)
Edit: Regarding the refresh problem, I am sure that it has something to do with the dash app being run inside the flask app, because when I run the dash app by itself I can refresh http://localhost:5000/page1 and it renders successfully.
You should use flask-security instead of building your own login mechanism it solve lot of issue like Registration, Login, Logout, Forgot Password etc.
Related
I have the following python script that is connecting to Google Dialogflow using Flask and is using a webhook to retrieve the response from Google Dialogflow.
The limitation is that I currently only enter the query in to the Google Dialogflow frontend, with the result returned here in variable result
However how can I use this same script to submit the query to Google Dialogflow, instead of entering in the front end?
Any help appreciated, thanks!
import json
import os
from flask import Flask
from flask import request
from flask import make_response`
`enter code here # Flask app should start in global layout
app = Flask(__name__)
#app.route('/webhook', methods=['POST'])
def webhook():
req = request.get_json(silent=True, force=True)
res = processRequest(req)
res = json.dumps(res, indent=4)
r = make_response(res)
r.headers['Content-Type'] = 'application/json'
return r
def processRequest(req):
result = req.get("queryResult")
result_message = result['fulfillmentText']
print(result_message)
#app.route('/test', methods=['GET'])
def test():
return "Hello there my friend !!"
if __name__ == '__main__':
port = int(os.getenv('PORT', 5000))
app.run(debug=True, port=port, host='0.0.0.0')`
I discovered shortly after posting this that a webhook is whats known as a reverse API, and only shows results as opposed to two way interactions.
When I add in a new user and password in the GCP console, refresh and wait, then re deploy and run my web app I can't login with that user. I can still login with my original test user (the first and only user thus far beside the 'postgres' admin. user)
Ive tried deleting and re-adding the same user.Ive tried adding yet another user and deploying - re attempting to login in again. Ive made sure Ive refreshed and waited for the change to take effect before re-deploying the web app . I have logged with my original user , log out and try login with the new user, also initially with the new user.I've scoured online for answers but surprisingly to no avail.
The main , outer, app.py file that has the user management/Auth code using Flask and flask_login functionality :
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import sys
#sys.path.append('/Users/crowledj/Mindfule/dash-flask-login/views/')
#sys.path.append('/Users/crowledj/Mindfule/dash-flask-login/flask_login/')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__ , external_stylesheets=external_stylesheets)
#server=app.server
app.css.append_css({'external_url': 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css'})
from server import app, server
from flask_login import logout_user, current_user
import success, login, login_fd, logout
#import sqlalchemy
header = html.Div(
className='header',
children=html.Div(
className='container-width',
style={'height': '100%'},
children=[
html.Img(
src='mindfule_company_logo.jpg',
className='logo'
),
html.Div(className='links', children=[
html.Div(id='user-name', className='link'),
html.Div(id='logout', className='link')
])
]
)
)
app.layout = html.Div(
[
header,
html.Div([
html.Div(
html.Div(id='page-content', className='content'),
className='content-container'
),
], className='container-width'),
dcc.Location(id='url', refresh=False),
]
)
#app.callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/':
return login.layout
elif pathname == '/login':
return login.layout
elif pathname == '/success':
if current_user.is_authenticated:
print('returning success page from main app ... \n')
return success.layout
else:
return login_fd.layout
elif pathname == '/logout':
if current_user.is_authenticated:
logout_user()
return logout.layout
else:
return logout.layout
else:
return '404'
#app.callback(
Output('user-name', 'children'),
[Input('page-content', 'children')])
def cur_user(input1):
if current_user.is_authenticated:
return html.Div('Current user: ' + current_user.username)
# 'User authenticated' return username in get_id()
else:
return ''
#app.callback(
Output('logout', 'children'),
[Input('page-content', 'children')])
def user_logout(input1):
if current_user.is_authenticated:
return html.A('Logout', href='/logout')
else:
return ''
if __name__ == '__main__':
app.run_server(debug=True,port=8080,host= "foodmoodai.appspot.com") #"0.0.0.0") #
the only Postgres and SQL related code # start of my 'success' page file :
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objs as go
from textwrap import dedent as d
from flask import Flask
import pandas as pd
import numpy as np
from NutrientParser import parseNutrientStr_frmUser,parseResearch,parseFoodResearch,find_substring
from userMindfuleClasses import *
import PIL
import urllib3
from PIL import Image
import json,os
import arrow
from server import app
from flask_login import current_user
import psycopg2
from datetime import datetime
timeStamp=datetime.now()
#db_user='test'
#db_pass='test1'
#db_name='foodnmood-db'
#INSTANCE_CONNECTION_NAME='foodmoodai:europe-west2:foodnmood-db'
from sqlalchemy import Table, Column, Integer, String, MetaData,create_engine
meta = MetaData()
#engine = create_engine('postgresql+psycopg2://postgres:Pollgorm1#/cloudsql/foodmoodai:europe-west2:foodnmood-db')
engine = create_engine('postgresql+psycopg2://postgres:Pollgorm1#/?host=/cloudsql/foodmoodai:europe-west2:foodnmood-db')
mealnMoodwithTimesnFoods = Table(
'mealnMoodwithTimesnFoods', meta,
Column('time', String, primary_key = True),
Column('id', String),
Column('food_1', String),
Column('food_2', String),
Column('food_3', String),
Column('mood', String),
)
meta.create_all(engine)
I expect to be able to at least add a new user (which automatically has login permissions) and log in past the log in page when I redeploy the app after making this change in the GCP console.
The issue here turned out to actually be completely to do with a local authentication library I had installed from GitHub - which uses 'flask_login' (flask_login==0.4.1 --> pip install flask-login==0.4.1).All I needed to do was update the new user and password in a local .txt file as well as on gcloud's cloud SQL console.
I am using flask_login for implementing auth in my flask application
Here are the unauthorized_handler and login_required enabled method
#login_manager.unauthorized_handler
def unauthorized_handler():
return redirect(url_for('login'))
# use decorators to link the function to a url
#app.route('/profile', methods=['GET'])
#flask_login.login_required
def profile():
return render_template('profile.html')
I am using firebase as my backend service
#app.route('/login', methods=['GET', 'POST'])
def login():
auth = firebase.auth()
if request.method == 'POST':
try:
user = auth.sign_in_with_email_and_password(request.form['email'], request.form['password'])
if user != None:
return redirect(url_for('profile'))
except requests.exceptions.HTTPError as e:
response = e.args[0].response
error = response.json()['error']['code']
return redirect(url_for('home'))
return render_template('login.html')
The problem is that after I login(which is successfull) the app is automatically redirected to the /login url instead of /profle.
I tried turning my debug mode off and on still not working.
It can be a case of a double redirect happening, where you first get redirected to profile, and then the login_required decorator kicks in redirecting you back to login, because from the point of view of flask_login you are not logged in yet.
You might have retrieved user credentials from firebse, but with flask_login you have to also call login_user function. (Also see Login Example of flask login's page)
I have a flask application in what I'm trying to create a session but only for the admin routes, so anywhere else can be accessible except admin routes, the thing is in my localhost everything works fine, but, in the live server, the session get created when I log in, but when I try to check if the session exist calling session.get("user_id"), it doesn't. Is like if the session is not created persistent. I have been reading for 10 hours today and I finally found why is not working, but I really don't undestand why it happens. The problem i'm facing is located in app.config["SESSION_FILE_DIR"] = mkdtemp(), if I use this in the localhost works, but not in the live server, and if I omite that line, then it works in the live server. Here is my code:
from flask import Flask, flash, redirect, render_template, request, session, jsonify, url_for
from flask_session import Session
from functools import wraps
from werkzeug.security import check_password_hash, generate_password_hash
from tempfile import mkdtemp # used to create a temp directory
from helpers import login_required
import os
# Configure application as flask app
app = Flask(__name__)
# Ensure templates are auto-reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True
# Configure session to use filesystem (instead of signed cookies)
app.config["SESSION_FILE_DIR"] = mkdtemp()
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
app.secret_key = 'superkey'
# function to login admin
#app.route("/login", methods=["GET", "POST"])
def login():
"""Log admin in"""
# Forget any user_id
session.clear()
# User reached route via POST (as by submitting a form via POST)
if request.method == "POST":
# Ensure username was submitted
if not request.form.get("username"):
flash ("must provide username")
return render_template('/login.html')
# Ensure password was submitted
elif not request.form.get("password"):
flash ("must provide password")
return render_template('/login.html')
# Query database for username
username = request.form.get("username")
username = str.lower(username)
db = dbConnection()
db.execute("SELECT * FROM identities WHERE user_name = ?", [username])
rows = db.fetchall()
# Ensure username exists and password is correct
if len(rows) == 1:
uid, name, pass_hash = rows[0]
if len(rows) != 1 or not check_password_hash(pass_hash, request.form.get("password")):
flash ("invalid username and/or password")
return render_template('/login.html')
# Remember which user has logged in
session["user_id"] = uid
# Redirect user to home page
return redirect("/adminForms")
# User reached route via GET (as by clicking a link or via redirect)
else:
return render_template("login.html")
# function as decorator to ensure the user was logged in before
# can go to a protected page
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
# if session don't return a value, then the user needs to log in
if session.get("user_id") is None:
return redirect("/login")
return f(*args, **kwargs)
return decorated_function
I am following the tutorial by http://www.patricksoftwareblog.com/flask-tutorial/, which I believe is based on https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world. Great stuff for a beginner.
I am getting different results when testing my code through frontend manually (which works fine) v.s. through pytest.
My test tries to show the "groups" endpoint which requires a login (standard #login_required decorator).
I initially test the user getting a login page ("Knock knock") when trying to get the endpoint without a login. This works manually and through pytest.
I login a user. If I inspect the response from the login I can clearly see a "Welcome back Pete!" success message.
My second assert receives a response from URL /login?next=%2Fgroups indicating the /groups endpoint is called without a login/authentication preceding it and the assert fails. Testing this manually works as expected. Why is that single test not using the same user/session combination in the next step(s)?
Test with the problem is the first snippet below:
def test_groups(app):
assert b'Knock knock' in get(app, "/groups").data
login(app, "pete#testmail.com", "pete123")
assert b'Test group 1' in get(app, "/groups").data
My "get" function for reference:
def get(app, endpoint: str):
return app.test_client().get(endpoint, follow_redirects=True)
My "login" function for reference:
def login(app, email="testuser#testmail.com", password="testing"):
return app.test_client().post('/login', data=dict(email=email, password=password), follow_redirects=True)
The app (from a conftest fixture imported in the test module by #pytest.mark.usefixtures('app')) for reference:
#pytest.fixture
def app():
"""An application for the tests."""
_app = create_app(DevConfig)
ctx = _app.test_request_context()
ctx.push()
yield _app
ctx.pop()
The login route for reference:
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST':
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is not None and user.is_correct_password(form.password.data):
user.authenticated = True
user.last_login = user.current_login
user.current_login = datetime.now()
user.insert_user()
login_user(user)
flash(f'Welcome back {user.name}!', 'success')
return redirect(url_for('our_awesome_group.index'))
else:
flash('Incorrect credentials! Did you already register?', 'error')
else:
flash_errors(form)
return render_template('login.html', form=form)
The groups route for reference:
#app.route('/groups')
#login_required
def groups():
groups_and_users = dict()
my_group_uuids = Membership.list_groups_per_user(current_user)
my_groups = [Group.query.filter_by(uuid=group).first() for group in my_group_uuids]
for group in my_groups:
user_uuids_in_group = Membership.list_users_per_group(group)
users_in_group = [User.query.filter_by(uuid=user).first() for user in user_uuids_in_group]
groups_and_users[group] = users_in_group
return render_template('groups.html', groups_and_users=groups_and_users)
Im going to sum up the comments I made that gave the answer on how to solve this issue.
When creating a test app using Pytest and Flask there are a few different ways to go about it.
The suggested way to create a test client with proper app context is to use something like:
#pytest.fixture
def client():
""" Creates the app from testconfig, activates test client and context then makes the db and allows the test client
to be used """
app = create_app(TestConfig)
client = app.test_client()
ctx = app.app_context()
ctx.push()
db.create_all()
yield client
db.session.close()
db.drop_all()
ctx.pop()
That creates the client while pushing the app context so you can register things like your database and create the tables to the test client.
The second way is show in OP's question where use app.test_request context
#pytest.fixture
def app():
"""An application for the tests."""
_app = create_app(DevConfig)
ctx = _app.test_request_context()
ctx.push()
yield _app
ctx.pop()
and then create the test client in another pytest fixture
#pytest.fixture
def client(app):
return app.test_client()
Creating a test client allows you to use various testing features and gives access to flask requests with the proper app context.