diff --git a/backend/trip/__init__.py b/backend/trip/__init__.py index 2d986fc..320141d 100644 --- a/backend/trip/__init__.py +++ b/backend/trip/__init__.py @@ -1 +1 @@ -__version__ = "1.8.1" +__version__ = "1.8.2" diff --git a/backend/trip/routers/auth.py b/backend/trip/routers/auth.py index c44637d..cfb153b 100644 --- a/backend/trip/routers/auth.py +++ b/backend/trip/routers/auth.py @@ -1,5 +1,6 @@ import jwt -from fastapi import APIRouter, Body, HTTPException +from fastapi import APIRouter, Body, Cookie, HTTPException +from fastapi.responses import JSONResponse from ..config import settings from ..db.core import init_user_data @@ -16,21 +17,34 @@ router = APIRouter(prefix="/api/auth", tags=["auth"]) async def auth_params() -> AuthParams: data = {"oidc": None, "register_enabled": settings.REGISTER_ENABLE} + response = JSONResponse(content=data) if settings.OIDC_CLIENT_ID and settings.OIDC_CLIENT_SECRET: oidc_config = await get_oidc_config() auth_endpoint = oidc_config.get("authorization_endpoint") - data["oidc"] = ( - f"{auth_endpoint}?client_id={settings.OIDC_CLIENT_ID}&redirect_uri={settings.OIDC_REDIRECT_URI}&response_type=code&scope=openid+profile" + uri, state = get_oidc_client().create_authorization_url(auth_endpoint) + data["oidc"] = uri + + response = JSONResponse(content=data) + response.set_cookie( + "oidc_state", value=state, httponly=True, secure=True, samesite="Lax", max_age=60 ) - return data + return response @router.post("/oidc/login", response_model=Token) -async def oidc_login(session: SessionDep, code: str = Body(..., embed=True)) -> Token: +async def oidc_login( + session: SessionDep, + code: str = Body(..., embed=True), + state: str = Body(..., embed=True), + oidc_state: str = Cookie(None), +) -> Token: if not (settings.OIDC_CLIENT_ID or settings.OIDC_CLIENT_SECRET): raise HTTPException(status_code=400, detail="Partial OIDC config") + if not oidc_state or state != oidc_state: + raise HTTPException(status_code=400, detail="OIDC login failed, invalid state") + oidc_config = await get_oidc_config() token_endpoint = oidc_config.get("token_endpoint") try: diff --git a/src/src/app/components/auth/auth.component.ts b/src/src/app/components/auth/auth.component.ts index 975ff66..48e1194 100644 --- a/src/src/app/components/auth/auth.component.ts +++ b/src/src/app/components/auth/auth.component.ts @@ -47,9 +47,11 @@ export class AuthComponent { private fb: FormBuilder, ) { this.route.queryParams.subscribe((params) => { + if (!Object.keys(params).length) return; const code = params["code"]; - if (code) { - this.authService.oidcLogin(code).subscribe({ + const state = params["state"]; + if (code && state) { + this.authService.oidcLogin(code, state).subscribe({ next: (data) => { if (!data.access_token) { this.error = "Authentication failed"; @@ -61,9 +63,12 @@ export class AuthComponent { } }); - this.authService.authParams().subscribe({ - next: (params) => (this.authParams = params), - }); + // Timeout to handle race condition + setTimeout(() => { + this.authService.authParams().subscribe({ + next: (params) => (this.authParams = params), + }); + }, 100); this.redirectURL = this.route.snapshot.queryParams["redirectURL"] || "/home"; @@ -97,7 +102,7 @@ export class AuthComponent { authenticate(): void { this.error = ""; if (this.authParams?.oidc) { - window.location.replace(encodeURI(this.authParams.oidc)); + window.location.replace(this.authParams.oidc); } this.authService.login(this.authForm.value).subscribe({ diff --git a/src/src/app/services/auth.service.ts b/src/src/app/services/auth.service.ts index 9b0b6ac..aa57e2f 100644 --- a/src/src/app/services/auth.service.ts +++ b/src/src/app/services/auth.service.ts @@ -109,9 +109,9 @@ export class AuthService { ); } - oidcLogin(code: string): Observable { + oidcLogin(code: string, state: string): Observable { return this.httpClient - .post(this.apiBaseUrl + "/auth/oidc/login", { code }) + .post(this.apiBaseUrl + "/auth/oidc/login", { code, state }) .pipe( tap((data: any) => { if (data.access_token && data.refresh_token) {