Files
shopdb-flask/shopdb/core/api/auth.py
cproudlock 1196de6e88 Initial commit: Shop Database Flask Application
Flask backend with Vue 3 frontend for shop floor machine management.
Includes database schema export for MySQL shopdb_flask database.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 16:07:34 -05:00

148 lines
3.9 KiB
Python

"""Authentication API endpoints."""
from flask import Blueprint, request
from flask_jwt_extended import (
create_access_token,
create_refresh_token,
jwt_required,
get_jwt_identity,
current_user
)
from werkzeug.security import check_password_hash
from shopdb.extensions import db
from shopdb.core.models import User
from shopdb.utils.responses import success_response, error_response, ErrorCodes
auth_bp = Blueprint('auth', __name__)
@auth_bp.route('/login', methods=['POST'])
def login():
"""
Authenticate user and return JWT tokens.
Request:
{
"username": "string",
"password": "string"
}
Response:
{
"data": {
"access_token": "...",
"refresh_token": "...",
"user": {...}
}
}
"""
data = request.get_json()
if not data or not data.get('username') or not data.get('password'):
return error_response(
ErrorCodes.VALIDATION_ERROR,
'Username and password required'
)
user = User.query.filter_by(
username=data['username'],
isactive=True
).first()
if not user or not check_password_hash(user.passwordhash, data['password']):
return error_response(
ErrorCodes.UNAUTHORIZED,
'Invalid username or password',
http_code=401
)
if user.islocked:
return error_response(
ErrorCodes.FORBIDDEN,
'Account is locked',
http_code=403
)
# Create tokens (identity must be a string in Flask-JWT-Extended 4.x)
access_token = create_access_token(
identity=str(user.userid),
additional_claims={
'username': user.username,
'roles': [r.rolename for r in user.roles]
}
)
refresh_token = create_refresh_token(identity=str(user.userid))
# Update last login
user.lastlogindate = db.func.now()
user.failedlogins = 0
db.session.commit()
return success_response({
'access_token': access_token,
'refresh_token': refresh_token,
'token_type': 'Bearer',
'expires_in': 3600,
'user': {
'userid': user.userid,
'username': user.username,
'email': user.email,
'firstname': user.firstname,
'lastname': user.lastname,
'roles': [r.rolename for r in user.roles]
}
})
@auth_bp.route('/refresh', methods=['POST'])
@jwt_required(refresh=True)
def refresh():
"""Refresh access token using refresh token."""
user_id = get_jwt_identity()
user = User.query.get(int(user_id))
if not user or not user.isactive:
return error_response(
ErrorCodes.UNAUTHORIZED,
'User not found or inactive',
http_code=401
)
access_token = create_access_token(
identity=str(user.userid),
additional_claims={
'username': user.username,
'roles': [r.rolename for r in user.roles]
}
)
return success_response({
'access_token': access_token,
'token_type': 'Bearer',
'expires_in': 3600
})
@auth_bp.route('/me', methods=['GET'])
@jwt_required()
def get_current_user():
"""Get current authenticated user info."""
return success_response({
'userid': current_user.userid,
'username': current_user.username,
'email': current_user.email,
'firstname': current_user.firstname,
'lastname': current_user.lastname,
'roles': [r.rolename for r in current_user.roles],
'permissions': current_user.getpermissions()
})
@auth_bp.route('/logout', methods=['POST'])
@jwt_required()
def logout():
"""Logout user (for frontend token cleanup)."""
# In a full implementation, you'd blacklist the token
return success_response(message='Successfully logged out')