diff --git a/.env.example b/.env.example index 4b27b096..aba14e76 100644 --- a/.env.example +++ b/.env.example @@ -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 @@ -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 # ============================================================================ diff --git a/.gitignore b/.gitignore index 8f009787..5e121976 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ dist/ .next/ .env .env.local +.env.bak.* *.log .nx/ .prisma/ diff --git a/apps/web-ui-e2e/src/pages/sso.flow.ts b/apps/web-ui-e2e/src/pages/sso.flow.ts index 9cc944f8..f57bd0a1 100644 --- a/apps/web-ui-e2e/src/pages/sso.flow.ts +++ b/apps/web-ui-e2e/src/pages/sso.flow.ts @@ -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 { 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(); diff --git a/apps/web-ui/app/(auth)/login/page.tsx b/apps/web-ui/app/(auth)/login/page.tsx index 0994d15f..7c548202 100644 --- a/apps/web-ui/app/(auth)/login/page.tsx +++ b/apps/web-ui/app/(auth)/login/page.tsx @@ -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 diff --git a/apps/web-ui/app/(auth)/register/page.tsx b/apps/web-ui/app/(auth)/register/page.tsx index decce40c..9edd3616 100644 --- a/apps/web-ui/app/(auth)/register/page.tsx +++ b/apps/web-ui/app/(auth)/register/page.tsx @@ -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 diff --git a/apps/web-ui/app/(marketing)/components/feature-grid.tsx b/apps/web-ui/app/(marketing)/components/feature-grid.tsx index f3985da2..c4303841 100644 --- a/apps/web-ui/app/(marketing)/components/feature-grid.tsx +++ b/apps/web-ui/app/(marketing)/components/feature-grid.tsx @@ -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, diff --git a/apps/web-ui/lib/env.ts b/apps/web-ui/lib/env.ts index 72fa22c8..afd629fb 100644 --- a/apps/web-ui/lib/env.ts +++ b/apps/web-ui/lib/env.ts @@ -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(), @@ -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(), diff --git a/libs/shared/src/auth/auth-options.ts b/libs/shared/src/auth/auth-options.ts index f309f133..b4bd3450 100644 --- a/libs/shared/src/auth/auth-options.ts +++ b/libs/shared/src/auth/auth-options.ts @@ -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'; @@ -81,11 +81,11 @@ export function createAuthOptions(overrides?: Partial): 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; } @@ -108,7 +108,7 @@ export function createAuthOptions(overrides?: Partial): NextAut source: 'platform', resource: user.email ?? user.id, metadata: { provider: 'credentials', userId: user.id }, - }).catch(() => {}); + }).catch(() => { }); return { id: user.id, @@ -119,16 +119,14 @@ export function createAuthOptions(overrides?: Partial): 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' }, @@ -222,7 +220,7 @@ export function createAuthOptions(overrides?: Partial): 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; @@ -240,7 +238,7 @@ export function createAuthOptions(overrides?: Partial): NextAut source: 'platform', resource: email ?? userId ?? 'unknown', metadata: { userId }, - }).catch(() => {}); + }).catch(() => { }); }, }, ...overrides, diff --git a/libs/shared/src/env.ts b/libs/shared/src/env.ts index 4fbfd29a..e6b98ea8 100644 --- a/libs/shared/src/env.ts +++ b/libs/shared/src/env.ts @@ -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(), @@ -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(),