diff --git a/.env.example b/.env.example index 0cb83e9..e1ad6b5 100644 --- a/.env.example +++ b/.env.example @@ -8,7 +8,7 @@ REDIS_URL=redis://redis:6379/0 AZURE_AD_TENANT_ID= AZURE_AD_CLIENT_ID= AZURE_AD_CLIENT_SECRET= -AZURE_AD_REDIRECT_URI=http://localhost/api/v1/auth/callback +AZURE_AD_REDIRECT_URI=https://yourdomain.com/api/v1/auth/callback # JWT JWT_SECRET_KEY=change-me-to-a-random-256-bit-key diff --git a/backend/api/v1/auth/router.py b/backend/api/v1/auth/router.py index 20de54c..e1784ae 100644 --- a/backend/api/v1/auth/router.py +++ b/backend/api/v1/auth/router.py @@ -1,3 +1,4 @@ +import os import uuid from fastapi import APIRouter, Depends, HTTPException, Response, Request from fastapi.responses import RedirectResponse, JSONResponse @@ -20,8 +21,12 @@ class DevLoginRequest(BaseModel): @AUTH_ROUTER.get("/dev-status") async def dev_status(): - """Check if dev auth mode is enabled.""" - return {"dev_mode": auth_service.is_dev_mode} + """Check auth modes available.""" + return { + "dev_mode": auth_service.is_dev_mode, + "dev_login_enabled": auth_service.dev_login_enabled, + "azure_enabled": not auth_service.is_dev_mode, + } @AUTH_ROUTER.get("/login") @@ -63,12 +68,14 @@ async def callback( user = await auth_service.get_or_create_user(claims, session) token = auth_service.create_session_jwt(user) - response = RedirectResponse(url="/dashboard", status_code=302) + base_path = os.environ.get("NEXT_PUBLIC_BASE_PATH", "") + is_https = os.environ.get("COOKIE_SECURE", "false").lower() == "true" + response = RedirectResponse(url=f"{base_path}/dashboard", status_code=302) response.set_cookie( key="session_token", value=token, httponly=True, - secure=False, # Set True in production with HTTPS + secure=is_https, samesite="lax", max_age=86400, # 24 hours ) @@ -86,8 +93,8 @@ async def dev_login( body: DevLoginRequest, session: AsyncSession = Depends(get_async_session), ): - """Dev-mode login with email and password. Only available when Azure AD is not configured.""" - if not auth_service.is_dev_mode: + """Dev login with email and password. Available when DEV_AUTH_PASSWORD is set.""" + if not auth_service.dev_login_enabled: raise HTTPException(status_code=404, detail="Dev login not available") user = await auth_service.dev_login(body.email, body.password, session) @@ -96,6 +103,7 @@ async def dev_login( token = auth_service.create_session_jwt(user) + is_https = os.environ.get("COOKIE_SECURE", "false").lower() == "true" response = JSONResponse( content={ "id": str(user.id), @@ -108,7 +116,7 @@ async def dev_login( key="session_token", value=token, httponly=True, - secure=False, + secure=is_https, samesite="lax", max_age=86400, ) diff --git a/backend/services/auth_service.py b/backend/services/auth_service.py index 20b505a..d3b7e5d 100644 --- a/backend/services/auth_service.py +++ b/backend/services/auth_service.py @@ -35,6 +35,11 @@ class AuthService: def is_dev_mode(self) -> bool: return not self.tenant_id + @property + def dev_login_enabled(self) -> bool: + """Dev login is available whenever DEV_AUTH_PASSWORD is set, regardless of Azure AD config.""" + return bool(self.dev_password) + @property def msal_app(self): if self._msal_app is None and not self.is_dev_mode: @@ -148,8 +153,8 @@ class AuthService: return user async def dev_login(self, email: str, password: str, session) -> Optional[UserModel]: - """Dev-mode login: validate password, get or create user.""" - if not self.is_dev_mode: + """Dev login: validate password, get or create user. Works even when Azure AD is configured.""" + if not self.dev_login_enabled: return None if password != self.dev_password: return None diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index df8b647..a670e5c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -22,6 +22,8 @@ services: - "127.0.0.1:${API_PORT:-8000}:8000" environment: PYTHONUNBUFFERED: "1" + NEXT_PUBLIC_BASE_PATH: "/ppt-tool" + COOKIE_SECURE: "true" worker: environment: diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index 52d10d6..d16834d 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -4,17 +4,18 @@ import { useEffect, useState } from 'react'; import { apiFetch } from '../../lib/apiFetch'; import { useSelector } from 'react-redux'; import { RootState } from '@/store/store'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; export default function LoginPage() { const router = useRouter(); - const { isAuthenticated, isDevMode } = useSelector( + const searchParams = useSearchParams(); + const { isAuthenticated, isDevMode, devLoginEnabled, azureEnabled } = useSelector( (state: RootState) => state.auth ); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const [error, setError] = useState(''); + const [error, setError] = useState(searchParams.get('error') || ''); const [loading, setLoading] = useState(false); useEffect(() => { @@ -74,7 +75,7 @@ export default function LoginPage() {
- {!isDevMode && ( + {azureEnabled && (