Skip to content

Commit 8d897d8

Browse files
authored
Merge pull request #226 from Two-Weeks-Team/feature/google-oauth
feat(auth): Google OAuth with domain-based access control
2 parents c86b7c0 + 2132108 commit 8d897d8

22 files changed

Lines changed: 1599 additions & 73 deletions

File tree

.do/app.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ alerts:
77

88
ingress:
99
rules:
10+
# NextAuth OAuth routes → web (must be before /api catch-all)
11+
- component:
12+
name: web
13+
match:
14+
path:
15+
prefix: /api/auth
1016
# API endpoints → backend
1117
- component:
1218
name: api
@@ -130,3 +136,18 @@ services:
130136
scope: RUN_TIME
131137
type: SECRET
132138
value: REPLACE_WITH_API_KEY
139+
- key: GOOGLE_CLIENT_ID
140+
scope: RUN_TIME
141+
type: SECRET
142+
value: REPLACE_WITH_GOOGLE_CLIENT_ID
143+
- key: GOOGLE_CLIENT_SECRET
144+
scope: RUN_TIME
145+
type: SECRET
146+
value: REPLACE_WITH_GOOGLE_CLIENT_SECRET
147+
- key: AUTH_SECRET
148+
scope: RUN_TIME
149+
type: SECRET
150+
value: REPLACE_WITH_AUTH_SECRET
151+
- key: AUTH_URL
152+
scope: RUN_TIME
153+
value: ${APP_URL}

agent/db/connection.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
_pool: asyncpg.Pool | None = None
77
_pool_lock: asyncio.Lock | None = None
8+
_users_table_ensured: bool = False
89
_DEFAULT_POOL_MIN_SIZE = 2
910
_DEFAULT_POOL_MAX_SIZE = 10
1011
_DEFAULT_POOL_RETRIES = 5
@@ -31,8 +32,28 @@ def _float_env(name: str, default: float, minimum: float = 0.0) -> float:
3132
return default
3233

3334

35+
async def ensure_users_table(pool: asyncpg.Pool) -> None:
36+
"""Create the users table if it does not already exist (idempotent)."""
37+
async with pool.acquire() as conn:
38+
await conn.execute(
39+
"""
40+
CREATE TABLE IF NOT EXISTS users (
41+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
42+
email TEXT UNIQUE NOT NULL,
43+
name TEXT,
44+
image TEXT,
45+
approved BOOLEAN NOT NULL DEFAULT FALSE,
46+
domain TEXT NOT NULL,
47+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
48+
last_login_at TIMESTAMPTZ NOT NULL DEFAULT now()
49+
);
50+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
51+
"""
52+
)
53+
54+
3455
async def get_pool() -> asyncpg.Pool:
35-
global _pool, _pool_lock
56+
global _pool, _pool_lock, _users_table_ensured
3657
if _pool_lock is None:
3758
_pool_lock = asyncio.Lock()
3859
if _pool is not None:
@@ -65,6 +86,16 @@ async def get_pool() -> asyncpg.Pool:
6586
await asyncio.sleep(retry_delay * (attempt + 1))
6687
if _pool is None and last_error is not None:
6788
raise last_error
89+
if _pool is not None and not _users_table_ensured:
90+
try:
91+
await ensure_users_table(_pool)
92+
_users_table_ensured = True
93+
except Exception:
94+
import logging
95+
96+
logging.getLogger(__name__).warning(
97+
"Could not create users table — DB may not be ready yet"
98+
)
6899
return _pool
69100

70101

0 commit comments

Comments
 (0)