Skip to content

Add ownership to templates and ranges + Implement auth endpoints #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 71 commits into from
Mar 8, 2025
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
6ed3511
fix typo. RRFC -> RFC
Adamkadaban Feb 23, 2025
a889938
add user secret and model
Adamkadaban Feb 23, 2025
8a8a48d
add secret model and fix up naming in user model
Adamkadaban Feb 23, 2025
528e054
add secret schema
Adamkadaban Feb 23, 2025
a8111ce
add crud for users
Adamkadaban Feb 23, 2025
0a38c42
add auth endpoints
Adamkadaban Feb 25, 2025
8c0ad3d
add template endpoints
Adamkadaban Feb 25, 2025
8f6e17b
add range endpoint
Adamkadaban Feb 25, 2025
5231761
add bcrypt and jwt requirements for auth
Adamkadaban Feb 25, 2025
354e034
add auth route to router
Adamkadaban Feb 25, 2025
e16035f
make error messages for template routes more consistent with other ra…
Adamkadaban Feb 25, 2025
e437d51
add core auth logic that will be used for all authenticated endpoints
Adamkadaban Feb 25, 2025
2d98188
add config for auth
Adamkadaban Feb 25, 2025
34f953b
popoulate and check owner for templates crud
Adamkadaban Feb 26, 2025
803cf32
add crud for users
Adamkadaban Feb 26, 2025
c8837f9
fix typo in user schema
Adamkadaban Feb 26, 2025
3d59cef
add owner to template base model
Adamkadaban Feb 26, 2025
7cda2f0
add user model with correct naming
Adamkadaban Feb 26, 2025
3bb2bab
fix prefix for auth route
Adamkadaban Feb 26, 2025
0cb7d64
change auth login error messages to not report if user exists or not …
Adamkadaban Feb 26, 2025
64376f8
add basic tests for auth endpoints
Adamkadaban Feb 26, 2025
edf778d
dumb solution to get all clients authorized
Adamkadaban Feb 26, 2025
15f1495
change usernames for registering between auth and template tests so t…
Adamkadaban Feb 26, 2025
836aa4d
add test to make sure users can only access their own templates
Adamkadaban Feb 26, 2025
8bbe67e
appease mypy
Adamkadaban Feb 26, 2025
52dc81b
make sure timezones are in UTC in the database
Adamkadaban Feb 26, 2025
7a28a3c
appease ruff
Adamkadaban Feb 26, 2025
5aed21a
format with black
Adamkadaban Feb 26, 2025
5402f40
add default jwt secret so tests can run in pipeline
Adamkadaban Feb 26, 2025
275646d
Merge branch 'main' into adam-auth-2
Adamkadaban Feb 27, 2025
793f8a8
update delete routes and tests to be authenticated
Adamkadaban Feb 27, 2025
b8ca080
annotate current_user arg for delete templates
Adamkadaban Feb 27, 2025
224fe47
black and ruff changes
Adamkadaban Feb 27, 2025
0fea90c
remove erroneous paren and comma from deploy endpoint that was breaki…
Adamkadaban Feb 27, 2025
4b80c0b
modify dev dockerfile to have dependencies for deploying ranges with …
Adamkadaban Feb 27, 2025
33748ea
auth by setting cookie instead of with jwt
Adamkadaban Mar 3, 2025
e849a9a
replace OpenLabsX with OpenLabs
Adamkadaban Mar 3, 2025
6979d20
user can read/deploy anything and is created on db start
Adamkadaban Mar 4, 2025
3405c1a
ruff/black plus cleaning up admin creation. DRY
Adamkadaban Mar 4, 2025
89b2df8
user schema validation fix
Adamkadaban Mar 4, 2025
4e2ab09
add endpoints for retrieving user info and secrets status. also for p…
Adamkadaban Mar 5, 2025
5f9437e
do not include inhereted token param in fastapi docs
Adamkadaban Mar 5, 2025
ea0c5b0
fix password change endpoint by adding missing await
Adamkadaban Mar 5, 2025
f7a18f6
fix endpoint for retrieving user secret status. add missing depends
Adamkadaban Mar 5, 2025
073f149
fix secret retrieval for secret post endpoints by explicitly getting …
Adamkadaban Mar 5, 2025
16e8573
add tests for users endpoint
Adamkadaban Mar 5, 2025
b148cfe
improve variable name for unused variable in user schema email valida…
Adamkadaban Mar 5, 2025
f613bd7
add test for invalid email
Adamkadaban Mar 5, 2025
b9d3316
fix test formatting with black
Adamkadaban Mar 5, 2025
7ac7aea
update file structure in readme
Adamkadaban Mar 5, 2025
6ae5fd9
fix directory structure spacing in readme
Adamkadaban Mar 5, 2025
6fe47c3
add admin field to user get endpoint. also add better fields to schema
Adamkadaban Mar 5, 2025
9ad931b
add other fields for azure. (tenant id, subscription id)
Adamkadaban Mar 5, 2025
af3e186
ruff/black reformatting
Adamkadaban Mar 5, 2025
fa567e3
add missing azure fields to tests
Adamkadaban Mar 5, 2025
218c58c
set up e2e for secrets and allow aws ranges to deploy from them
Adamkadaban Mar 6, 2025
b2eb480
ruff/mypy/black for auth changes
Adamkadaban Mar 6, 2025
e94afe3
fix links in changelog i accidentally broke with a sed
Adamkadaban Mar 6, 2025
0d1fa9b
update file structure in readme
Adamkadaban Mar 6, 2025
6ab1c73
fix typing issue with crypto keys for mypy
Adamkadaban Mar 6, 2025
401c206
remove unused function
Adamkadaban Mar 7, 2025
3b029a4
make password minimum 8 bytes in schema
Adamkadaban Mar 7, 2025
5db4d6f
add schemas for user and auth endpoints to make example values on fas…
Adamkadaban Mar 7, 2025
4620d53
fix github link in config to new org name
Adamkadaban Mar 7, 2025
c18575a
make minimum email length 3
Adamkadaban Mar 8, 2025
3d9640f
add fields to secret schemas for better fastapi docs
Adamkadaban Mar 8, 2025
742cfa6
use async_get_db instead of local_session for admin user init
Adamkadaban Mar 8, 2025
ab81d59
move api test structures to config
Adamkadaban Mar 8, 2025
da98a01
split up secrets tests
Adamkadaban Mar 8, 2025
4d0d257
make secret function in crud users return schema instead of dict
Adamkadaban Mar 8, 2025
4241bc8
Added logging messages
alexchristy Mar 8, 2025
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
27 changes: 25 additions & 2 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,41 @@ FROM python:3.12-slim

WORKDIR /code

# For dynamic versioning
RUN apt-get update && apt-get install -y git \
RUN apt-get update && apt-get install -y git curl \
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs \
&& apt-get install -y gnupg software-properties-common \
&& apt-get install -y wget \
&& apt-get install -y gnupg2 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

RUN echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com bookworm main" | \
tee /etc/apt/sources.list.d/hashicorp.list

RUN apt-get update && apt-get install -y terraform


# Install debugpy for debugging
RUN pip install --no-cache-dir debugpy

# Install python dependencies
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

# Set up terraform cache
WORKDIR src/app/core/cdktf
RUN mkdir -p "/root/.terraform.d/plugin-cache"
COPY src/app/core/cdktf/.terraformrc /root/.terraformrc
RUN terraform init
RUN rm -rf .terraform*
WORKDIR /code

EXPOSE 80

CMD ["uvicorn", "src.app.main:app", "--host", "0.0.0.0", "--port", "80", "--reload"]
6 changes: 5 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ setuptools-scm~=8.1

# CDKTF
cdktf>=0.20
cdktf-cdktf-provider-aws>=19.52
cdktf-cdktf-provider-aws>=19.52

# Auth
pyjwt
bcrypt
2 changes: 2 additions & 0 deletions src/app/api/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from fastapi import APIRouter

from .auth import router as auth_router
from .health import router as health_router
from .ranges import router as ranges_router
from .templates import router as templates_router
Expand All @@ -10,3 +11,4 @@
router.include_router(health_router)
router.include_router(templates_router)
router.include_router(ranges_router)
router.include_router(auth_router)
99 changes: 99 additions & 0 deletions src/app/api/v1/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from datetime import UTC, datetime, timedelta
from typing import Any

import jwt
from bcrypt import checkpw
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio.session import AsyncSession

from ...core.config import settings
from ...core.db.database import async_get_db
from ...crud.crud_users import create_user, get_user
from ...schemas.user_schema import (
UserBaseSchema,
UserCreateBaseSchema,
UserID,
)

router = APIRouter(prefix="/auth", tags=["auth"])


@router.post("/login")
async def login(
openlabs_user: UserBaseSchema,
db: AsyncSession = Depends(async_get_db), # noqa: B008
) -> dict[str, str]:
"""Login a user.

Args:
----
openlabs_user (UserBaseSchema): User authentication data.
db (AsyncSession): Async database connection.

Returns:
-------
dict: token with JWT for the user.

"""
user = await get_user(db, openlabs_user.email)

if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials or user does not exist",
)

user_hash = user.hashed_password
user_id = user.id

if not checkpw(openlabs_user.password.encode(), user_hash.encode()):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials or user does not exist",
)

data_dict: dict[str, Any] = {"user": str(user_id)}

expire = datetime.now(UTC) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)

data_dict.update({"exp": expire})

return {
"token": jwt.encode(
data_dict, settings.SECRET_KEY, algorithm=settings.ALGORITHM
)
}


@router.post("/register")
async def register_new_user(
openlabs_user: UserCreateBaseSchema,
db: AsyncSession = Depends(async_get_db), # noqa: B008
) -> UserID:
"""Create a new user.

Args:
----
openlabs_user (UserCreateBaseSchema): User creation data.
db (AsyncSession): Async database connection.

Returns:
-------
UserID: Identity of the created user.

"""
existing_user = await get_user(db, openlabs_user.email)
if existing_user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="User already exists",
)
created_user = await create_user(db, openlabs_user)

if not created_user:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Unable to create user",
)

return UserID.model_validate(created_user, from_attributes=True)
36 changes: 33 additions & 3 deletions src/app/api/v1/ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio.session import AsyncSession

from ...core.auth.auth import get_current_user
from ...core.config import settings
from ...core.db.database import async_get_db
from ...crud.crud_range_templates import get_range_template
from ...crud.crud_range_templates import get_range_template, is_range_template_owner
from ...models.user_model import UserModel
from ...schemas.template_range_schema import TemplateRangeID, TemplateRangeSchema

router = APIRouter(prefix="/ranges", tags=["ranges"])
Expand All @@ -16,14 +18,42 @@
async def deploy_range_from_template(
range_ids: list[TemplateRangeID],
db: AsyncSession = Depends(async_get_db), # noqa: B008
current_user: UserModel = Depends(get_current_user), # noqa: B008
) -> dict[str, Any]:
"""Deploy range templates."""
"""Deploy range templates.

Args:
----
range_ids (list[TemplateRangeID]): List of range template IDs to deploy.
db (AsyncSession): Async database connection.
current_user (UserModel): Currently authenticated user.

Returns:
-------
dict[str, Any]: Deployment status.

"""
# Import CDKTF dependencies to avoid long import times
from ...core.cdktf.aws.aws import create_aws_stack, deploy_infrastructure

ranges: list[TemplateRangeSchema] = []
for range_id in range_ids:
range_model = await get_range_template(db, range_id)
# Check if the user owns this template
is_owner = await is_range_template_owner(db, range_id, current_user.id)
if not is_owner:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"You don't have permission to deploy range with ID: {range_id.id}",
)

# Get the template
range_model = await get_range_template(db, range_id, user_id=current_user.id)
if not range_model:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Range template with ID: {range_id.id} not found or you don't have access to it!",
)

ranges.append(
TemplateRangeSchema.model_validate(range_model, from_attributes=True)
)
Expand Down
Loading
Loading