Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.250.128"
VERSION = "0.250.129"
IS_DEVELOPMENT = is_development_env_enabled()

SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
Expand Down
9 changes: 7 additions & 2 deletions application/single_app/route_frontend_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,13 @@ def authorized():

code = request.args.get('code')
if not code:
print("Authorization code not found in callback.")
return "Authorization code not found", 400
log_event(
"[AUTH_CALLBACK] OAuth callback reached without an authorization code; redirecting to sign-in.",
extra={'path': request.path},
level=logging.INFO,
debug_only=True,
)
return redirect(url_for('public_app.index'))

# Build MSAL app WITH session cache (will be loaded by _build_msal_app via _load_cache)
msal_app = _build_msal_app(cache=_load_cache()) # Load existing cache
Expand Down
37 changes: 37 additions & 0 deletions docs/explanation/fixes/GETATOKEN_MISSING_CODE_REDIRECT_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# getAToken Missing Code Redirect Fix

Fixed/Implemented in version: **0.250.129**

## Issue Description

Unauthenticated users who browsed directly to protected SimpleChat pages could be redirected to `/getAToken` without first completing Microsoft Entra sign-in. Because the OAuth callback did not receive an authorization `code`, the page returned an "Authorization code not found" error and created avoidable support tickets.

## Root Cause Analysis

The `/getAToken` frontend OAuth callback treated every request without a `code` query parameter as a failed callback. Direct browser visits to the callback path are not valid token exchanges, but they are recoverable user navigation events and should route users back to the normal sign-in entry point.

## Technical Details

Files modified:

- `application/single_app/route_frontend_authentication.py`
- `application/single_app/config.py`
- `functional_tests/test_getatoken_missing_code_redirect.py`

Code changes summary:

- Updated the `/getAToken` callback missing-code branch to log the recoverable condition and redirect to `public_app.index`.
- Preserved the valid OAuth authorization-code exchange flow.
- Left `/getATokenApi` unchanged so API token callback callers still receive explicit request errors.
- Updated `config.py` version to `0.250.129` after merging the latest `Development` changes.

## Validation

Testing approach:

- Added a focused functional regression test that verifies the `/getAToken` missing-code branch redirects to the home sign-in route instead of returning the previous error text.

Impact analysis:

- Users see the normal SimpleChat sign-in entry point rather than a technical OAuth callback error.
- Valid Microsoft Entra callback requests with authorization codes continue through the existing token redemption path.
9 changes: 9 additions & 0 deletions docs/explanation/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

For feature-focused and fix-focused drill-downs by version, see [Features by Version](/explanation/features/) and [Fixes by Version](/explanation/fixes/).

### **(v0.250.129)**

#### Bug Fixes

* **getAToken Missing Authorization Code Redirect**
* Redirects direct `/getAToken` browser visits without an OAuth authorization code back to the home sign-in page instead of showing a technical callback error.
* Preserves the normal Microsoft Entra authorization-code callback flow and keeps `/getATokenApi` explicit error behavior unchanged for API token callbacks.
* (Ref: `/getAToken` OAuth callback, `route_frontend_authentication.py`, `test_getatoken_missing_code_redirect.py`)

### **(v0.250.128)**

#### Bug Fixes
Expand Down
100 changes: 100 additions & 0 deletions functional_tests/test_getatoken_missing_code_redirect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# test_getatoken_missing_code_redirect.py
"""
Functional test for direct getAToken callback visits without an OAuth code.
Version: 0.250.129
Implemented in: 0.250.129

This test ensures that users who reach /getAToken directly are redirected to
the home sign-in page instead of seeing an authorization-code error.
"""

import ast
import sys
from pathlib import Path


ROOT_DIR = Path(__file__).resolve().parents[1]
AUTH_ROUTE_PATH = ROOT_DIR / "application" / "single_app" / "route_frontend_authentication.py"


def _find_authorized_function(tree):
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef) and node.name == "authorized":
return node
raise AssertionError("Could not find the /getAToken authorized route function.")


def _is_missing_code_branch(node):
return (
isinstance(node, ast.If)
and isinstance(node.test, ast.UnaryOp)
and isinstance(node.test.op, ast.Not)
and isinstance(node.test.operand, ast.Name)
and node.test.operand.id == "code"
)


def _returns_home_redirect(node):
if not isinstance(node, ast.Return):
return False
value = node.value
return (
isinstance(value, ast.Call)
and isinstance(value.func, ast.Name)
and value.func.id == "redirect"
and len(value.args) == 1
and isinstance(value.args[0], ast.Call)
and isinstance(value.args[0].func, ast.Name)
and value.args[0].func.id == "url_for"
and len(value.args[0].args) == 1
and isinstance(value.args[0].args[0], ast.Constant)
and value.args[0].args[0].value == "public_app.index"
)


def _returns_authorization_code_error(node):
if not isinstance(node, ast.Return):
return False
value = node.value
if isinstance(value, ast.Constant):
return value.value == "Authorization code not found"
if isinstance(value, ast.Tuple):
return any(
isinstance(element, ast.Constant)
and element.value == "Authorization code not found"
for element in value.elts
)
return False


def test_getatoken_missing_code_redirects_home():
"""Validate that /getAToken without a code redirects to the sign-in entry point."""
print("Testing /getAToken missing authorization-code redirect...")

tree = ast.parse(AUTH_ROUTE_PATH.read_text(encoding="utf-8"))
authorized_function = _find_authorized_function(tree)
missing_code_branches = [
node for node in ast.walk(authorized_function) if _is_missing_code_branch(node)
]

if len(missing_code_branches) != 1:
raise AssertionError(f"Expected exactly one missing-code branch, found {len(missing_code_branches)}.")

missing_code_branch = missing_code_branches[0]
if not any(_returns_home_redirect(node) for node in missing_code_branch.body):
raise AssertionError("Expected missing-code branch to redirect to public_app.index.")

if any(_returns_authorization_code_error(node) for node in missing_code_branch.body):
raise AssertionError("Missing-code branch must not return the authorization-code error to users.")

print("/getAToken missing-code requests redirect to the sign-in entry point.")


if __name__ == "__main__":
try:
test_getatoken_missing_code_redirects_home()
except Exception as exc:
print(f"Test failed: {exc}")
sys.exit(1)

print("All getAToken missing-code redirect tests passed")