diff --git a/README.md b/README.md index 034a1cb..8e0eae5 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,6 @@ Both installation guides include detailed OS-specific instructions for Windows, Before installation, make sure to complete the [prerequisite setup steps](https://www.surfsense.net/docs/) including: - PGVector setup -- Google OAuth configuration - Unstructured.io API key - Other required API keys diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 19a4115..f9c43d1 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -1,10 +1,15 @@ DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense" SECRET_KEY="SECRET" -GOOGLE_OAUTH_CLIENT_ID="924507538m" -GOOGLE_OAUTH_CLIENT_SECRET="GOCSV" NEXT_FRONTEND_URL="http://localhost:3000" +#Auth +AUTH_TYPE="GOOGLE" or "LOCAL" +# For Google Auth Only +GOOGLE_OAUTH_CLIENT_ID="924507538m" +GOOGLE_OAUTH_CLIENT_SECRET="GOCSV" + +#Embedding Model EMBEDDING_MODEL="mixedbread-ai/mxbai-embed-large-v1" RERANKERS_MODEL_NAME="ms-marco-MiniLM-L-12-v2" diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py index 5649d2d..956740f 100644 --- a/surfsense_backend/app/app.py +++ b/surfsense_backend/app/app.py @@ -6,16 +6,18 @@ from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.ext.asyncio import AsyncSession from app.db import User, create_db_and_tables, get_async_session -from app.retriver.chunks_hybrid_search import ChucksHybridSearchRetriever from app.schemas import UserCreate, UserRead, UserUpdate + + +from app.routes import router as crud_router +from app.config import config + from app.users import ( SECRET, auth_backend, fastapi_users, - google_oauth_client, - current_active_user, + current_active_user ) -from app.routes import router as crud_router @asynccontextmanager @@ -59,16 +61,20 @@ app.include_router( prefix="/users", tags=["users"], ) -app.include_router( - fastapi_users.get_oauth_router( - google_oauth_client, - auth_backend, - SECRET, - is_verified_by_default=True - ), - prefix="/auth/google", - tags=["auth"], -) + +if config.AUTH_TYPE == "GOOGLE": + from app.users import google_oauth_client + app.include_router( + fastapi_users.get_oauth_router( + google_oauth_client, + auth_backend, + SECRET, + is_verified_by_default=True + ), + prefix="/auth/google", + tags=["auth"], + ) + app.include_router(crud_router, prefix="/api/v1", tags=["crud"]) diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index 9dc8627..81cd9a2 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -38,12 +38,17 @@ class Config: # Database DATABASE_URL = os.getenv("DATABASE_URL") - - # AUTH: Google OAuth - GOOGLE_OAUTH_CLIENT_ID = os.getenv("GOOGLE_OAUTH_CLIENT_ID") - GOOGLE_OAUTH_CLIENT_SECRET = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET") + NEXT_FRONTEND_URL = os.getenv("NEXT_FRONTEND_URL") + + # AUTH: Google OAuth + AUTH_TYPE = os.getenv("AUTH_TYPE") + if AUTH_TYPE == "GOOGLE": + GOOGLE_OAUTH_CLIENT_ID = os.getenv("GOOGLE_OAUTH_CLIENT_ID") + GOOGLE_OAUTH_CLIENT_SECRET = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET") + + # LONG-CONTEXT LLMS LONG_CONTEXT_LLM = os.getenv("LONG_CONTEXT_LLM") LONG_CONTEXT_LLM_API_BASE = os.getenv("LONG_CONTEXT_LLM_API_BASE") diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index 7327c3a..10f78a5 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -3,11 +3,7 @@ from datetime import datetime, timezone from enum import Enum from fastapi import Depends -from fastapi_users.db import ( - SQLAlchemyBaseOAuthAccountTableUUID, - SQLAlchemyBaseUserTableUUID, - SQLAlchemyUserDatabase, -) + from pgvector.sqlalchemy import Vector from sqlalchemy import ( ARRAY, @@ -30,6 +26,18 @@ from app.config import config from app.retriver.chunks_hybrid_search import ChucksHybridSearchRetriever from app.retriver.documents_hybrid_search import DocumentHybridSearchRetriever +if config.AUTH_TYPE == "GOOGLE": + from fastapi_users.db import ( + SQLAlchemyBaseOAuthAccountTableUUID, + SQLAlchemyBaseUserTableUUID, + SQLAlchemyUserDatabase, + ) +else: + from fastapi_users.db import ( + SQLAlchemyBaseUserTableUUID, + SQLAlchemyUserDatabase, + ) + DATABASE_URL = config.DATABASE_URL @@ -141,17 +149,22 @@ class SearchSourceConnector(BaseModel, TimestampMixin): user_id = Column(UUID(as_uuid=True), ForeignKey("user.id", ondelete='CASCADE'), nullable=False) user = relationship("User", back_populates="search_source_connectors") - -class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base): - pass +if config.AUTH_TYPE == "GOOGLE": + class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base): + pass -class User(SQLAlchemyBaseUserTableUUID, Base): - oauth_accounts: Mapped[list[OAuthAccount]] = relationship( - "OAuthAccount", lazy="joined" - ) - search_spaces = relationship("SearchSpace", back_populates="user") - search_source_connectors = relationship("SearchSourceConnector", back_populates="user") + class User(SQLAlchemyBaseUserTableUUID, Base): + oauth_accounts: Mapped[list[OAuthAccount]] = relationship( + "OAuthAccount", lazy="joined" + ) + search_spaces = relationship("SearchSpace", back_populates="user") + search_source_connectors = relationship("SearchSourceConnector", back_populates="user") +else: + class User(SQLAlchemyBaseUserTableUUID, Base): + + search_spaces = relationship("SearchSpace", back_populates="user") + search_source_connectors = relationship("SearchSourceConnector", back_populates="user") engine = create_async_engine(DATABASE_URL) @@ -180,8 +193,12 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]: yield session -async def get_user_db(session: AsyncSession = Depends(get_async_session)): - yield SQLAlchemyUserDatabase(session, User, OAuthAccount) +if config.AUTH_TYPE == "GOOGLE": + async def get_user_db(session: AsyncSession = Depends(get_async_session)): + yield SQLAlchemyUserDatabase(session, User, OAuthAccount) +else: + async def get_user_db(session: AsyncSession = Depends(get_async_session)): + yield SQLAlchemyUserDatabase(session, User) async def get_chucks_hybrid_search_retriever(session: AsyncSession = Depends(get_async_session)): return ChucksHybridSearchRetriever(session) diff --git a/surfsense_backend/app/users.py b/surfsense_backend/app/users.py index ec4445c..d73baae 100644 --- a/surfsense_backend/app/users.py +++ b/surfsense_backend/app/users.py @@ -10,8 +10,8 @@ from fastapi_users.authentication import ( JWTStrategy, ) from fastapi_users.db import SQLAlchemyUserDatabase -from httpx_oauth.clients.google import GoogleOAuth2 - +from fastapi.responses import JSONResponse +from fastapi_users.schemas import model_dump from app.config import config from app.db import User, get_user_db from pydantic import BaseModel @@ -22,10 +22,13 @@ class BearerResponse(BaseModel): SECRET = config.SECRET_KEY -google_oauth_client = GoogleOAuth2( - config.GOOGLE_OAUTH_CLIENT_ID, - config.GOOGLE_OAUTH_CLIENT_SECRET, -) +if config.AUTH_TYPE == "GOOGLE": + from httpx_oauth.clients.google import GoogleOAuth2 + + google_oauth_client = GoogleOAuth2( + config.GOOGLE_OAUTH_CLIENT_ID, + config.GOOGLE_OAUTH_CLIENT_SECRET, + ) class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): @@ -79,7 +82,10 @@ class CustomBearerTransport(BearerTransport): async def get_login_response(self, token: str) -> Response: bearer_response = BearerResponse(access_token=token, token_type="bearer") redirect_url = f"{config.NEXT_FRONTEND_URL}/auth/callback?token={bearer_response.access_token}" - return RedirectResponse(redirect_url, status_code=302) + if config.AUTH_TYPE == "GOOGLE": + return RedirectResponse(redirect_url, status_code=302) + else: + return JSONResponse(model_dump(bearer_response)) bearer_transport = CustomBearerTransport(tokenUrl="auth/jwt/login") diff --git a/surfsense_web/.env.example b/surfsense_web/.env.example index abd370c..3ab9d17 100644 --- a/surfsense_web/.env.example +++ b/surfsense_web/.env.example @@ -1 +1,2 @@ -NEXT_PUBLIC_FASTAPI_BACKEND_URL=http://localhost:8000 \ No newline at end of file +NEXT_PUBLIC_FASTAPI_BACKEND_URL=http://localhost:8000 +NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE=LOCAL or GOOGLE \ No newline at end of file diff --git a/surfsense_web/app/dashboard/page.tsx b/surfsense_web/app/dashboard/page.tsx index d3e298f..176c9bf 100644 --- a/surfsense_web/app/dashboard/page.tsx +++ b/surfsense_web/app/dashboard/page.tsx @@ -4,7 +4,7 @@ import React from 'react' import Link from 'next/link' import { motion } from 'framer-motion' import { Button } from '@/components/ui/button' -import { Plus, Search, Trash2, AlertCircle, Loader2 } from 'lucide-react' +import { Plus, Search, Trash2, AlertCircle, Loader2, LogOut } from 'lucide-react' import { Tilt } from '@/components/ui/tilt' import { Spotlight } from '@/components/ui/spotlight' import { Logo } from '@/components/Logo'; @@ -145,11 +145,19 @@ const DashboardPage = () => { }, }; + const router = useRouter(); const { searchSpaces, loading, error, refreshSearchSpaces } = useSearchSpaces(); if (loading) return ; if (error) return ; + const handleLogout = () => { + if (typeof window !== 'undefined') { + localStorage.removeItem('surfsense_bearer_token'); + router.push('/'); + } + }; + const handleDeleteSearchSpace = async (id: number) => { // Send DELETE request to the API try { @@ -193,7 +201,18 @@ const DashboardPage = () => {

- +
+ + +
diff --git a/surfsense_web/app/login/AmbientBackground.tsx b/surfsense_web/app/login/AmbientBackground.tsx new file mode 100644 index 0000000..6b61d51 --- /dev/null +++ b/surfsense_web/app/login/AmbientBackground.tsx @@ -0,0 +1,43 @@ +"use client"; +import React from "react"; + +export const AmbientBackground = () => { + return ( +
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/surfsense_web/app/login/GoogleLoginButton.tsx b/surfsense_web/app/login/GoogleLoginButton.tsx index 11caafb..ee5deb3 100644 --- a/surfsense_web/app/login/GoogleLoginButton.tsx +++ b/surfsense_web/app/login/GoogleLoginButton.tsx @@ -3,6 +3,7 @@ import React from "react"; import { IconBrandGoogleFilled } from "@tabler/icons-react"; import { motion } from "framer-motion"; import { Logo } from "@/components/Logo"; +import { AmbientBackground } from "./AmbientBackground"; export function GoogleLoginButton() { const handleGoogleLogin = () => { @@ -88,47 +89,4 @@ export function GoogleLoginButton() {
); -} - - - -const AmbientBackground = () => { - return ( -
-
-
-
-
- ); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/surfsense_web/app/login/LocalLoginForm.tsx b/surfsense_web/app/login/LocalLoginForm.tsx new file mode 100644 index 0000000..3459418 --- /dev/null +++ b/surfsense_web/app/login/LocalLoginForm.tsx @@ -0,0 +1,114 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; + +export function LocalLoginForm() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [authType, setAuthType] = useState(null); + const router = useRouter(); + + useEffect(() => { + // Get the auth type from environment variables + setAuthType(process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "GOOGLE"); + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + setError(""); + + try { + // Create form data for the API request + const formData = new URLSearchParams(); + formData.append("username", username); + formData.append("password", password); + formData.append("grant_type", "password"); + + const response = await fetch( + `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/jwt/login`, + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: formData.toString(), + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.detail || "Failed to login"); + } + + router.push("/auth/callback?token=" + data.access_token); + } catch (err: any) { + setError(err.message || "An error occurred during login"); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+ {error && ( +
+ {error} +
+ )} + +
+ + setUsername(e.target.value)} + className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white" + /> +
+ +
+ + setPassword(e.target.value)} + className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white" + /> +
+ + +
+ + {authType === "LOCAL" && ( +
+

+ Don't have an account?{" "} + + Register here + +

+
+ )} +
+ ); +} \ No newline at end of file diff --git a/surfsense_web/app/login/page.tsx b/surfsense_web/app/login/page.tsx index ee3b462..95620d2 100644 --- a/surfsense_web/app/login/page.tsx +++ b/surfsense_web/app/login/page.tsx @@ -1,5 +1,67 @@ +"use client"; + +import { useState, useEffect } from "react"; import { GoogleLoginButton } from "./GoogleLoginButton"; +import { LocalLoginForm } from "./LocalLoginForm"; +import { Logo } from "@/components/Logo"; +import { AmbientBackground } from "./AmbientBackground"; +import { useSearchParams } from "next/navigation"; +import { Loader2 } from "lucide-react"; export default function LoginPage() { - return ; + const [authType, setAuthType] = useState(null); + const [registrationSuccess, setRegistrationSuccess] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const searchParams = useSearchParams(); + + useEffect(() => { + // Check if the user was redirected from registration + if (searchParams.get("registered") === "true") { + setRegistrationSuccess(true); + } + + // Get the auth type from environment variables + setAuthType(process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "GOOGLE"); + setIsLoading(false); + }, [searchParams]); + + // Show loading state while determining auth type + if (isLoading) { + return ( +
+ +
+ +
+ + Loading... +
+
+
+ ); + } + + if (authType === "GOOGLE") { + return ; + } + + return ( +
+ +
+ +

+ Sign In +

+ + {registrationSuccess && ( +
+ Registration successful! You can now sign in with your credentials. +
+ )} + + +
+
+ ); } \ No newline at end of file diff --git a/surfsense_web/app/register/page.tsx b/surfsense_web/app/register/page.tsx new file mode 100644 index 0000000..33e0126 --- /dev/null +++ b/surfsense_web/app/register/page.tsx @@ -0,0 +1,149 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; +import { Logo } from "@/components/Logo"; +import { AmbientBackground } from "../login/AmbientBackground"; + +export default function RegisterPage() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + + // Check authentication type and redirect if not LOCAL + useEffect(() => { + const authType = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "GOOGLE"; + if (authType !== "LOCAL") { + router.push("/login"); + } + }, [router]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Form validation + if (password !== confirmPassword) { + setError("Passwords do not match"); + return; + } + + setIsLoading(true); + setError(""); + + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/register`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email, + password, + is_active: true, + is_superuser: false, + is_verified: false, + }), + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.detail || "Registration failed"); + } + + // Redirect to login page after successful registration + router.push("/login?registered=true"); + } catch (err: any) { + setError(err.message || "An error occurred during registration"); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ +
+ +

+ Create an Account +

+ +
+
+ {error && ( +
+ {error} +
+ )} + +
+ + setEmail(e.target.value)} + className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white" + /> +
+ +
+ + setPassword(e.target.value)} + className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white" + /> +
+ +
+ + setConfirmPassword(e.target.value)} + className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 dark:border-gray-700 dark:bg-gray-800 dark:text-white" + /> +
+ + +
+ +
+

+ Already have an account?{" "} + + Sign in + +

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/surfsense_web/components/Navbar.tsx b/surfsense_web/components/Navbar.tsx index fcc45f0..d61e008 100644 --- a/surfsense_web/components/Navbar.tsx +++ b/surfsense_web/components/Navbar.tsx @@ -1,6 +1,6 @@ "use client"; import { cn } from "@/lib/utils"; -import { IconMenu2, IconX, IconBrandGoogleFilled } from "@tabler/icons-react"; +import { IconMenu2, IconX, IconBrandGoogleFilled, IconUser } from "@tabler/icons-react"; import { motion, AnimatePresence, @@ -62,7 +62,7 @@ export const Navbar = () => { const DesktopNav = ({ navItems, visible }: NavbarProps) => { const [hoveredIndex, setHoveredIndex] = useState(null); - + const handleGoogleLogin = () => { // Redirect to the login page window.location.href = '/login'; @@ -73,8 +73,8 @@ const DesktopNav = ({ navItems, visible }: NavbarProps) => { onMouseLeave={() => setHoveredIndex(null)} animate={{ backdropFilter: "blur(16px)", - background: visible - ? "rgba(var(--background-rgb), 0.8)" + background: visible + ? "rgba(var(--background-rgb), 0.8)" : "rgba(var(--background-rgb), 0.6)", width: visible ? "38%" : "80%", height: visible ? "48px" : "64px", @@ -99,7 +99,7 @@ const DesktopNav = ({ navItems, visible }: NavbarProps) => { } as React.CSSProperties} >
- + SurfSense
@@ -175,8 +175,8 @@ const DesktopNav = ({ navItems, visible }: NavbarProps) => { variant="outline" className="hidden cursor-pointer md:flex items-center gap-2 rounded-full dark:bg-white/20 dark:hover:bg-white/30 dark:text-white bg-gray-100 hover:bg-gray-200 text-gray-800 border-0" > - - Sign in with Google + + Sign in )} @@ -188,19 +188,19 @@ const DesktopNav = ({ navItems, visible }: NavbarProps) => { const MobileNav = ({ navItems, visible }: NavbarProps) => { const [open, setOpen] = useState(false); - + const handleGoogleLogin = () => { // Redirect to the login page window.location.href = "./login"; }; - + return ( <> { } as React.CSSProperties} >
- +
{open ? ( @@ -278,8 +278,8 @@ const MobileNav = ({ navItems, visible }: NavbarProps) => { variant="outline" className="flex cursor-pointer items-center gap-2 mt-4 w-full justify-center rounded-full dark:bg-white/20 dark:hover:bg-white/30 dark:text-white bg-gray-100 hover:bg-gray-200 text-gray-800 border-0" > - - Sign in with Google + + Sign in )} diff --git a/surfsense_web/content/docs/docker-installation.mdx b/surfsense_web/content/docs/docker-installation.mdx index 6e64cd5..aac7cc7 100644 --- a/surfsense_web/content/docs/docker-installation.mdx +++ b/surfsense_web/content/docs/docker-installation.mdx @@ -82,8 +82,7 @@ Before you begin, ensure you have: | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | DATABASE_URL | PostgreSQL connection string (e.g., `postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense`) | | SECRET_KEY | JWT Secret key for authentication (should be a secure random string) | -| GOOGLE_OAUTH_CLIENT_ID | Google OAuth client ID obtained from Google Cloud Console | -| GOOGLE_OAUTH_CLIENT_SECRET | Google OAuth client secret obtained from Google Cloud Console | +| AUTH_TYPE | Authentication method: `GOOGLE` for OAuth with Google, `LOCAL` for email/password authentication | | NEXT_FRONTEND_URL | URL where your frontend application is hosted (e.g., `http://localhost:3000`) | | EMBEDDING_MODEL | Name of the embedding model (e.g., `openai://text-embedding-ada-002`, `anthropic://claude-v1`, `mixedbread-ai/mxbai-embed-large-v1`) | | RERANKERS_MODEL_NAME | Name of the reranker model (e.g., `ms-marco-MiniLM-L-12-v2`) | @@ -96,10 +95,21 @@ Before you begin, ensure you have: | TTS_SERVICE | Text-to-Speech API provider for Podcasts (e.g., `openai/tts-1`, `azure/neural`, `vertex_ai/`). See [supported providers](https://docs.litellm.ai/docs/text_to_speech#supported-providers) | | STT_SERVICE | Speech-to-Text API provider for Podcasts (e.g., `openai/whisper-1`). See [supported providers](https://docs.litellm.ai/docs/audio_transcription#supported-providers) | -Include API keys for the LLM providers you're using. For example: -- `OPENAI_API_KEY`: If using OpenAI models -- `GEMINI_API_KEY`: If using Google Gemini models +Include API keys for your chosen LLM providers: + +| ENV VARIABLE | DESCRIPTION | +|--------------------|-----------------------------------------------------------------------------| +| `OPENAI_API_KEY` | Required if using OpenAI models | +| `GEMINI_API_KEY` | Required if using Google Gemini models | +| `ANTHROPIC_API_KEY`| Required if using Anthropic models | + +### Google OAuth Configuration (if AUTH_TYPE=GOOGLE) + +| ENV VARIABLE | DESCRIPTION | +|----------------------------|-----------------------------------------------------------------------------| +| `GOOGLE_OAUTH_CLIENT_ID` | Client ID from Google Cloud Console | +| `GOOGLE_OAUTH_CLIENT_SECRET` | Client secret from Google Cloud Console | **Optional Backend LangSmith Observability:** | ENV VARIABLE | DESCRIPTION | @@ -125,6 +135,7 @@ For other LLM providers, refer to the [LiteLLM documentation](https://docs.litel | ENV VARIABLE | DESCRIPTION | | ------------------------------- | ---------------------------------------------------------- | | NEXT_PUBLIC_FASTAPI_BACKEND_URL | URL of the backend service (e.g., `http://localhost:8000`) | +| NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE | Same value as set in backend AUTH_TYPE i.e `GOOGLE` for OAuth with Google, `LOCAL` for email/password authentication | 2. **Build and Start Containers** diff --git a/surfsense_web/content/docs/index.mdx b/surfsense_web/content/docs/index.mdx index f3411b8..4845a73 100644 --- a/surfsense_web/content/docs/index.mdx +++ b/surfsense_web/content/docs/index.mdx @@ -47,9 +47,11 @@ See the [installation notes](https://github.com/pgvector/pgvector/tree/master#in --- -## Google OAuth Setup +## Google OAuth Setup (Optional) -SurfSense user management and authentication works on Google OAuth. Lets set it up. +SurfSense supports both Google OAuth and local email/password authentication. Google OAuth is optional - if you prefer local authentication, you can skip this section. + +To set up Google OAuth: 1. Login to your [Google Developer Console](https://console.cloud.google.com/) 2. Enable People API. diff --git a/surfsense_web/content/docs/manual-installation.mdx b/surfsense_web/content/docs/manual-installation.mdx index b3999dc..72492c1 100644 --- a/surfsense_web/content/docs/manual-installation.mdx +++ b/surfsense_web/content/docs/manual-installation.mdx @@ -53,25 +53,37 @@ Edit the `.env` file and set the following variables: | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | DATABASE_URL | PostgreSQL connection string (e.g., `postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense`) | | SECRET_KEY | JWT Secret key for authentication (should be a secure random string) | -| GOOGLE_OAUTH_CLIENT_ID | Google OAuth client ID | -| GOOGLE_OAUTH_CLIENT_SECRET | Google OAuth client secret | -| NEXT_FRONTEND_URL | Frontend application URL (e.g., `http://localhost:3000`) | +| AUTH_TYPE | Authentication method: `GOOGLE` for OAuth with Google, `LOCAL` for email/password authentication | +| NEXT_FRONTEND_URL | URL where your frontend application is hosted (e.g., `http://localhost:3000`) | | EMBEDDING_MODEL | Name of the embedding model (e.g., `openai://text-embedding-ada-002`, `anthropic://claude-v1`, `mixedbread-ai/mxbai-embed-large-v1`) | | RERANKERS_MODEL_NAME | Name of the reranker model (e.g., `ms-marco-MiniLM-L-12-v2`) | | RERANKERS_MODEL_TYPE | Type of reranker model (e.g., `flashrank`) | -| FAST_LLM | LiteLLM routed faster LLM (e.g., `openai/gpt-4o-mini`, `ollama/deepseek-r1:8b`) | -| STRATEGIC_LLM | LiteLLM routed advanced LLM (e.g., `openai/gpt-4o`, `ollama/gemma3:12b`) | -| LONG_CONTEXT_LLM | LiteLLM routed long-context LLM (e.g., `gemini/gemini-2.0-flash`, `ollama/deepseek-r1:8b`) | -| UNSTRUCTURED_API_KEY | API key for Unstructured.io service | -| FIRECRAWL_API_KEY | API key for Firecrawl service (if using crawler) | +| FAST_LLM | LiteLLM routed smaller, faster LLM (e.g., `openai/gpt-4o-mini`, `ollama/deepseek-r1:8b`) | +| STRATEGIC_LLM | LiteLLM routed advanced LLM for complex tasks (e.g., `openai/gpt-4o`, `ollama/gemma3:12b`) | +| LONG_CONTEXT_LLM | LiteLLM routed LLM for longer context windows (e.g., `gemini/gemini-2.0-flash`, `ollama/deepseek-r1:8b`) | +| UNSTRUCTURED_API_KEY | API key for Unstructured.io service for document parsing | +| FIRECRAWL_API_KEY | API key for Firecrawl service for web crawling | | TTS_SERVICE | Text-to-Speech API provider for Podcasts (e.g., `openai/tts-1`, `azure/neural`, `vertex_ai/`). See [supported providers](https://docs.litellm.ai/docs/text_to_speech#supported-providers) | | STT_SERVICE | Speech-to-Text API provider for Podcasts (e.g., `openai/whisper-1`). See [supported providers](https://docs.litellm.ai/docs/audio_transcription#supported-providers) | -**Important**: Since LLM calls are routed through LiteLLM, include API keys for the LLM providers you're using: -- For OpenAI models: `OPENAI_API_KEY` -- For Google Gemini models: `GEMINI_API_KEY` -- For other providers, refer to the [LiteLLM documentation](https://docs.litellm.ai/docs/providers) +Include API keys for your chosen LLM providers: + +| ENV VARIABLE | DESCRIPTION | +|--------------------|-----------------------------------------------------------------------------| +| `OPENAI_API_KEY` | Required if using OpenAI models | +| `GEMINI_API_KEY` | Required if using Google Gemini models | +| `ANTHROPIC_API_KEY`| Required if using Anthropic models | + +For other providers, refer to the [LiteLLM documentation](https://docs.litellm.ai/docs/providers) + +### Google OAuth Configuration (if AUTH_TYPE=GOOGLE) + +| ENV VARIABLE | DESCRIPTION | +|----------------------------|-----------------------------------------------------------------------------| +| `GOOGLE_OAUTH_CLIENT_ID` | Client ID from Google Cloud Console | +| `GOOGLE_OAUTH_CLIENT_SECRET` | Client secret from Google Cloud Console | + **Optional Backend LangSmith Observability:** | ENV VARIABLE | DESCRIPTION | @@ -169,6 +181,7 @@ Edit the `.env` file and set: | ENV VARIABLE | DESCRIPTION | | ------------------------------- | ------------------------------------------- | | NEXT_PUBLIC_FASTAPI_BACKEND_URL | Backend URL (e.g., `http://localhost:8000`) | +| NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE | Same value as set in backend AUTH_TYPE i.e `GOOGLE` for OAuth with Google, `LOCAL` for email/password authentication | ### 2. Install Dependencies