Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d475b3876a | |||
| 76bcc53992 | |||
| 470b615528 | |||
| f26f8f7e56 | |||
| 78b7831d43 | |||
| e45b510519 | |||
| f25044ea7e | |||
| b637fd9c11 | |||
| 983ee2c398 | |||
| 8af7b37b38 | |||
| b21a30b2e7 | |||
| 361ad3acc2 | |||
| 5e165d277e |
@@ -95,7 +95,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
CHROME_PATH=$(find /home/runner/.cache/ms-playwright -name chrome -type f 2>/dev/null | head -1)
|
CHROME_PATH=$(find /home/runner/.cache/ms-playwright -name chrome -type f 2>/dev/null | head -1)
|
||||||
npm install -g @lhci/cli
|
npm install -g @lhci/cli
|
||||||
LHCI_CHROME_PATH="$CHROME_PATH" lhci autorun
|
CHROME_PATH="$CHROME_PATH" lhci autorun --chrome-flags="--headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage"
|
||||||
|
|
||||||
build-and-push:
|
build-and-push:
|
||||||
runs-on: runners-cartsnitch
|
runs-on: runners-cartsnitch
|
||||||
|
|||||||
+1
-1
@@ -30,4 +30,4 @@ EXPOSE 8000
|
|||||||
HEALTHCHECK --interval=30s --timeout=3s \
|
HEALTHCHECK --interval=30s --timeout=3s \
|
||||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
|
||||||
|
|
||||||
CMD ["uvicorn", "cartsnitch_api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["sh", "-c", "python -m alembic upgrade head && uvicorn cartsnitch_api.main:app --host 0.0.0.0 --port 8000"]
|
||||||
@@ -5,6 +5,7 @@ Sessions are verified by querying the shared sessions table directly.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
from hashlib import sha256
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import Cookie, Depends, Header, HTTPException, Request, status
|
from fastapi import Cookie, Depends, Header, HTTPException, Request, status
|
||||||
@@ -27,10 +28,13 @@ async def _validate_session_token(token: str, db: AsyncSession) -> UUID:
|
|||||||
"""Validate a Better-Auth session token against the sessions table.
|
"""Validate a Better-Auth session token against the sessions table.
|
||||||
|
|
||||||
Returns the user_id (as UUID) if the session is valid and not expired.
|
Returns the user_id (as UUID) if the session is valid and not expired.
|
||||||
|
Better-Auth v1.5.6+ stores tokens as SHA-256 hashes, so we hash the
|
||||||
|
incoming raw token before querying.
|
||||||
"""
|
"""
|
||||||
|
hashed_token = sha256(token.encode("utf-8")).hexdigest()
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
text("SELECT user_id, expires_at FROM sessions WHERE token = :token"),
|
text("SELECT user_id, expires_at FROM sessions WHERE token = :token"),
|
||||||
{"token": token},
|
{"token": hashed_token},
|
||||||
)
|
)
|
||||||
row = result.first()
|
row = result.first()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import APIRouter, FastAPI
|
||||||
|
|
||||||
from cartsnitch_api.auth.routes import router as auth_router
|
from cartsnitch_api.auth.routes import router as auth_router
|
||||||
from cartsnitch_api.middleware.cors import add_cors_middleware
|
from cartsnitch_api.middleware.cors import add_cors_middleware
|
||||||
@@ -46,15 +46,19 @@ def create_app() -> FastAPI:
|
|||||||
# Routers
|
# Routers
|
||||||
app.include_router(health_router)
|
app.include_router(health_router)
|
||||||
app.include_router(auth_router)
|
app.include_router(auth_router)
|
||||||
app.include_router(stores_router)
|
|
||||||
app.include_router(purchases_router)
|
# Data endpoints mounted under /api/v1
|
||||||
app.include_router(products_router)
|
v1_router = APIRouter(prefix="/api/v1")
|
||||||
app.include_router(prices_router)
|
v1_router.include_router(stores_router)
|
||||||
app.include_router(coupons_router)
|
v1_router.include_router(purchases_router)
|
||||||
app.include_router(shopping_router)
|
v1_router.include_router(products_router)
|
||||||
app.include_router(alerts_router)
|
v1_router.include_router(prices_router)
|
||||||
app.include_router(scraping_router)
|
v1_router.include_router(coupons_router)
|
||||||
app.include_router(public_router)
|
v1_router.include_router(shopping_router)
|
||||||
|
v1_router.include_router(alerts_router)
|
||||||
|
v1_router.include_router(scraping_router)
|
||||||
|
v1_router.include_router(public_router)
|
||||||
|
app.include_router(v1_router)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -3,7 +3,12 @@
|
|||||||
"collect": {
|
"collect": {
|
||||||
"staticDistDir": "./dist",
|
"staticDistDir": "./dist",
|
||||||
"url": ["http://localhost:4173/"],
|
"url": ["http://localhost:4173/"],
|
||||||
"numberOfRuns": 1
|
"numberOfRuns": 1,
|
||||||
|
"settings": {
|
||||||
|
"chromeFlags": ["--headless=new", "--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage"],
|
||||||
|
"skipAudits": ["bf-cache"],
|
||||||
|
"disableFullPageScreenshot": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"assert": {
|
"assert": {
|
||||||
"assertions": {
|
"assertions": {
|
||||||
@@ -16,4 +21,4 @@
|
|||||||
"target": "temporary-public-storage"
|
"target": "temporary-public-storage"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+8
-2
@@ -31,8 +31,14 @@ export function Login() {
|
|||||||
throw new Error(authError.message ?? 'Sign in failed')
|
throw new Error(authError.message ?? 'Sign in failed')
|
||||||
}
|
}
|
||||||
|
|
||||||
setAuthenticated(true)
|
// After successful signIn, force a session fetch to confirm the cookie is set
|
||||||
navigate('/')
|
// before navigating to the protected route
|
||||||
|
const sessionResult = await authClient.getSession()
|
||||||
|
if (sessionResult.data) {
|
||||||
|
navigate('/')
|
||||||
|
} else {
|
||||||
|
setError('Sign in failed. Please try again.')
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (import.meta.env.VITE_MOCK_AUTH === 'true') {
|
if (import.meta.env.VITE_MOCK_AUTH === 'true') {
|
||||||
setAuthenticated(true)
|
setAuthenticated(true)
|
||||||
|
|||||||
@@ -38,8 +38,15 @@ export function Register() {
|
|||||||
throw new Error(authError.message ?? 'Registration failed')
|
throw new Error(authError.message ?? 'Registration failed')
|
||||||
}
|
}
|
||||||
|
|
||||||
setAuthenticated(true)
|
// After successful signUp, force a session fetch to confirm the cookie is set
|
||||||
navigate('/')
|
// before navigating to the protected route
|
||||||
|
const sessionResult = await authClient.getSession()
|
||||||
|
if (sessionResult.data) {
|
||||||
|
navigate('/')
|
||||||
|
} else {
|
||||||
|
// Session not established — show success message and link to login
|
||||||
|
setError('Account created! Please sign in.')
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (import.meta.env.VITE_MOCK_AUTH === 'true') {
|
if (import.meta.env.VITE_MOCK_AUTH === 'true') {
|
||||||
setAuthenticated(true)
|
setAuthenticated(true)
|
||||||
|
|||||||
Reference in New Issue
Block a user