Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
eb1c98d
Add IBM Watsonx Data (AMS) auth support
edwinjosechittilappilly Mar 10, 2026
744ed5b
Parse IBM public key from JSON response
edwinjosechittilappilly Mar 11, 2026
03b14b3
Add user/pass support
mfortman11 Mar 12, 2026
e36c2e2
cookie update
mfortman11 Mar 13, 2026
204c012
cookie env
mfortman11 Mar 13, 2026
0834d76
logout suppport
mfortman11 Mar 16, 2026
c452a9b
Merge branch 'main' into IBM_AMS
edwinjosechittilappilly Mar 16, 2026
97c5873
raising an HTTPException(status_code=400/409, ...)
edwinjosechittilappilly Mar 16, 2026
63a9cbf
avoid unexpected attribute error in settings.py
edwinjosechittilappilly Mar 16, 2026
f75613f
Update .env.example
edwinjosechittilappilly Mar 16, 2026
2660e9c
Update ibm_auth.py
edwinjosechittilappilly Mar 16, 2026
80df21a
Merge branch 'main' into IBM_AMS
edwinjosechittilappilly Mar 16, 2026
49bb793
Merge branch 'main' into IBM_AMS
edwinjosechittilappilly Mar 16, 2026
ba84ae8
Merge branch 'main' into IBM_AMS
edwinjosechittilappilly Mar 17, 2026
58836c5
Handle IBM auth partial logout and cookie behavior
edwinjosechittilappilly Mar 17, 2026
24e2dac
Merge branch 'main' into IBM_AMS
edwinjosechittilappilly Mar 17, 2026
bdd446a
Support IBM header auth and persist credentials
edwinjosechittilappilly Mar 18, 2026
4f7fc22
Inject IBM OpenSearch creds into Langflow vars
edwinjosechittilappilly Mar 18, 2026
54ec6bd
Update .env.example
edwinjosechittilappilly Mar 18, 2026
c198528
Use IBM session cookie for identity and creds from JSON
edwinjosechittilappilly Mar 23, 2026
52fb02d
Align OpenSearch auth/token handling and defaults
edwinjosechittilappilly Mar 23, 2026
b024651
[WIP] Per-user OpenSearch client & IBM auth checks, Adds new creds fo…
edwinjosechittilappilly Mar 23, 2026
2035471
Defer IBM header validation and improve error
edwinjosechittilappilly Mar 23, 2026
6861f7d
Update dependencies.py
edwinjosechittilappilly Mar 23, 2026
433415f
Add unauthorized page and IBM auth handling
edwinjosechittilappilly Mar 23, 2026
f820a34
Add temporary request headers/cookies logging
edwinjosechittilappilly Mar 23, 2026
05694f1
Fallback to JWT when IBM LH Basic missing
edwinjosechittilappilly Mar 23, 2026
9a9acf0
Skip OpenSearch checks when IBM auth enabled
edwinjosechittilappilly Mar 23, 2026
4c7710e
Pass JWT through default document ingestion
edwinjosechittilappilly Mar 23, 2026
6bc69cc
Handle IBM auth for OpenSearch and health probe
edwinjosechittilappilly Mar 23, 2026
71b6ced
fix: add any_configured() to ProvidersConfig to prevent onboarding 50…
themavik Mar 19, 2026
7d36fff
Merge branch 'main' into IBM_AMS_NO_LOGIN
edwinjosechittilappilly Mar 23, 2026
e8d1abd
Merge remote-tracking branch 'origin/fix/configuration_watsonx' into …
edwinjosechittilappilly Mar 23, 2026
33a13e3
Add Openserach Url to VAriable pass through
edwinjosechittilappilly Mar 23, 2026
7774824
Merge branch 'main' into IBM_AMS_NO_LOGIN
edwinjosechittilappilly Mar 23, 2026
dae38a5
Update test_api_endpoints.py
edwinjosechittilappilly Mar 24, 2026
7994826
Strip 'Bearer ' from token and add AppClients.close
edwinjosechittilappilly Mar 24, 2026
d6f233d
fix redirect loop
mfortman11 Mar 24, 2026
a5bce6a
Support raw/base64 IBM credentials and JWT
edwinjosechittilappilly Mar 24, 2026
1730791
Use user-authenticated OpenSearch client
edwinjosechittilappilly Mar 24, 2026
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
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@ OPENSEARCH_USERNAME=admin
# Change this if you want to use a different index name or avoid conflicts
OPENSEARCH_INDEX_NAME=documents

# IBM AMS Authentication (IBM Watsonx Data embedded mode)
# Set IBM_AUTH_ENABLED=true to authenticate via the ibm-openrag-session cookie
# instead of Google OAuth. The raw IBM JWT is also passed directly to OpenSearch.
# When enabled, GOOGLE_OAUTH_CLIENT_ID/SECRET are not required for login.
IBM_AUTH_ENABLED=false
# URL to fetch IBM's JWT public key (used to validate ibm-openrag-session tokens).
IBM_JWT_PUBLIC_KEY_URL=
# Cookie name set by Traefik after successful IBM AMS authentication (default: ibm-openrag-session).
IBM_SESSION_COOKIE_NAME=ibm-openrag-session
# Header injected by Traefik on every forwarded request containing the OpenSearch Basic credentials
# (base64-encoded username:password). Override if your proxy uses a different header name.
IBM_CREDENTIALS_HEADER=X-IBM-LH-Credentials

# make here https://console.cloud.google.com/apis/credentials
GOOGLE_OAUTH_CLIENT_ID=
GOOGLE_OAUTH_CLIENT_SECRET=
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ define test_jwt_opensearch
TEST_TOKEN=$$(uv run python -c 'from utils.logging_config import configure_logging; configure_logging(log_level="CRITICAL"); \
from src.session_manager import SessionManager, AnonymousUser; \
sm = SessionManager("test"); \
print(sm.create_jwt_token(AnonymousUser()))' 2>/dev/null); \
print(sm.create_jwt_token(AnonymousUser()).removeprefix("Bearer "))' 2>/dev/null); \
if [ -z "$$TEST_TOKEN" ]; then \
echo "$(RED)Failed to generate JWT token$(NC)"; \
exit 1; \
Expand Down
2 changes: 1 addition & 1 deletion flows/components/opensearch_multimodal.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class OpenSearchVectorStoreComponentMultimodalMultiEmbedding(LCVectorStoreCompon
BoolInput(
name="bearer_prefix",
display_name="Prefix 'Bearer '",
value=True,
value=False,
show=False,
advanced=True,
),
Expand Down
2 changes: 1 addition & 1 deletion flows/ingestion_flow.json
Original file line number Diff line number Diff line change
Expand Up @@ -3557,7 +3557,7 @@
"trace_as_metadata": true,
"track_in_telemetry": true,
"type": "bool",
"value": true
"value": false
},
"code": {
"advanced": true,
Expand Down
2 changes: 1 addition & 1 deletion flows/openrag_agent.json
Original file line number Diff line number Diff line change
Expand Up @@ -2956,7 +2956,7 @@
"trace_as_metadata": true,
"track_in_telemetry": true,
"type": "bool",
"value": true
"value": false
},
"code": {
"advanced": true,
Expand Down
2 changes: 1 addition & 1 deletion flows/openrag_nudges.json
Original file line number Diff line number Diff line change
Expand Up @@ -2435,7 +2435,7 @@
"trace_as_metadata": true,
"track_in_telemetry": true,
"type": "bool",
"value": true
"value": false
},
"code": {
"advanced": true,
Expand Down
2 changes: 1 addition & 1 deletion flows/openrag_url_mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -3842,7 +3842,7 @@
"trace_as_metadata": true,
"track_in_telemetry": true,
"type": "bool",
"value": true
"value": false
},
"code": {
"advanced": true,
Expand Down
10 changes: 5 additions & 5 deletions frontend/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import { Button } from "@/components/ui/button";
import { useAuth } from "@/contexts/auth-context";

function LoginPageContent() {
const { isLoading, isAuthenticated, isNoAuthMode, login } = useAuth();
const { isLoading, isAuthenticated, isNoAuthMode, isIbmAuthMode, login } =
useAuth();
const router = useRouter();
const searchParams = useSearchParams();

const redirect = searchParams.get("redirect") || "/chat";

// Redirect if already authenticated or in no-auth mode
useEffect(() => {
if (!isLoading && (isAuthenticated || isNoAuthMode)) {
if (!isLoading && (isAuthenticated || isNoAuthMode || isIbmAuthMode)) {
router.push(redirect);
}
}, [isLoading, isAuthenticated, isNoAuthMode, router, redirect]);
}, [isLoading, isAuthenticated, isNoAuthMode, isIbmAuthMode, router, redirect]);

if (isLoading) {
return (
Expand All @@ -33,7 +33,7 @@ function LoginPageContent() {
);
}

if (isAuthenticated || isNoAuthMode) {
if (isAuthenticated || isNoAuthMode || isIbmAuthMode) {
return null; // Will redirect in useEffect
}

Expand Down
62 changes: 62 additions & 0 deletions frontend/app/unauthorized/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use client";

import { Loader2, ShieldAlert } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import Logo from "@/components/icons/openrag-logo";
import { useAuth } from "@/contexts/auth-context";

export default function UnauthorizedPage() {
const { isLoading, isAuthenticated, isIbmAuthMode } = useAuth();
const router = useRouter();

useEffect(() => {
if (!isLoading && isAuthenticated) {
router.push("/chat");
}
}, [isLoading, isAuthenticated, router]);

if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="flex flex-col items-center gap-4">
<Loader2 className="h-8 w-8 animate-spin" />
<p className="text-muted-foreground">Loading...</p>
</div>
</div>
);
}

if (isAuthenticated) {
return null;
}

return (
<div className="min-h-dvh relative flex gap-4 flex-col items-center justify-center bg-card rounded-lg m-4">
<div className="flex flex-col items-center justify-center gap-6 z-10 max-w-md px-4 text-center">
<Logo className="fill-primary" width={50} height={40} />
<ShieldAlert className="h-12 w-12 text-destructive" />
<h1 className="text-2xl font-medium font-chivo">
Authentication Required
</h1>
{isIbmAuthMode ? (
<p className="text-muted-foreground">
Your session could not be authenticated. Please ensure you are
accessing OpenRAG through IBM watsonx.data with valid credentials.
</p>
) : (
<p className="text-muted-foreground">
You do not have permission to access this page. Please sign in to
continue.
</p>
)}
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
>
Retry
</button>
</div>
</div>
);
}
22 changes: 16 additions & 6 deletions frontend/components/layout-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,28 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
}
}, [isMenuOpen, closePanelOnly]);

const { isLoading, isAuthenticated, isNoAuthMode } = useAuth();
const { isLoading, isAuthenticated, isNoAuthMode, isIbmAuthMode } = useAuth();
const { isOnboardingComplete } = useChat();

const authPaths = ["/login", "/auth/callback"];
const authPaths = ["/login", "/auth/callback", "/unauthorized"];
const isAuthPage = authPaths.includes(pathname);

useEffect(() => {
if (!isLoading && !isAuthenticated && !isNoAuthMode && !isAuthPage) {
const redirectUrl = `/login?redirect=${encodeURIComponent(pathname)}`;
router.push(redirectUrl);
if (isLoading || isAuthenticated || isNoAuthMode || isAuthPage) return;
if (isIbmAuthMode) {
router.push("/unauthorized");
return;
}
}, [isLoading, isAuthenticated, isNoAuthMode, isAuthPage, pathname, router]);
router.push(`/login?redirect=${encodeURIComponent(pathname)}`);
}, [
isLoading,
isAuthenticated,
isNoAuthMode,
isIbmAuthMode,
isAuthPage,
pathname,
router,
]);

const { data: settings, isLoading: isSettingsLoading } = useGetSettingsQuery({
enabled: !isAuthPage && (isAuthenticated || isNoAuthMode),
Expand Down
21 changes: 12 additions & 9 deletions frontend/components/protected-route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface ProtectedRouteProps {
}

export function ProtectedRoute({ children }: ProtectedRouteProps) {
const { isLoading, isAuthenticated, isNoAuthMode } = useAuth();
const { isLoading, isAuthenticated, isNoAuthMode, isIbmAuthMode } = useAuth();
const router = useRouter();
const pathname = usePathname();

Expand All @@ -21,20 +21,26 @@ export function ProtectedRoute({ children }: ProtectedRouteProps) {
isAuthenticated,
"isNoAuthMode:",
isNoAuthMode,
"isIbmAuthMode:",
isIbmAuthMode,
"pathname:",
pathname,
);

useEffect(() => {
if (!isLoading && !isAuthenticated && !isNoAuthMode) {
// Redirect to login with current path as redirect parameter
if (isLoading) return;

if (!isAuthenticated) {
if (isNoAuthMode) return;
if (isIbmAuthMode) {
router.push("/unauthorized");
return;
}
const redirectUrl = `/login?redirect=${encodeURIComponent(pathname)}`;
router.push(redirectUrl);
return;
}
}, [isLoading, isAuthenticated, isNoAuthMode, router, pathname]);
}, [isLoading, isAuthenticated, isNoAuthMode, isIbmAuthMode, router, pathname]);

// Show loading state while checking authentication
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
Expand All @@ -46,16 +52,13 @@ export function ProtectedRoute({ children }: ProtectedRouteProps) {
);
}

// In no-auth mode, always render content
if (isNoAuthMode) {
return <>{children}</>;
}

// Don't render anything if not authenticated (will redirect)
if (!isAuthenticated) {
return null;
}

// Render protected content
return <>{children}</>;
}
63 changes: 55 additions & 8 deletions frontend/contexts/auth-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface AuthContextType {
isNoAuthMode: boolean;
isIbmAuthMode: boolean;
login: () => void;
loginWithIbm: (username: string, password: string) => Promise<void>;
logout: () => Promise<void>;
refreshAuth: () => Promise<void>;
}
Expand All @@ -50,6 +51,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
const [isIbmAuthMode, setIsIbmAuthMode] = useState(false);

const checkAuth = useCallback(async () => {
setIsLoading(true);
try {
const response = await fetch("/api/auth/me");

Expand All @@ -61,22 +63,35 @@ export function AuthProvider({ children }: AuthProviderProps) {
}

const data = await response.json();
console.log("[checkAuth] /api/auth/me response:", data);

setIsIbmAuthMode(!!data.ibm_auth_mode);

// Check if we're in no-auth mode
if (data.no_auth_mode) {
// Check auth mode flags
if (data.ibm_auth_mode) {
setIsIbmAuthMode(true);
setIsNoAuthMode(false);
setUser(data.authenticated && data.user ? data.user : null);
console.log(
"[checkAuth] IBM auth mode — authenticated:",
data.authenticated,
"user:",
data.user,
);
} else if (data.no_auth_mode) {
setIsNoAuthMode(true);
setIsIbmAuthMode(false);
setUser(null);
} else if (data.authenticated && data.user) {
setIsNoAuthMode(false);
setIsIbmAuthMode(false);
setUser(data.user);
} else {
setIsNoAuthMode(false);
setIsIbmAuthMode(false);
setUser(null);
}

setIsLoading(false);
console.log("[checkAuth] done — isLoading: false");
} catch (error) {
console.error("Auth check failed:", error);
// Network error - backend not ready, keep loading and retry
Expand All @@ -86,11 +101,17 @@ export function AuthProvider({ children }: AuthProviderProps) {
}, []);

const login = () => {
// Don't allow login in no-auth mode
// Don't allow login in no-auth mode or IBM auth mode
if (isNoAuthMode) {
console.log("Login attempted in no-auth mode - ignored");
return;
}
if (isIbmAuthMode) {
console.log(
"Login attempted in IBM auth mode - ignored (auth managed by IBM Watsonx Data)",
);
return;
}

// Use the correct auth callback URL, not connectors callback
const redirectUri = `${window.location.origin}/auth/callback`;
Expand Down Expand Up @@ -150,10 +171,35 @@ export function AuthProvider({ children }: AuthProviderProps) {
});
};

const loginWithIbm = async (username: string, password: string) => {
console.log("[loginWithIbm] posting to /api/auth/ibm/login");
const response = await fetch("/api/auth/ibm/login", {
method: "POST",
headers: {
Authorization: "Basic " + btoa(username + ":" + password),
},
});
console.log(
"[loginWithIbm] response status:",
response.status,
"ok:",
response.ok,
);
console.log(
"[loginWithIbm] response cookies after login:",
document.cookie,
);
if (!response.ok) {
const data = await response.json().catch(() => ({}));
throw new Error(data.detail || "Login failed");
}
await checkAuth();
};

const logout = async () => {
// Don't allow logout in no-auth mode
if (isNoAuthMode) {
console.log("Logout attempted in no-auth mode - ignored");
// Don't allow logout in no-auth mode or IBM auth mode
if (isNoAuthMode || isIbmAuthMode) {
console.log("Logout attempted in no-auth/IBM auth mode - ignored");
return;
}

Expand Down Expand Up @@ -182,6 +228,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
isNoAuthMode,
isIbmAuthMode,
login,
loginWithIbm,
logout,
refreshAuth,
};
Expand Down
Loading
Loading