initial
This commit is contained in:
124
backend/routes/auth.py
Normal file
124
backend/routes/auth.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from flask import Blueprint, request, jsonify, redirect, make_response, current_app, g
|
||||
from backend.auth import oauth
|
||||
from backend.auth.middleware import require_auth
|
||||
from backend.models import db, User
|
||||
from datetime import datetime
|
||||
|
||||
bp = Blueprint('auth', __name__, url_prefix='/api/auth')
|
||||
|
||||
|
||||
@bp.route('/login')
|
||||
def login():
|
||||
"""Redirect to Authelia login page"""
|
||||
redirect_uri = current_app.config['OIDC_REDIRECT_URI']
|
||||
return oauth.authelia.authorize_redirect(redirect_uri)
|
||||
|
||||
|
||||
@bp.route('/callback')
|
||||
def callback():
|
||||
"""Handle OIDC callback from Authelia"""
|
||||
try:
|
||||
# Exchange authorization code for tokens
|
||||
token = oauth.authelia.authorize_access_token()
|
||||
|
||||
# Parse ID token to get user info
|
||||
user_info = token.get('userinfo')
|
||||
if not user_info:
|
||||
user_info = oauth.authelia.parse_id_token(token)
|
||||
|
||||
# Get or create user
|
||||
user = User.query.filter_by(authelia_sub=user_info['sub']).first()
|
||||
|
||||
if not user:
|
||||
user = User(
|
||||
authelia_sub=user_info['sub'],
|
||||
email=user_info.get('email'),
|
||||
name=user_info.get('name'),
|
||||
preferred_username=user_info.get('preferred_username'),
|
||||
groups=user_info.get('groups', [])
|
||||
)
|
||||
db.session.add(user)
|
||||
else:
|
||||
user.email = user_info.get('email')
|
||||
user.name = user_info.get('name')
|
||||
user.preferred_username = user_info.get('preferred_username')
|
||||
user.groups = user_info.get('groups', [])
|
||||
user.last_login = datetime.utcnow()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Redirect to frontend with tokens in URL fragment (SPA pattern)
|
||||
frontend_url = current_app.config.get('FRONTEND_URL', 'http://localhost:3000')
|
||||
|
||||
# Create response with refresh token in HTTP-only cookie
|
||||
response = make_response(redirect(
|
||||
f"{frontend_url}/auth/callback#access_token={token['access_token']}"
|
||||
f"&id_token={token['id_token']}"
|
||||
f"&expires_in={token.get('expires_in', 900)}"
|
||||
))
|
||||
|
||||
# Set refresh token as HTTP-only cookie
|
||||
if token.get('refresh_token'):
|
||||
response.set_cookie(
|
||||
'refresh_token',
|
||||
value=token['refresh_token'],
|
||||
httponly=True,
|
||||
secure=current_app.config.get('SESSION_COOKIE_SECURE', False),
|
||||
samesite='Strict',
|
||||
max_age=7*24*60*60 # 7 days
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"OIDC callback error: {e}")
|
||||
frontend_url = current_app.config.get('FRONTEND_URL', 'http://localhost:3000')
|
||||
return redirect(f"{frontend_url}/login?error=auth_failed")
|
||||
|
||||
|
||||
@bp.route('/refresh', methods=['POST'])
|
||||
def refresh():
|
||||
"""Refresh access token using refresh token"""
|
||||
refresh_token = request.cookies.get('refresh_token')
|
||||
|
||||
if not refresh_token:
|
||||
return jsonify({'error': 'No refresh token'}), 401
|
||||
|
||||
try:
|
||||
# Exchange refresh token for new access token
|
||||
new_token = oauth.authelia.fetch_access_token(
|
||||
grant_type='refresh_token',
|
||||
refresh_token=refresh_token
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'access_token': new_token['access_token'],
|
||||
'expires_in': new_token.get('expires_in', 900)
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Token refresh failed: {e}")
|
||||
return jsonify({'error': 'Token refresh failed'}), 401
|
||||
|
||||
|
||||
@bp.route('/logout', methods=['POST'])
|
||||
def logout():
|
||||
"""Logout user and revoke tokens"""
|
||||
# Clear refresh token cookie
|
||||
response = make_response(jsonify({'message': 'Logged out'}), 200)
|
||||
response.set_cookie('refresh_token', '', expires=0)
|
||||
|
||||
# Return Authelia logout URL for frontend to redirect
|
||||
authelia_logout_url = f"{current_app.config['OIDC_ISSUER']}/logout"
|
||||
|
||||
return jsonify({
|
||||
'message': 'Logged out',
|
||||
'logout_url': authelia_logout_url
|
||||
}), 200
|
||||
|
||||
|
||||
@bp.route('/me')
|
||||
@require_auth
|
||||
def get_current_user():
|
||||
"""Get current user info (requires auth)"""
|
||||
return jsonify(g.current_user.to_dict()), 200
|
||||
Reference in New Issue
Block a user