Add Entra ID authentication with role-based access control
Implement Microsoft Entra ID (Azure AD) authentication with user and admin roles. Features: - OAuth 2.0 authentication flow using MSAL - User and admin role-based access control - Tenant ownership and access management - Server-side session storage - Protected routes with authentication decorators Authentication: - Users authenticate via Microsoft organizational accounts - Admin role automatically assigned to DEFAULT_ADMIN_EMAIL - User profiles synced from Microsoft Graph API Access Control: - Admins: Full access to all tenants and server settings - Users: Can create and manage their own tenants only - Tenant association: Auto-created when user adds products - API endpoints remain open for AR application access Database Changes: - New users table with email, azure_oid, name, and role - New user_tenants table for many-to-many tenant ownership - Case-insensitive admin email comparison Configuration: - Environment-based config via .env file - Azure AD credentials (client ID, secret, tenant ID) - Configurable redirect URI and admin email - Updated to Flask 2.2.5 for compatibility
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
from flask import render_template, request, redirect, url_for, flash, jsonify, current_app
|
||||
from flask import render_template, request, redirect, url_for, flash, jsonify, current_app, session
|
||||
from . import admin_bp
|
||||
from app.models import TenantModel, ProductModel, ARFieldModel
|
||||
from app.models import TenantModel, ProductModel, ARFieldModel, UserModel
|
||||
from app.services import ProductService
|
||||
from app.decorators.auth import tenant_access_required
|
||||
|
||||
@admin_bp.route('/add', methods=['GET', 'POST'])
|
||||
@tenant_access_required
|
||||
def add_product(tenant_id):
|
||||
"""Add a new product"""
|
||||
custom_fields = ARFieldModel.get_all(tenant_id)
|
||||
@@ -128,12 +130,18 @@ def add_product(tenant_id):
|
||||
for img in images_to_save:
|
||||
ProductModel.save_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type'])
|
||||
|
||||
# Associate the tenant with the user if not already associated
|
||||
user_id = session['user']['id']
|
||||
if not UserModel.has_access_to_tenant(user_id, tenant_id):
|
||||
UserModel.add_tenant(user_id, tenant_id)
|
||||
|
||||
flash('Product added successfully!')
|
||||
return redirect(f'/{tenant_id}/')
|
||||
|
||||
return render_template('admin/add_product.html', tenant_id=tenant_id, custom_fields=custom_fields)
|
||||
|
||||
@admin_bp.route('/edit/<product_id>', methods=['GET', 'POST'])
|
||||
@tenant_access_required
|
||||
def edit_product(tenant_id, product_id):
|
||||
"""Edit an existing product"""
|
||||
products = ProductModel.get_all(tenant_id)
|
||||
@@ -253,6 +261,7 @@ def edit_product(tenant_id, product_id):
|
||||
custom_fields=custom_fields)
|
||||
|
||||
@admin_bp.route('/delete/<product_id>', methods=['POST'])
|
||||
@tenant_access_required
|
||||
def delete_product(tenant_id, product_id):
|
||||
"""Delete a product"""
|
||||
products = ProductModel.get_all(tenant_id)
|
||||
@@ -266,6 +275,7 @@ def delete_product(tenant_id, product_id):
|
||||
return redirect(f'/{tenant_id}/')
|
||||
|
||||
@admin_bp.route('/generate_barcode/<product_id>/<code_type>')
|
||||
@tenant_access_required
|
||||
def generate_barcode(tenant_id, product_id, code_type):
|
||||
"""Redirect to the main barcode generation endpoint"""
|
||||
products = ProductModel.get_all(tenant_id)
|
||||
@@ -282,6 +292,7 @@ def generate_barcode(tenant_id, product_id, code_type):
|
||||
return redirect(f'/{tenant_id}/barcodes/{product_id}_{barcode_type}.png')
|
||||
|
||||
@admin_bp.route('/barcodes', methods=['GET'])
|
||||
@tenant_access_required
|
||||
def view_all_barcodes(tenant_id):
|
||||
"""Display all product barcodes for printing"""
|
||||
products = ProductModel.get_all(tenant_id)
|
||||
@@ -295,6 +306,7 @@ def view_all_barcodes(tenant_id):
|
||||
barcode_type=barcode_type)
|
||||
|
||||
@admin_bp.route('/ar_fields', methods=['GET', 'POST'])
|
||||
@tenant_access_required
|
||||
def manage_ar_fields(tenant_id):
|
||||
"""Manage custom AR content fields"""
|
||||
tenant = TenantModel.get_or_create(tenant_id)
|
||||
|
||||
Reference in New Issue
Block a user