ac-tool/backend/server/api/auth.py
Vadym Samoilenko 72c50b2c92 Initial commit — AC Tool unified application
Merges ac-helper (PHP Activation Calendar) and brief-extractor (Python AI)
into a single Docker app with React/TypeScript frontend.

Features:
- Brief upload → AI extraction → review → Activation Calendar import
- Handsontable v17 spreadsheet with dependent dropdowns (148 categories)
- AI natural language commands via Gemini (YOLO mode, voice input)
- Azure AD MSAL SPA PKCE authentication, user roles (user/admin)
- CSV Activation Calendar export
- Real-time WebSocket job progress
- Admin: user management, dropdown Excel upload
- Multi-stage Dockerfile, docker-compose, nginx proxy instructions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:24:46 +00:00

83 lines
2.6 KiB
Python

"""
Auth API endpoints.
"""
import logging
from quart import Blueprint, jsonify, request
from ..auth.msal_auth import msal_auth
from ..auth.middleware import auth_required, get_current_user
from ..auth.user_store import upsert_user
logger = logging.getLogger(__name__)
auth_bp = Blueprint('auth', __name__, url_prefix='/api/auth')
@auth_bp.route('/config', methods=['GET'])
async def get_auth_config():
return jsonify({'config': msal_auth.get_client_config(), 'devMode': msal_auth.is_dev_mode()})
@auth_bp.route('/validate', methods=['POST'])
async def validate_token():
try:
data = await request.get_json()
token = (data or {}).get('accessToken')
if not token:
return jsonify({'error': 'invalid_request', 'message': 'accessToken required'}), 400
user_info = await msal_auth.validate_token(token)
if not user_info:
return jsonify({'valid': False, 'error': 'invalid_token'}), 401
stored = upsert_user(user_info['oid'], user_info.get('preferred_username', ''), user_info.get('name', ''))
return jsonify({
'valid': True,
'user': {
'id': user_info['oid'],
'email': user_info.get('preferred_username'),
'name': user_info.get('name'),
'role': stored.get('role', 'user'),
},
})
except Exception as e:
logger.error(f"Token validation error: {e}")
return jsonify({'error': 'validation_error'}), 500
@auth_bp.route('/me', methods=['GET'])
@auth_required
async def me():
"""Return current user profile including role."""
from ..auth.user_store import get_user as get_stored_user
user = await get_current_user()
stored = get_stored_user(user['oid']) or {}
return jsonify({
'id': user['oid'],
'email': user.get('preferred_username'),
'name': user.get('name'),
'role': user.get('role', 'user'),
'active': stored.get('active', True),
'created': stored.get('created'),
'last_seen': stored.get('last_seen'),
})
@auth_bp.route('/user', methods=['GET'])
@auth_required
async def get_current_user_info():
user = await get_current_user()
return jsonify({'user': {
'id': user['oid'],
'username': user.get('preferred_username'),
'name': user.get('name'),
'role': user.get('role', 'user'),
}})
@auth_bp.route('/logout', methods=['POST'])
async def logout():
data = await request.get_json() or {}
logout_url = await msal_auth.get_logout_url(data.get('redirectUri'))
return jsonify({'logoutUrl': logout_url})