Skip to content

Commit 94c7913

Browse files
authored
Merge pull request #110 from MODSetter/dev
feat: Removed Hard Dependency on Google Auth
2 parents c290146 + 521ee4a commit 94c7913

File tree

17 files changed

+533
-123
lines changed

17 files changed

+533
-123
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ Both installation guides include detailed OS-specific instructions for Windows,
130130

131131
Before installation, make sure to complete the [prerequisite setup steps](https://www.surfsense.net/docs/) including:
132132
- PGVector setup
133-
- Google OAuth configuration
134133
- Unstructured.io API key
135134
- Other required API keys
136135

surfsense_backend/.env.example

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense"
22

33
SECRET_KEY="SECRET"
4+
NEXT_FRONTEND_URL="http://localhost:3000"
5+
6+
#Auth
7+
AUTH_TYPE="GOOGLE" or "LOCAL"
8+
# For Google Auth Only
49
GOOGLE_OAUTH_CLIENT_ID="924507538m"
510
GOOGLE_OAUTH_CLIENT_SECRET="GOCSV"
6-
NEXT_FRONTEND_URL="http://localhost:3000"
711

12+
#Embedding Model
813
EMBEDDING_MODEL="mixedbread-ai/mxbai-embed-large-v1"
914

1015
RERANKERS_MODEL_NAME="ms-marco-MiniLM-L-12-v2"

surfsense_backend/app/app.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@
66
from sqlalchemy.ext.asyncio import AsyncSession
77

88
from app.db import User, create_db_and_tables, get_async_session
9-
from app.retriver.chunks_hybrid_search import ChucksHybridSearchRetriever
109
from app.schemas import UserCreate, UserRead, UserUpdate
10+
11+
12+
from app.routes import router as crud_router
13+
from app.config import config
14+
1115
from app.users import (
1216
SECRET,
1317
auth_backend,
1418
fastapi_users,
15-
google_oauth_client,
16-
current_active_user,
19+
current_active_user
1720
)
18-
from app.routes import router as crud_router
1921

2022

2123
@asynccontextmanager
@@ -59,16 +61,20 @@ async def lifespan(app: FastAPI):
5961
prefix="/users",
6062
tags=["users"],
6163
)
62-
app.include_router(
63-
fastapi_users.get_oauth_router(
64-
google_oauth_client,
65-
auth_backend,
66-
SECRET,
67-
is_verified_by_default=True
68-
),
69-
prefix="/auth/google",
70-
tags=["auth"],
71-
)
64+
65+
if config.AUTH_TYPE == "GOOGLE":
66+
from app.users import google_oauth_client
67+
app.include_router(
68+
fastapi_users.get_oauth_router(
69+
google_oauth_client,
70+
auth_backend,
71+
SECRET,
72+
is_verified_by_default=True
73+
),
74+
prefix="/auth/google",
75+
tags=["auth"],
76+
)
77+
7278
app.include_router(crud_router, prefix="/api/v1", tags=["crud"])
7379

7480

surfsense_backend/app/config/__init__.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,17 @@ class Config:
3838

3939
# Database
4040
DATABASE_URL = os.getenv("DATABASE_URL")
41-
42-
# AUTH: Google OAuth
43-
GOOGLE_OAUTH_CLIENT_ID = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
44-
GOOGLE_OAUTH_CLIENT_SECRET = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
41+
4542
NEXT_FRONTEND_URL = os.getenv("NEXT_FRONTEND_URL")
4643

44+
45+
# AUTH: Google OAuth
46+
AUTH_TYPE = os.getenv("AUTH_TYPE")
47+
if AUTH_TYPE == "GOOGLE":
48+
GOOGLE_OAUTH_CLIENT_ID = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
49+
GOOGLE_OAUTH_CLIENT_SECRET = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
50+
51+
4752
# LONG-CONTEXT LLMS
4853
LONG_CONTEXT_LLM = os.getenv("LONG_CONTEXT_LLM")
4954
LONG_CONTEXT_LLM_API_BASE = os.getenv("LONG_CONTEXT_LLM_API_BASE")

surfsense_backend/app/db.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
from enum import Enum
44

55
from fastapi import Depends
6-
from fastapi_users.db import (
7-
SQLAlchemyBaseOAuthAccountTableUUID,
8-
SQLAlchemyBaseUserTableUUID,
9-
SQLAlchemyUserDatabase,
10-
)
6+
117
from pgvector.sqlalchemy import Vector
128
from sqlalchemy import (
139
ARRAY,
@@ -30,6 +26,18 @@
3026
from app.retriver.chunks_hybrid_search import ChucksHybridSearchRetriever
3127
from app.retriver.documents_hybrid_search import DocumentHybridSearchRetriever
3228

29+
if config.AUTH_TYPE == "GOOGLE":
30+
from fastapi_users.db import (
31+
SQLAlchemyBaseOAuthAccountTableUUID,
32+
SQLAlchemyBaseUserTableUUID,
33+
SQLAlchemyUserDatabase,
34+
)
35+
else:
36+
from fastapi_users.db import (
37+
SQLAlchemyBaseUserTableUUID,
38+
SQLAlchemyUserDatabase,
39+
)
40+
3341
DATABASE_URL = config.DATABASE_URL
3442

3543

@@ -141,17 +149,22 @@ class SearchSourceConnector(BaseModel, TimestampMixin):
141149
user_id = Column(UUID(as_uuid=True), ForeignKey("user.id", ondelete='CASCADE'), nullable=False)
142150
user = relationship("User", back_populates="search_source_connectors")
143151

152+
if config.AUTH_TYPE == "GOOGLE":
153+
class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
154+
pass
144155

145-
class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
146-
pass
147156

157+
class User(SQLAlchemyBaseUserTableUUID, Base):
158+
oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
159+
"OAuthAccount", lazy="joined"
160+
)
161+
search_spaces = relationship("SearchSpace", back_populates="user")
162+
search_source_connectors = relationship("SearchSourceConnector", back_populates="user")
163+
else:
164+
class User(SQLAlchemyBaseUserTableUUID, Base):
148165

149-
class User(SQLAlchemyBaseUserTableUUID, Base):
150-
oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
151-
"OAuthAccount", lazy="joined"
152-
)
153-
search_spaces = relationship("SearchSpace", back_populates="user")
154-
search_source_connectors = relationship("SearchSourceConnector", back_populates="user")
166+
search_spaces = relationship("SearchSpace", back_populates="user")
167+
search_source_connectors = relationship("SearchSourceConnector", back_populates="user")
155168

156169

157170
engine = create_async_engine(DATABASE_URL)
@@ -180,8 +193,12 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
180193
yield session
181194

182195

183-
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
184-
yield SQLAlchemyUserDatabase(session, User, OAuthAccount)
196+
if config.AUTH_TYPE == "GOOGLE":
197+
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
198+
yield SQLAlchemyUserDatabase(session, User, OAuthAccount)
199+
else:
200+
async def get_user_db(session: AsyncSession = Depends(get_async_session)):
201+
yield SQLAlchemyUserDatabase(session, User)
185202

186203
async def get_chucks_hybrid_search_retriever(session: AsyncSession = Depends(get_async_session)):
187204
return ChucksHybridSearchRetriever(session)

surfsense_backend/app/users.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
JWTStrategy,
1111
)
1212
from fastapi_users.db import SQLAlchemyUserDatabase
13-
from httpx_oauth.clients.google import GoogleOAuth2
14-
13+
from fastapi.responses import JSONResponse
14+
from fastapi_users.schemas import model_dump
1515
from app.config import config
1616
from app.db import User, get_user_db
1717
from pydantic import BaseModel
@@ -22,10 +22,13 @@ class BearerResponse(BaseModel):
2222

2323
SECRET = config.SECRET_KEY
2424

25-
google_oauth_client = GoogleOAuth2(
26-
config.GOOGLE_OAUTH_CLIENT_ID,
27-
config.GOOGLE_OAUTH_CLIENT_SECRET,
28-
)
25+
if config.AUTH_TYPE == "GOOGLE":
26+
from httpx_oauth.clients.google import GoogleOAuth2
27+
28+
google_oauth_client = GoogleOAuth2(
29+
config.GOOGLE_OAUTH_CLIENT_ID,
30+
config.GOOGLE_OAUTH_CLIENT_SECRET,
31+
)
2932

3033

3134
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
@@ -79,7 +82,10 @@ class CustomBearerTransport(BearerTransport):
7982
async def get_login_response(self, token: str) -> Response:
8083
bearer_response = BearerResponse(access_token=token, token_type="bearer")
8184
redirect_url = f"{config.NEXT_FRONTEND_URL}/auth/callback?token={bearer_response.access_token}"
82-
return RedirectResponse(redirect_url, status_code=302)
85+
if config.AUTH_TYPE == "GOOGLE":
86+
return RedirectResponse(redirect_url, status_code=302)
87+
else:
88+
return JSONResponse(model_dump(bearer_response))
8389

8490
bearer_transport = CustomBearerTransport(tokenUrl="auth/jwt/login")
8591

surfsense_web/.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
NEXT_PUBLIC_FASTAPI_BACKEND_URL=http://localhost:8000
1+
NEXT_PUBLIC_FASTAPI_BACKEND_URL=http://localhost:8000
2+
NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE=LOCAL or GOOGLE

surfsense_web/app/dashboard/page.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React from 'react'
44
import Link from 'next/link'
55
import { motion } from 'framer-motion'
66
import { Button } from '@/components/ui/button'
7-
import { Plus, Search, Trash2, AlertCircle, Loader2 } from 'lucide-react'
7+
import { Plus, Search, Trash2, AlertCircle, Loader2, LogOut } from 'lucide-react'
88
import { Tilt } from '@/components/ui/tilt'
99
import { Spotlight } from '@/components/ui/spotlight'
1010
import { Logo } from '@/components/Logo';
@@ -145,11 +145,19 @@ const DashboardPage = () => {
145145
},
146146
};
147147

148+
const router = useRouter();
148149
const { searchSpaces, loading, error, refreshSearchSpaces } = useSearchSpaces();
149150

150151
if (loading) return <LoadingScreen />;
151152
if (error) return <ErrorScreen message={error} />;
152153

154+
const handleLogout = () => {
155+
if (typeof window !== 'undefined') {
156+
localStorage.removeItem('surfsense_bearer_token');
157+
router.push('/');
158+
}
159+
};
160+
153161
const handleDeleteSearchSpace = async (id: number) => {
154162
// Send DELETE request to the API
155163
try {
@@ -193,7 +201,18 @@ const DashboardPage = () => {
193201
</p>
194202
</div>
195203
</div>
196-
<ThemeTogglerComponent />
204+
<div className="flex items-center space-x-3">
205+
<Button
206+
variant="ghost"
207+
size="icon"
208+
onClick={handleLogout}
209+
className="h-9 w-9 rounded-full"
210+
aria-label="Logout"
211+
>
212+
<LogOut className="h-5 w-5" />
213+
</Button>
214+
<ThemeTogglerComponent />
215+
</div>
197216
</div>
198217

199218
<div className="flex flex-col space-y-6 mt-6">
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
import React from "react";
3+
4+
export const AmbientBackground = () => {
5+
return (
6+
<div className="pointer-events-none absolute left-0 top-0 z-0 h-screen w-screen">
7+
<div
8+
style={{
9+
transform: "translateY(-350px) rotate(-45deg)",
10+
width: "560px",
11+
height: "1380px",
12+
background:
13+
"radial-gradient(68.54% 68.72% at 55.02% 31.46%, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.02) 50%, rgba(59, 130, 246, 0) 100%)",
14+
}}
15+
className="absolute left-0 top-0"
16+
/>
17+
<div
18+
style={{
19+
transform: "rotate(-45deg) translate(5%, -50%)",
20+
transformOrigin: "top left",
21+
width: "240px",
22+
height: "1380px",
23+
background:
24+
"radial-gradient(50% 50% at 50% 50%, rgba(59, 130, 246, 0.06) 0%, rgba(59, 130, 246, 0.02) 80%, transparent 100%)",
25+
}}
26+
className="absolute left-0 top-0"
27+
/>
28+
<div
29+
style={{
30+
position: "absolute",
31+
borderRadius: "20px",
32+
transform: "rotate(-45deg) translate(-180%, -70%)",
33+
transformOrigin: "top left",
34+
width: "240px",
35+
height: "1380px",
36+
background:
37+
"radial-gradient(50% 50% at 50% 50%, rgba(59, 130, 246, 0.04) 0%, rgba(59, 130, 246, 0.02) 80%, transparent 100%)",
38+
}}
39+
className="absolute left-0 top-0"
40+
/>
41+
</div>
42+
);
43+
};

surfsense_web/app/login/GoogleLoginButton.tsx

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from "react";
33
import { IconBrandGoogleFilled } from "@tabler/icons-react";
44
import { motion } from "framer-motion";
55
import { Logo } from "@/components/Logo";
6+
import { AmbientBackground } from "./AmbientBackground";
67

78
export function GoogleLoginButton() {
89
const handleGoogleLogin = () => {
@@ -88,47 +89,4 @@ export function GoogleLoginButton() {
8889
</div>
8990
</div>
9091
);
91-
}
92-
93-
94-
95-
const AmbientBackground = () => {
96-
return (
97-
<div className="pointer-events-none absolute left-0 top-0 z-0 h-screen w-screen">
98-
<div
99-
style={{
100-
transform: "translateY(-350px) rotate(-45deg)",
101-
width: "560px",
102-
height: "1380px",
103-
background:
104-
"radial-gradient(68.54% 68.72% at 55.02% 31.46%, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.02) 50%, rgba(59, 130, 246, 0) 100%)",
105-
}}
106-
className="absolute left-0 top-0"
107-
/>
108-
<div
109-
style={{
110-
transform: "rotate(-45deg) translate(5%, -50%)",
111-
transformOrigin: "top left",
112-
width: "240px",
113-
height: "1380px",
114-
background:
115-
"radial-gradient(50% 50% at 50% 50%, rgba(59, 130, 246, 0.06) 0%, rgba(59, 130, 246, 0.02) 80%, transparent 100%)",
116-
}}
117-
className="absolute left-0 top-0"
118-
/>
119-
<div
120-
style={{
121-
position: "absolute",
122-
borderRadius: "20px",
123-
transform: "rotate(-45deg) translate(-180%, -70%)",
124-
transformOrigin: "top left",
125-
width: "240px",
126-
height: "1380px",
127-
background:
128-
"radial-gradient(50% 50% at 50% 50%, rgba(59, 130, 246, 0.04) 0%, rgba(59, 130, 246, 0.02) 80%, transparent 100%)",
129-
}}
130-
className="absolute left-0 top-0"
131-
/>
132-
</div>
133-
);
134-
};
92+
}

0 commit comments

Comments
 (0)