Google AUTH and Readme

This commit is contained in:
Bobby Abellana 2025-03-21 11:34:58 -07:00
parent 5a900e0ded
commit bac1d84b09
8 changed files with 278 additions and 10 deletions

89
README.md Normal file
View 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
View File

@ -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)

View File

@ -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

View 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()

View File

@ -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
View 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

View File

@ -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
@ -40,3 +42,7 @@ 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
# OAuth and SSO
Flask-Dance[sqla]==6.2.0
blinker==1.6.2

View File

@ -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>