Skip to content
Merged
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
24 changes: 21 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,16 @@ S3_BUCKET=chatbot-knowledge-base-dev
# S3_SECRET_ACCESS_KEY=minioadmin # MinIO root password

# ============================================================================
# AWS Cognito — SSO login + invitation email delivery
# Google OAuth — SSO login
# Credentials-based login works without these.
# To enable: create a Cognito User Pool + App Client, then fill in below.
# Callback URL to register in Cognito: http://localhost:3005/api/auth/callback/cognito
# To enable: create a Google OAuth 2.0 client in Google Cloud Console and set:
# Authorized redirect URI: http://localhost:3005/api/auth/callback/google
# ============================================================================
# GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
# GOOGLE_CLIENT_SECRET=your-google-client-secret

# Legacy Cognito values are no longer used by NextAuth; leave them unset unless
# another service in the project still depends on them.
# COGNITO_USER_POOL_ID=ap-south-1_example
# COGNITO_APP_CLIENT_ID=your-app-client-id
# COGNITO_APP_CLIENT_SECRET=your-app-client-secret
Expand All @@ -114,6 +119,19 @@ BEDROCK_CHAT_MODEL=anthropic.claude-sonnet-4-20250514
# Embedding model for RAG pipeline
BEDROCK_EMBEDDING_MODEL=amazon.titan-embed-text-v2:0

# ============================================================================
# Ollama — optional. This app does NOT auto-create a default provider from env;
# create an Ollama provider in Dashboard -> LLM Providers and mark it default.
# Chat base URL + API key are entered per tenant in that form.
#
# OLLAMA_BASE_URL below is the ONLY Ollama env var the app reads: it points at a
# local server for knowledge-base embeddings/reranking (native /api/embeddings,
# no auth). Pull the model first: `ollama pull nomic-embed-text`.
# For Ollama Cloud chat, set the base URL to https://ollama.com in the
# LLM Providers form and paste a token from https://ollama.com/settings/keys.
# ============================================================================
# OLLAMA_BASE_URL=http://localhost:11434

# ============================================================================
# LiteLLM Gateway — optional, for the LITELLM provider type
# ============================================================================
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dist/
.next/
.env
.env.local
.env.bak.*
*.log
.nx/
.prisma/
Expand Down
8 changes: 4 additions & 4 deletions apps/web-ui-e2e/src/pages/sso.flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ import { env } from '../config/env';
import { ROUTES } from '../constants/routes';

/**
* Drives the Cognito hosted-UI SSO login and the in-app sign-out flow.
* Drives the Google OAuth SSO login and the in-app sign-out flow.
* Credentials come from the typed env (E2E_SSO_EMAIL / E2E_SSO_PASSWORD);
* gate specs with `hasSsoCreds()` so they skip when creds are absent.
*/
export class SsoFlow {
constructor(private readonly page: Page) {}
constructor(private readonly page: Page) { }

async login(): Promise<void> {
await this.page.goto(ROUTES.home, { waitUntil: 'domcontentloaded' });
await this.page.getByRole('link', { name: 'Sign in' }).click();
await this.page.getByRole('button', { name: 'Sign in with SSO' }).click();
await this.page.getByRole('button', { name: 'Sign in with Google' }).click();

await this.page.getByRole('textbox', { name: 'name@host.com' }).fill(env.E2E_SSO_EMAIL);
await this.page.getByRole('textbox', { name: 'Password' }).fill(env.E2E_SSO_PASSWORD);
await this.page.getByRole('textbox', { name: 'Password' }).press('Enter');

// Cognito may render a submit button before redirecting
// Google may render a consent form or submit button before redirecting
const submitBtn = this.page.getByRole('button', { name: 'submit' });
if (await submitBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
await submitBtn.click();
Expand Down
4 changes: 2 additions & 2 deletions apps/web-ui/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ export default function LoginPage() {
variant="outline"
type="button"
className="w-full"
onClick={() => signIn('cognito', { callbackUrl: '/dashboard' })}
onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
>
Sign in with SSO
Sign in with Google
</Button>
</Field>
<FieldSeparator className="*:data-[slot=field-separator-content]:bg-card">
Expand Down
4 changes: 2 additions & 2 deletions apps/web-ui/app/(auth)/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ export default function RegisterPage() {
variant="outline"
type="button"
className="w-full"
onClick={() => signIn('cognito', { callbackUrl: '/dashboard' })}
onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
>
Sign up with SSO
Sign up with Google
</Button>
</Field>
<FieldSeparator className="*:data-[slot=field-separator-content]:bg-card">
Expand Down
4 changes: 2 additions & 2 deletions apps/web-ui/app/(marketing)/components/feature-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ const features = [
},
{
icon: KeyRound,
title: 'Cognito Auth',
title: 'Google Auth',
description:
'NextAuth with AWS Cognito SSO and credentials login.',
'NextAuth with Google OAuth SSO and credentials login.',
},
{
icon: MessageSquare,
Expand Down
10 changes: 9 additions & 1 deletion apps/web-ui/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export const env = createEnv({
NEXTAUTH_SECRET: z.string().min(1),
NEXTAUTH_URL: z.string().url().optional(),

// Cognito
// Google OAuth
GOOGLE_CLIENT_ID: z.string().optional(),
GOOGLE_CLIENT_SECRET: z.string().optional(),

// Legacy Cognito values kept for compatibility while the app is migrated.
COGNITO_APP_CLIENT_ID: z.string().optional(),
COGNITO_APP_CLIENT_SECRET: z.string().optional(),
COGNITO_ISSUER: z.string().url().optional(),
Expand All @@ -22,6 +26,10 @@ export const env = createEnv({
BEDROCK_CHAT_MODEL: z.string().optional(),
BEDROCK_EMBEDDING_MODEL: z.string().optional(),

// Ollama — local Embeddings/reranking host (used by libs/knowledge-base).
// Chat + cloud credentials are configured per tenant in the UI, not from env.
OLLAMA_BASE_URL: z.string().optional(),

// Web Search
TAVILY_API_KEY: z.string().optional(),
BRAVE_API_KEY: z.string().optional(),
Expand Down
28 changes: 13 additions & 15 deletions libs/shared/src/auth/auth-options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './auth-types';
import { NextAuthOptions } from 'next-auth';
import CognitoProvider from 'next-auth/providers/cognito';
import GoogleProvider from 'next-auth/providers/google';
import CredentialsProvider from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { getPrismaClient } from '../db/prisma-client';
Expand Down Expand Up @@ -81,11 +81,11 @@ export function createAuthOptions(overrides?: Partial<NextAuthOptions>): NextAut
status: 'error',
severity: newFailedAttempts >= 5 ? 'critical' : 'high',
details: `Failed login attempt ${newFailedAttempts} for ${email}` +
(lockedUntil ? ` — account locked for 15 minutes` : ''),
(lockedUntil ? ` — account locked for 15 minutes` : ''),
source: 'platform',
resource: email,
metadata: { email, failedAttempts: newFailedAttempts, locked: !!lockedUntil },
}).catch(() => {});
}).catch(() => { });

return null;
}
Expand All @@ -108,7 +108,7 @@ export function createAuthOptions(overrides?: Partial<NextAuthOptions>): NextAut
source: 'platform',
resource: user.email ?? user.id,
metadata: { provider: 'credentials', userId: user.id },
}).catch(() => {});
}).catch(() => { });

return {
id: user.id,
Expand All @@ -119,16 +119,14 @@ export function createAuthOptions(overrides?: Partial<NextAuthOptions>): NextAut
};
},
}),
...(env.COGNITO_APP_CLIENT_ID
...(env.GOOGLE_CLIENT_ID
? [
CognitoProvider({
clientId: env.COGNITO_APP_CLIENT_ID,
clientSecret: env.COGNITO_APP_CLIENT_SECRET!,
issuer: env.COGNITO_ISSUER!,
allowDangerousEmailAccountLinking: true,
httpOptions: { timeout: 10000 },
}),
]
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET!,
allowDangerousEmailAccountLinking: true,
}),
]
: []),
],
pages: { signIn: '/login', error: '/login' },
Expand Down Expand Up @@ -222,7 +220,7 @@ export function createAuthOptions(overrides?: Partial<NextAuthOptions>): NextAut
source: 'platform',
resource: user.email ?? user.id,
metadata: { provider: account?.provider ?? 'credentials', userId: user.id },
}).catch(() => {});
}).catch(() => { });
},
async signOut({ token }) {
const userId = token?.sub as string | undefined;
Expand All @@ -240,7 +238,7 @@ export function createAuthOptions(overrides?: Partial<NextAuthOptions>): NextAut
source: 'platform',
resource: email ?? userId ?? 'unknown',
metadata: { userId },
}).catch(() => {});
}).catch(() => { });
},
},
...overrides,
Expand Down
11 changes: 10 additions & 1 deletion libs/shared/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export const env = createEnv({
NEXTAUTH_SECRET: z.string().optional(),
NEXTAUTH_URL: z.string().url().optional(),

// Cognito — optional because credentials-based login works without SSO
// Google OAuth — optional because credentials-based login works without SSO
GOOGLE_CLIENT_ID: z.string().optional(),
GOOGLE_CLIENT_SECRET: z.string().optional(),

// Legacy Cognito values kept for compatibility while the app is migrated.
COGNITO_APP_CLIENT_ID: z.string().optional(),
COGNITO_APP_CLIENT_SECRET: z.string().optional(),
COGNITO_ISSUER: z.string().url().optional(),
Expand All @@ -29,6 +33,11 @@ export const env = createEnv({
BEDROCK_CHAT_MODEL: z.string().optional(),
BEDROCK_EMBEDDING_MODEL: z.string().optional(),

// Ollama — local host only. Chat models and any Cloud credentials are stored
// per tenant in the LlmProvider table (UI). The sole env read is
// OLLAMA_BASE_URL, used by libs/knowledge-base for local embeddings/reranking.
OLLAMA_BASE_URL: z.string().optional(),

// App URL (used for reset links, etc.)
APP_URL: z.string().url().optional(),

Expand Down
Loading