Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions carbonserver/carbonserver/api/routers/authenticate.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
LOGGER = logging.getLogger(__name__)
OAUTH_SCOPES = ["openid", "email", "profile"]
SESSION_COOKIE_NAME = "user_session"
DEFAULT_REDIRECT_URL = (
f"{(settings.frontend_url or 'http://localhost:3000').rstrip('/')}/home"
)

router = APIRouter()

Expand Down Expand Up @@ -81,7 +84,7 @@ async def get_login(
login and redirect to frontend app with token
"""
if auth_provider is None:
raise HTTPException(status_code=501, detail="Authentication not configured")
return RedirectResponse(DEFAULT_REDIRECT_URL)
login_url = request.url_for("login")
if code:
try:
Expand Down Expand Up @@ -133,7 +136,7 @@ async def logout(
Logout user by clearing session and removing cookie
"""
if auth_provider is None:
raise HTTPException(status_code=501, detail="Authentication not configured")
return RedirectResponse(DEFAULT_REDIRECT_URL)

# Revoke the access token at the OIDC provider before clearing it locally
access_token = request.cookies.get(SESSION_COOKIE_NAME)
Expand Down
15 changes: 15 additions & 0 deletions carbonserver/carbonserver/api/services/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from carbonserver.api.services.auth_providers.oidc_auth_provider import (
OIDCAuthProvider,
)
from carbonserver.api.services.signup_service import SignUpService
from carbonserver.api.services.user_service import UserService
from carbonserver.config import settings
from carbonserver.container import ServerContainer
Expand All @@ -25,6 +26,11 @@ class FullUser:


SESSION_COOKIE_NAME = "user_session"
LOCAL_DEV_AUTH_USER = {
"sub": "d1b9d5e0-58e8-45f0-9ef5-4549b3d6f3f0",
"email": "local.user@example.com",
"fields": {"name": "Local user"},
}


web_scheme = APIKeyCookie(name=SESSION_COOKIE_NAME, auto_error=False)
Expand All @@ -49,11 +55,20 @@ async def __call__(
user_service: Optional[UserService] = Depends(
Provide[ServerContainer.user_service]
),
sign_up_service: Optional[SignUpService] = Depends(
Provide[ServerContainer.sign_up_service]
),
auth_provider: Optional[OIDCAuthProvider] = Depends(
Provide[ServerContainer.auth_provider]
),
):
self.user_service = user_service
if settings.auth_provider.lower() == "none":
self.auth_user = LOCAL_DEV_AUTH_USER
sign_up_service.check_jwt_user(self.auth_user, create=True)
self.db_user = user_service.get_user_by_id(self.auth_user["sub"])
return self

if cookie_token is not None:
self.auth_user = jwt.decode(
cookie_token,
Expand Down
12 changes: 6 additions & 6 deletions carbonserver/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Dockerfile

# Use Ubuntu to install Python and uv
# For production, you could use python:3.11-slim
# For production, you could use python:3.12-slim

FROM ubuntu:22.04@sha256:3c61d3759c2639d4b836d32a2d3c83fa0214e36f195a3421018dbaaf79cbe37f

Expand All @@ -17,10 +17,10 @@ RUN apt-get update && apt-get upgrade -y && \
apt-get install -y software-properties-common curl && \
add-apt-repository ppa:deadsnakes/ppa -y && \
apt-get update && \
apt-get install -y gcc libpq-dev python3.11 python3.11-dev
apt-get install -y gcc libpq-dev python3.12 python3.12-dev

RUN ln -sf /usr/bin/python3.11 /usr/bin/python && \
ln -sf /usr/bin/python3.11 /usr/bin/python3
RUN ln -sf /usr/bin/python3.12 /usr/bin/python && \
ln -sf /usr/bin/python3.12 /usr/bin/python3

# Download the latest UV installer
ADD https://astral.sh/uv/install.sh /uv-installer.sh
Expand All @@ -36,8 +36,8 @@ COPY pyproject.toml /app/
COPY codecarbon /app/codecarbon
COPY carbonserver /app/carbonserver

# Install dependencies using uv with the api dependency group
RUN uv pip install --system -e ".[api]"
# Install carbonserver dependencies.
RUN uv pip install --system -e ./carbonserver

COPY ./carbonserver/docker/entrypoint.sh /opt
RUN chmod a+x /opt/entrypoint.sh
Expand Down
51 changes: 51 additions & 0 deletions carbonserver/tests/api/service/test_auth_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from unittest import mock

import pytest
from starlette.requests import Request

from carbonserver.api.routers import authenticate
from carbonserver.api.services import auth_service


@pytest.mark.asyncio
async def test_no_auth_provider_uses_local_dev_user(monkeypatch):
monkeypatch.setattr(auth_service.settings, "auth_provider", "none")
user_service = mock.Mock()
sign_up_service = mock.Mock()

dependency = auth_service.UserWithAuthDependency(error_if_not_found=True)
result = await dependency(
user_service=user_service,
sign_up_service=sign_up_service,
auth_provider=None,
)

assert result.auth_user == auth_service.LOCAL_DEV_AUTH_USER
sign_up_service.check_jwt_user.assert_called_once_with(
auth_service.LOCAL_DEV_AUTH_USER, create=True
)
user_service.get_user_by_id.assert_called_once_with(
auth_service.LOCAL_DEV_AUTH_USER["sub"]
)


@pytest.mark.asyncio
async def test_no_auth_login_redirects_to_frontend(monkeypatch):
monkeypatch.setattr(authenticate.settings, "frontend_url", "http://localhost:3000")
request = Request(
{
"type": "http",
"method": "GET",
"path": "/auth/login",
"headers": [],
"scheme": "http",
"server": ("localhost", 8008),
"client": ("testclient", 50000),
"query_string": b"redirect=http%3A%2F%2Flocalhost%3A3000%2Fhome%3Fauth%3Dtrue",
}
)

response = await authenticate.get_login(request, auth_provider=None)

assert response.status_code == 307
assert response.headers["location"] == "http://localhost:3000/home"
146 changes: 32 additions & 114 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,133 +1,51 @@
# cp .env.example .env
# docker compose up -d
services:
###############################################
# Codecarbon-related services
###############################################
postgres:
image: postgres:13
environment:
POSTGRES_DB: codecarbon_db
POSTGRES_USER: codecarbon-user
POSTGRES_PASSWORD: supersecret
ports:
- "5432:5432"
volumes:
- postgres_codecarbon_data:/var/lib/postgresql/data

carbonserver:
depends_on:
- postgres
build:
context: .
dockerfile: ./carbonserver/docker/Dockerfile
depends_on:
- postgres
volumes:
- ./carbonserver:/carbonserver
labels:
- "traefik.enable=true"
# - >
# traefik.http.routers.carbonserver.rule=(
# Host(`${APP_HOSTNAME}`) && (
# PathPrefix(`/users`) || PathPrefix(`/auth`) || PathPrefix(`/docs`) ||
# PathPrefix(`/organizations`) || PathPrefix(`/runs`) || PathPrefix(`/emissions`) ||
# PathPrefix(`/projects`)|| PathPrefix(`/api`) || PathPrefix(`/auth-callback`)
# ))"
- "traefik.http.routers.carbonserver.rule=(Host(`${APP_HOSTNAME}`) && (PathPrefix(`/users`) || PathPrefix(`/auth`)|| PathPrefix(`/docs`)|| PathPrefix(`/organizations`) || PathPrefix(`/runs`) || PathPrefix(`/emissions`) || PathPrefix(`/projects`)|| PathPrefix(`/api`) || PathPrefix(`/auth-callback`) ))"
- "traefik.http.routers.carbonserver.entrypoints=web,websecure"
# - "traefik.http.routers.carbonserver.tls.certresolver=myresolver"
# - "traefik.http.routers.carbonserver.tls={}"
- "traefik.http.routers.carbonserver.priority=10000"
- "traefik.http.services.carbonserver.loadbalancer.server.port=8000"
- "traefik.docker.network=shared"
# ports:
# - "8000:8000"
env_file:
- ./.env
environment:
CODECARBON_LOG_LEVEL: DEBUG
DATABASE_URL: postgresql://${DATABASE_USER:-codecarbon-user}:${DATABASE_PASS:-supersecret}@${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}/${DATABASE_NAME:-codecarbon_db}
networks:
- default
- shared
AUTH_PROVIDER: ${AUTH_PROVIDER:-none}
ENVIRONMENT: ${ENVIRONMENT:-local}
DATABASE_URL: postgresql://codecarbon-user:supersecret@postgres:5432/codecarbon_db
FRONTEND_URL: http://localhost:3000
CORS_ORIGINS: http://localhost:3000
ports:
- "8008:8000"

ui:
build:
context: ./webapp
dockerfile: Dockerfile

# Set environment variables based on the .env file
env_file:
- ./webapp/.env.development
target: build
restart: always
labels:
- "traefik.enable=true"
- "traefik.http.routers.ui.rule=Host(`${APP_HOSTNAME}`)"
- "traefik.http.routers.ui.entrypoints=web,websecure"
# - "traefik.http.routers.ui.tls.certresolver=myresolver"
- "traefik.http.routers.ui.priority=1"
- "traefik.http.services.ui.loadbalancer.server.port=3000"
- "traefik.docker.network=shared"

# ports:
# - "3000:3000"
networks:
- default
- shared

postgres:
# container_name: ${DATABASE_HOST:-postgres_codecarbon}
depends_on:
- carbonserver
environment:
HOSTNAME: ${DATABASE_HOST:-postgres_codecarbon}
POSTGRES_DB: ${DATABASE_NAME:-codecarbon_db}
POSTGRES_PASSWORD: ${DATABASE_PASS:-supersecret}
POSTGRES_USER: ${DATABASE_USER:-codecarbon-user}
image: postgres:13
# ports:
# - 5480:5432
restart: unless-stopped
VITE_API_URL: http://localhost:8008
VITE_BASE_URL: http://localhost:3000
VITE_USE_MOCK_DATA: "false"
VITE_PROJECT_ENCRYPTION_KEY: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
volumes:
- postgres_codecarbon_data:/var/lib/postgresql/data:rw
networks:
- default

# pgadmin:
# # container_name: pgadmin_codecarbon
# image: dpage/pgadmin4
# environment:
# PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-test@test.com}
# PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-test}
# volumes:
# - pgadmin:/root/.pgadmin
# - ./carbonserver/docker/pgpassfile:/pgadmin4/pgpassfile
# - ./carbonserver/docker/pgadmin-servers.json:/pgadmin4/servers.json
# # ports:
# # - "${PGADMIN_PORT:-5080}:80"
# networks:
# - default
# restart: unless-stopped

###############################################
# Prometheus-related services
###############################################
# Uncomment the following to enable prometheus and pushgateway

# prometheus:
# image: prom/prometheus:latest
# ports:
# - "9090:9090"
# volumes:
# - ./docker/prometheus.yml:/etc/prometheus/prometheus.yml
# depends_on:
# - "prometheus-pushgateway"
# networks:
# - default
# - shared

# prometheus-pushgateway:
# image: prom/pushgateway
# ports:
# - "9091:9091"
# networks:
# - default
# - shared
- ./webapp:/app
command: sh -c "pnpm install --frozen-lockfile --force && pnpm dev --host 0.0.0.0 --port 5173"
ports:
- "3000:5173"

volumes:
postgres_codecarbon_data:
name: postgres_codecarbon_data1
pgadmin:
name: pgadmin_codecarbon_data1

networks:
default:
driver: bridge
shared: # traefik network
external: true
webapp_node_modules:
4 changes: 4 additions & 0 deletions webapp/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.pnpm-store
dist
.next
6 changes: 3 additions & 3 deletions webapp/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
FROM node:18-alpine AS build
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
RUN npm install -g pnpm@10.8.0 && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM nginx:alpine
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Loading