Adds auth module
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# ================================================================
|
||||
# services/auth/app/services/auth_service.py (COMPLETE VERSION)
|
||||
# ================================================================
|
||||
"""
|
||||
Authentication service business logic
|
||||
Authentication service business logic - Complete implementation
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -59,9 +62,9 @@ class AuthService:
|
||||
"user_events",
|
||||
"user.registered",
|
||||
UserRegisteredEvent(
|
||||
event_id="",
|
||||
event_id=str(user.id),
|
||||
service_name="auth-service",
|
||||
timestamp= datetime.now(datetime.timezone.utc),
|
||||
timestamp=datetime.now(datetime.timezone.utc),
|
||||
data={
|
||||
"user_id": str(user.id),
|
||||
"email": user.email,
|
||||
@@ -111,12 +114,13 @@ class AuthService:
|
||||
await db.execute(
|
||||
update(User)
|
||||
.where(User.id == user.id)
|
||||
.values(last_login= datetime.now(datetime.timezone.utc))
|
||||
.values(last_login=datetime.now(datetime.timezone.utc))
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
# Create tokens
|
||||
token_data = {
|
||||
"sub": str(user.id), # Standard JWT claim for subject
|
||||
"user_id": str(user.id),
|
||||
"email": user.email,
|
||||
"tenant_id": str(user.tenant_id) if user.tenant_id else None,
|
||||
@@ -132,10 +136,10 @@ class AuthService:
|
||||
# Create session record
|
||||
session = UserSession(
|
||||
user_id=user.id,
|
||||
refresh_token_hash=security_manager.hash_password(refresh_token),
|
||||
expires_at= datetime.now(datetime.timezone.utc) + timedelta(days=7),
|
||||
refresh_token_hash=security_manager.hash_token(refresh_token),
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent
|
||||
user_agent=user_agent,
|
||||
expires_at=datetime.now(datetime.timezone.utc) + timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS)
|
||||
)
|
||||
|
||||
db.add(session)
|
||||
@@ -146,9 +150,9 @@ class AuthService:
|
||||
"user_events",
|
||||
"user.login",
|
||||
UserLoginEvent(
|
||||
event_id="",
|
||||
event_id=str(session.id),
|
||||
service_name="auth-service",
|
||||
timestamp= datetime.now(datetime.timezone.utc),
|
||||
timestamp=datetime.now(datetime.timezone.utc),
|
||||
data={
|
||||
"user_id": str(user.id),
|
||||
"email": user.email,
|
||||
@@ -158,38 +162,39 @@ class AuthService:
|
||||
).__dict__
|
||||
)
|
||||
|
||||
logger.info(f"User logged in: {user.email}")
|
||||
logger.info(f"User login successful: {user.email}")
|
||||
|
||||
return TokenResponse(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
token_type="bearer",
|
||||
expires_in=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def refresh_token(refresh_token: str, db: AsyncSession) -> TokenResponse:
|
||||
"""Refresh access token"""
|
||||
"""Refresh access token using refresh token"""
|
||||
|
||||
# Verify refresh token
|
||||
payload = security_manager.verify_token(refresh_token)
|
||||
if not payload or payload.get("type") != "refresh":
|
||||
token_data = security_manager.verify_token(refresh_token)
|
||||
if not token_data or token_data.get("type") != "refresh":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token"
|
||||
)
|
||||
|
||||
user_id = payload.get("user_id")
|
||||
user_id = token_data.get("user_id")
|
||||
if not user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token"
|
||||
detail="Invalid token data"
|
||||
)
|
||||
|
||||
# Verify refresh token is stored
|
||||
# Check if refresh token exists in Redis
|
||||
if not await security_manager.verify_refresh_token(user_id, refresh_token):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token"
|
||||
detail="Refresh token not found or expired"
|
||||
)
|
||||
|
||||
# Get user
|
||||
@@ -205,80 +210,68 @@ class AuthService:
|
||||
)
|
||||
|
||||
# Create new tokens
|
||||
token_data = {
|
||||
new_token_data = {
|
||||
"sub": str(user.id),
|
||||
"user_id": str(user.id),
|
||||
"email": user.email,
|
||||
"tenant_id": str(user.tenant_id) if user.tenant_id else None,
|
||||
"role": user.role
|
||||
}
|
||||
|
||||
new_access_token = security_manager.create_access_token(token_data)
|
||||
new_refresh_token = security_manager.create_refresh_token(token_data)
|
||||
new_access_token = security_manager.create_access_token(new_token_data)
|
||||
new_refresh_token = security_manager.create_refresh_token(new_token_data)
|
||||
|
||||
# Update stored refresh token
|
||||
await security_manager.store_refresh_token(str(user.id), new_refresh_token)
|
||||
# Revoke old refresh token
|
||||
await security_manager.revoke_refresh_token(user_id, refresh_token)
|
||||
|
||||
# Store new refresh token
|
||||
await security_manager.store_refresh_token(user_id, new_refresh_token)
|
||||
|
||||
logger.info(f"Token refreshed for user: {user.email}")
|
||||
|
||||
return TokenResponse(
|
||||
access_token=new_access_token,
|
||||
refresh_token=new_refresh_token,
|
||||
token_type="bearer",
|
||||
expires_in=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def verify_token(token: str, db: AsyncSession) -> Dict[str, Any]:
|
||||
"""Verify access token"""
|
||||
async def verify_token(token: str) -> Dict[str, Any]:
|
||||
"""Verify access token and return user data"""
|
||||
|
||||
# Verify token
|
||||
payload = security_manager.verify_token(token)
|
||||
if not payload or payload.get("type") != "access":
|
||||
token_data = security_manager.verify_token(token)
|
||||
if not token_data or token_data.get("type") != "access":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token"
|
||||
detail="Invalid access token"
|
||||
)
|
||||
|
||||
user_id = payload.get("user_id")
|
||||
if not user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid token"
|
||||
)
|
||||
|
||||
# Get user
|
||||
result = await db.execute(
|
||||
select(User).where(User.id == user_id)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if not user or not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="User not found or inactive"
|
||||
)
|
||||
|
||||
return {
|
||||
"user_id": str(user.id),
|
||||
"email": user.email,
|
||||
"tenant_id": str(user.tenant_id) if user.tenant_id else None,
|
||||
"role": user.role,
|
||||
"full_name": user.full_name,
|
||||
"is_verified": user.is_verified
|
||||
}
|
||||
return token_data
|
||||
|
||||
@staticmethod
|
||||
async def logout_user(user_id: str, db: AsyncSession):
|
||||
async def logout_user(user_id: str, refresh_token: str, db: AsyncSession) -> bool:
|
||||
"""Logout user and revoke tokens"""
|
||||
|
||||
# Revoke refresh token
|
||||
await security_manager.revoke_refresh_token(user_id)
|
||||
|
||||
# Deactivate user sessions
|
||||
await db.execute(
|
||||
update(UserSession)
|
||||
.where(UserSession.user_id == user_id)
|
||||
.values(is_active=False)
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
logger.info(f"User logged out: {user_id}")
|
||||
try:
|
||||
# Revoke refresh token
|
||||
await security_manager.revoke_refresh_token(user_id, refresh_token)
|
||||
|
||||
# Deactivate session
|
||||
await db.execute(
|
||||
update(UserSession)
|
||||
.where(
|
||||
UserSession.user_id == user_id,
|
||||
UserSession.refresh_token_hash == security_manager.hash_token(refresh_token)
|
||||
)
|
||||
.values(is_active=False)
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
logger.info(f"User logged out: {user_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error logging out user {user_id}: {e}")
|
||||
await db.rollback()
|
||||
return False
|
||||
Reference in New Issue
Block a user