Google AUTH and Readme
This commit is contained in:
parent
5a900e0ded
commit
bac1d84b09
89
README.md
Normal file
89
README.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Decision Tree Helpdesk Application
|
||||
|
||||
This application provides a decision tree-based helpdesk system to guide users through troubleshooting steps.
|
||||
|
||||
## Features
|
||||
|
||||
- Interactive decision tree navigation
|
||||
- Google OAuth authentication
|
||||
- Admin interface for managing decision trees
|
||||
- User-friendly interface
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.7+
|
||||
- pip (Python package manager)
|
||||
- A Google Developer account (for OAuth)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/yourusername/decision-tree-helpdesk.git
|
||||
cd decision-tree-helpdesk
|
||||
```
|
||||
|
||||
2. Create and activate a virtual environment:
|
||||
```
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
```
|
||||
|
||||
3. Install dependencies:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4. Set up environment variables:
|
||||
Create a `.env` file in the root directory with the following variables:
|
||||
```
|
||||
GOOGLE_CLIENT_ID=your_client_id_here
|
||||
GOOGLE_CLIENT_SECRET=your_client_secret_here
|
||||
SECRET_KEY=your_secret_key_here
|
||||
```
|
||||
|
||||
5. Initialize the database:
|
||||
```
|
||||
flask db init
|
||||
flask db migrate -m "Initial migration"
|
||||
flask db upgrade
|
||||
```
|
||||
|
||||
## Running the Application
|
||||
|
||||
1. Start the development server:
|
||||
```
|
||||
flask run
|
||||
```
|
||||
|
||||
2. Open your browser and navigate to:
|
||||
```
|
||||
http://localhost:5000
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### User Guide
|
||||
|
||||
1. Log in using your Google account
|
||||
2. Navigate through the decision tree by answering questions
|
||||
3. Follow the recommended solutions for your issue
|
||||
|
||||
### Admin Guide
|
||||
|
||||
1. Log in as an administrator
|
||||
2. Access the admin panel to manage decision trees
|
||||
3. Create, edit, or delete nodes in the decision tree
|
||||
4. Define question and answer paths
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch: `git checkout -b feature-name`
|
||||
3. Commit your changes: `git commit -m 'Add some feature'`
|
||||
4. Push to the branch: `git push origin feature-name`
|
||||
5. Submit a pull request
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
26
app.py
26
app.py
@ -1,33 +1,39 @@
|
||||
from flask import Flask
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask, render_template
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager
|
||||
from models import db, User # Our database instance
|
||||
from user import user_bp
|
||||
from admin import admin_bp
|
||||
from auth import auth_bp
|
||||
from oauth import google_bp
|
||||
from commands import create_admin
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-for-testing')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///helpdesk.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.secret_key = 'your_secret_key' # Replace with a secure key
|
||||
|
||||
# Initialize the database with the Flask app
|
||||
db.init_app(app)
|
||||
|
||||
# Initialize Flask-Login
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager = LoginManager(app)
|
||||
login_manager.login_view = 'auth.login'
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(id):
|
||||
return User.query.get(int(id))
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
# Register Blueprints
|
||||
app.register_blueprint(user_bp)
|
||||
app.register_blueprint(admin_bp, url_prefix='/admin')
|
||||
app.register_blueprint(auth_bp, url_prefix='/auth')
|
||||
app.register_blueprint(google_bp, url_prefix='/login')
|
||||
|
||||
# Create database tables if they don't exist
|
||||
with app.app_context():
|
||||
@ -36,5 +42,13 @@ with app.app_context():
|
||||
# Add this after creating the app
|
||||
app.cli.add_command(create_admin)
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
return render_template('error.html'), 404
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
return render_template('error.html'), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
|
||||
6
auth.py
6
auth.py
@ -5,6 +5,7 @@ from models import db, User, SiteConfig
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||
from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError
|
||||
from oauth import google_bp
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
@ -45,7 +46,10 @@ def login():
|
||||
return redirect(next_page or url_for('user.home'))
|
||||
flash('Invalid username or password', 'danger')
|
||||
|
||||
return render_template('auth/login.html', form=form)
|
||||
# Check if Google OAuth is configured
|
||||
google_configured = google_bp.client_id is not None
|
||||
|
||||
return render_template('auth/login.html', form=form, google_configured=google_configured)
|
||||
|
||||
@auth_bp.route('/logout')
|
||||
@login_required
|
||||
|
||||
42
migrations/add_oauth_support.py
Normal file
42
migrations/add_oauth_support.py
Normal file
@ -0,0 +1,42 @@
|
||||
from app import app, db
|
||||
from models import User, OAuth
|
||||
|
||||
def upgrade_database():
|
||||
"""Add OAuth support to the database"""
|
||||
with app.app_context():
|
||||
# Check if columns already exist
|
||||
with db.engine.connect() as conn:
|
||||
# Check if is_oauth_user column exists in User table
|
||||
result = conn.execute(db.text("PRAGMA table_info(user)"))
|
||||
columns = result.fetchall()
|
||||
column_names = [col[1] for col in columns]
|
||||
|
||||
if 'is_oauth_user' not in column_names:
|
||||
conn.execute(db.text("ALTER TABLE user ADD COLUMN is_oauth_user BOOLEAN DEFAULT 0"))
|
||||
print("Added is_oauth_user column to User table")
|
||||
|
||||
# Check if OAuth table exists
|
||||
result = conn.execute(db.text("SELECT name FROM sqlite_master WHERE type='table' AND name='oauth'"))
|
||||
table_exists = result.fetchone() is not None
|
||||
|
||||
if not table_exists:
|
||||
# Create the OAuth table
|
||||
conn.execute(db.text("""
|
||||
CREATE TABLE oauth (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
provider VARCHAR(50) NOT NULL,
|
||||
provider_user_id VARCHAR(256) NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
user_id INTEGER,
|
||||
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE,
|
||||
UNIQUE (provider, provider_user_id)
|
||||
)
|
||||
"""))
|
||||
print("Created OAuth table")
|
||||
|
||||
conn.commit()
|
||||
|
||||
print("Database migration for OAuth support completed successfully")
|
||||
|
||||
if __name__ == "__main__":
|
||||
upgrade_database()
|
||||
12
models.py
12
models.py
@ -1,22 +1,30 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(64), unique=True, nullable=False)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
username = db.Column(db.String(64), index=True, unique=True)
|
||||
email = db.Column(db.String(120), index=True, unique=True)
|
||||
password_hash = db.Column(db.String(128))
|
||||
is_admin = db.Column(db.Boolean, default=False)
|
||||
|
||||
# New field to track if user was created via OAuth
|
||||
is_oauth_user = db.Column(db.Boolean, default=False)
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
class OAuth(OAuthConsumerMixin, db.Model):
|
||||
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
|
||||
user = db.relationship(User)
|
||||
|
||||
class NodeImage(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
node_id = db.Column(db.Integer, db.ForeignKey('node.id'), nullable=False)
|
||||
|
||||
90
oauth.py
Normal file
90
oauth.py
Normal file
@ -0,0 +1,90 @@
|
||||
import os
|
||||
from flask import flash, redirect, url_for
|
||||
from flask_dance.consumer import oauth_authorized
|
||||
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage
|
||||
from flask_dance.contrib.google import make_google_blueprint
|
||||
from flask_login import current_user, login_user
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
from models import db, User, OAuth
|
||||
|
||||
# Get Google OAuth credentials from environment variables
|
||||
GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID")
|
||||
GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET")
|
||||
|
||||
# Create the Google OAuth blueprint
|
||||
google_bp = make_google_blueprint(
|
||||
client_id=GOOGLE_CLIENT_ID,
|
||||
client_secret=GOOGLE_CLIENT_SECRET,
|
||||
scope=["profile", "email"],
|
||||
storage=SQLAlchemyStorage(OAuth, db.session, user=current_user)
|
||||
)
|
||||
|
||||
# Register a callback for when the OAuth login is successful
|
||||
@oauth_authorized.connect_via(google_bp)
|
||||
def google_logged_in(blueprint, token):
|
||||
if not token:
|
||||
flash("Failed to log in with Google.", "danger")
|
||||
return False
|
||||
|
||||
# Get the user's Google account info
|
||||
resp = blueprint.session.get("/oauth2/v1/userinfo")
|
||||
if not resp.ok:
|
||||
flash("Failed to fetch user info from Google.", "danger")
|
||||
return False
|
||||
|
||||
google_info = resp.json()
|
||||
google_user_id = str(google_info["id"])
|
||||
|
||||
# Find this OAuth token in the database, or create it
|
||||
query = OAuth.query.filter_by(
|
||||
provider=blueprint.name,
|
||||
provider_user_id=google_user_id
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
except NoResultFound:
|
||||
oauth = OAuth(
|
||||
provider=blueprint.name,
|
||||
provider_user_id=google_user_id,
|
||||
token=token
|
||||
)
|
||||
|
||||
# Now, check to see if we already have a user with this email
|
||||
if oauth.user:
|
||||
# If this OAuth token already has an associated user, log them in
|
||||
login_user(oauth.user)
|
||||
flash("Successfully signed in with Google.", "success")
|
||||
else:
|
||||
# Check if we have a user with this email already
|
||||
user = User.query.filter_by(email=google_info["email"]).first()
|
||||
if user:
|
||||
# If we do, link the OAuth token to the existing user
|
||||
oauth.user = user
|
||||
db.session.add(oauth)
|
||||
db.session.commit()
|
||||
login_user(user)
|
||||
flash("Successfully signed in with Google.", "success")
|
||||
else:
|
||||
# If we don't, create a new user
|
||||
username = google_info["email"].split("@")[0]
|
||||
# Make sure username is unique
|
||||
base_username = username
|
||||
count = 1
|
||||
while User.query.filter_by(username=username).first():
|
||||
username = f"{base_username}{count}"
|
||||
count += 1
|
||||
|
||||
user = User(
|
||||
username=username,
|
||||
email=google_info["email"],
|
||||
is_oauth_user=True
|
||||
)
|
||||
# No password is set for OAuth users
|
||||
oauth.user = user
|
||||
db.session.add_all([user, oauth])
|
||||
db.session.commit()
|
||||
login_user(user)
|
||||
flash("Successfully signed in with Google.", "success")
|
||||
|
||||
# Prevent Flask-Dance from creating another token
|
||||
return False
|
||||
@ -5,6 +5,7 @@ Flask-Login==0.6.2
|
||||
Flask-WTF==1.2.1
|
||||
Flask-Migrate==4.0.5
|
||||
Werkzeug==2.3.7
|
||||
Flask-Dance[sqla]==6.2.0
|
||||
|
||||
# Database
|
||||
SQLAlchemy==2.0.23
|
||||
@ -16,6 +17,7 @@ email-validator==2.1.0
|
||||
# Security
|
||||
python-dotenv==1.0.0
|
||||
bcrypt==4.0.1
|
||||
blinker==1.6.2
|
||||
|
||||
# Image handling
|
||||
Pillow==10.1.0
|
||||
@ -39,4 +41,8 @@ click==8.1.7
|
||||
|
||||
# If you plan to use a different database than SQLite
|
||||
# psycopg2-binary==2.9.9 # For PostgreSQL
|
||||
# mysqlclient==2.2.0 # For MySQL
|
||||
# mysqlclient==2.2.0 # For MySQL
|
||||
|
||||
# OAuth and SSO
|
||||
Flask-Dance[sqla]==6.2.0
|
||||
blinker==1.6.2
|
||||
@ -33,6 +33,21 @@
|
||||
{{ form.submit(class="btn btn-primary") }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if google_configured %}
|
||||
<div class="mt-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<hr class="flex-grow-1">
|
||||
<span class="mx-2 text-muted">OR</span>
|
||||
<hr class="flex-grow-1">
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<a href="{{ url_for('google.login') }}" class="btn btn-outline-danger">
|
||||
<i class="fab fa-google me-2"></i> Sign in with Google
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<p>New User? <a href="{{ url_for('auth.register') }}">Register here</a></p>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user