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,10 +1,12 @@
|
||||
from flask import render_template, request, redirect, flash, jsonify, Response, current_app
|
||||
from flask import render_template, request, redirect, flash, jsonify, Response, current_app, session
|
||||
from . import tenant_bp
|
||||
from app.models import TenantModel, ProductModel, ARFieldModel, SettingsModel
|
||||
from app.models import TenantModel, ProductModel, ARFieldModel, SettingsModel, UserModel
|
||||
from app.services import AuthService, ProductService, BarcodeService
|
||||
from app.decorators.auth import tenant_access_required
|
||||
import os
|
||||
|
||||
@tenant_bp.route('/')
|
||||
@tenant_access_required
|
||||
def index(tenant_id):
|
||||
"""Tenant home page - product listing"""
|
||||
tenant = TenantModel.get_or_create(tenant_id)
|
||||
@@ -17,13 +19,19 @@ def index(tenant_id):
|
||||
return render_template('index.html', tenant=tenant, products=products, custom_fields=custom_fields)
|
||||
|
||||
@tenant_bp.route('/delete', methods=['POST'])
|
||||
@tenant_access_required
|
||||
def delete_tenant(tenant_id):
|
||||
"""Delete a tenant and all its data"""
|
||||
# Also remove the user's association with this tenant
|
||||
user_id = session['user']['id']
|
||||
UserModel.remove_tenant(user_id, tenant_id)
|
||||
|
||||
TenantModel.delete(tenant_id)
|
||||
flash(f'Tenant "{tenant_id}" has been deleted successfully.', 'success')
|
||||
return redirect('/')
|
||||
|
||||
@tenant_bp.route('/settings')
|
||||
@tenant_access_required
|
||||
def settings(tenant_id):
|
||||
"""Tenant settings page"""
|
||||
tenant = TenantModel.get_or_create(tenant_id)
|
||||
@@ -36,6 +44,7 @@ def settings(tenant_id):
|
||||
return render_template('tenant_settings.html', tenant=tenant, custom_fields=custom_fields, server_url=server_url)
|
||||
|
||||
@tenant_bp.route('/settings/credentials', methods=['POST'])
|
||||
@tenant_access_required
|
||||
def update_credentials(tenant_id):
|
||||
"""Update tenant credentials"""
|
||||
tenant = TenantModel.get_by_id(tenant_id)
|
||||
@@ -54,6 +63,7 @@ def update_credentials(tenant_id):
|
||||
return redirect(f'/{tenant_id}/settings')
|
||||
|
||||
@tenant_bp.route('/settings/barcode', methods=['POST'])
|
||||
@tenant_access_required
|
||||
def update_barcode_type(tenant_id):
|
||||
"""Update tenant barcode type"""
|
||||
tenant = TenantModel.get_by_id(tenant_id)
|
||||
|
||||
Reference in New Issue
Block a user