Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
74d5cfc
config routes updated
vprashrex Mar 27, 2026
bd48eb8
code update
vprashrex Mar 27, 2026
10fd7c3
made query optional
vprashrex Mar 27, 2026
f73c22a
code updated
vprashrex Mar 27, 2026
0c7175b
added has_more functionality
vprashrex Mar 27, 2026
a363f45
refactor: update config CRUD methods to include query parameter and h…
vprashrex Mar 30, 2026
abd1fc8
Add projects-by-org endpoint and pagination for organizations list
vprashrex Mar 30, 2026
84ce2e8
Enhance organization validation: return 503 status code for inactive …
vprashrex Mar 31, 2026
6144d98
feat(*): google integration flow
Ayush8923 Mar 31, 2026
7f98414
Merge branch 'main' into feat/adding-query-params-to-config
Ayush8923 Mar 31, 2026
a7e9139
Merge branch 'feat/adding-query-params-to-config' of https://github.c…
Ayush8923 Mar 31, 2026
4cf576b
fix(*): update the js comment
Ayush8923 Mar 31, 2026
0b3b30e
fix(*): update the uv.lock
Ayush8923 Mar 31, 2026
7e46f7d
feat(*): API for create user corresponding to projects
Ayush8923 Apr 1, 2026
a6b6281
fix(*): few updates on user projects
Ayush8923 Apr 1, 2026
6731b1d
Merge branch 'main' of https://github.com/ProjectTech4DevAI/kaapi-bac…
Ayush8923 Apr 1, 2026
8b3e5e9
Merge branch 'feat/google-integration-auth-flow' of https://github.co…
Ayush8923 Apr 1, 2026
6bb875c
fix(*): update the test cases
Ayush8923 Apr 1, 2026
b1082a5
Merge branch 'feat/google-integration-auth-flow' of https://github.co…
Ayush8923 Apr 1, 2026
3a1c9c8
fix(*): update test coverage
Ayush8923 Apr 1, 2026
9e833d8
Merge branch 'feat/google-integration-auth-flow' of https://github.co…
Ayush8923 Apr 1, 2026
b22588f
fix(*): update the test cases
Ayush8923 Apr 1, 2026
a57f829
Merge branch 'feat/google-integration-auth-flow' of https://github.co…
Ayush8923 Apr 1, 2026
d1c9416
Merge branch 'main' into feat/google-integration-auth-flow
Ayush8923 Apr 4, 2026
b211652
Merge branch 'feat/google-integration-auth-flow' into feat/add-user-p…
Ayush8923 Apr 4, 2026
fd377e8
fix(*): some of the edge cases implementation
Ayush8923 Apr 4, 2026
781fccc
Merge branch 'feat/add-user-project' of https://github.com/ProjectTec…
Ayush8923 Apr 4, 2026
7133fa1
fix(*): remove the unused vairbales
Ayush8923 Apr 4, 2026
5b8af37
fix(*): for the response used the APIResponses utils function
Ayush8923 Apr 4, 2026
833bda2
Merge branch 'feat/google-integration-auth-flow' into feat/add-user-p…
Ayush8923 Apr 4, 2026
b3eb1fd
fix(*): update the test cases
Ayush8923 Apr 4, 2026
ccb11cc
Merge branch 'feat/google-integration-auth-flow' into feat/add-user-p…
Ayush8923 Apr 4, 2026
8166675
Merge branch 'main' into feat/google-integration-auth-flow
Ayush8923 Apr 7, 2026
a81d18e
Merge branch 'feat/google-integration-auth-flow' into feat/add-user-p…
Ayush8923 Apr 7, 2026
280b254
sugg(*): made the changes as per the suggestion
Ayush8923 Apr 8, 2026
4e391f8
cleanups(*): stt and tts flow
Ayush8923 Apr 8, 2026
e62ed10
fix(*): update the invitation user email html file
Ayush8923 Apr 8, 2026
8aa31a0
fix(*): update the invite verify md file
Ayush8923 Apr 8, 2026
20fa939
Revert "fix(*): update the invite verify md file"
Ayush8923 Apr 8, 2026
eba128a
fix(*): update the invite verify md file
Ayush8923 Apr 8, 2026
944a67c
fix(*): added the env example
Ayush8923 Apr 9, 2026
74d252b
Add/Delete User in Org/Project (#737)
Ayush8923 Apr 9, 2026
ac700af
Merge branch 'main' into feat/google-integration-auth-flow
Ayush8923 Apr 9, 2026
71720a3
fix(*): update the test cases
Ayush8923 Apr 9, 2026
d9c00c2
fix(*): added the test cases for user project
Ayush8923 Apr 9, 2026
6970446
fix(*): update the invite user template
Ayush8923 Apr 9, 2026
8810300
Merge branch 'feat/google-integration-auth-flow' of https://github.co…
Ayush8923 Apr 9, 2026
254fadc
Merge branch 'main' of https://github.com/ProjectTech4DevAI/kaapi-bac…
Ayush8923 Apr 10, 2026
3fec131
fix(*): added the test cases
Ayush8923 Apr 10, 2026
e3db12c
fix(*): test cases updates
Ayush8923 Apr 10, 2026
a3c946c
Merge branch 'main' into feat/invitation-flow
Ayush8923 Apr 14, 2026
de692f4
Merge branch 'main' into feat/invitation-flow
Ayush8923 Apr 15, 2026
8640abe
Merge branch 'main' into feat/invitation-flow
AkhileshNegi Apr 15, 2026
8daa10e
fix(*): made the changes as per the suggestion
Ayush8923 Apr 15, 2026
d3884cb
Merge branch 'main' into feat/invitation-flow
Ayush8923 Apr 15, 2026
7c3fbf7
Merge branch 'main' into feat/invitation-flow
Ayush8923 Apr 17, 2026
361dd27
Auth: Email Login Flow (#747)
Ayush8923 Apr 17, 2026
889faa4
fix(*): updated the test cases
Ayush8923 Apr 17, 2026
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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,12 @@ OPENAI_API_KEY=""

KAAPI_GUARDRAILS_AUTH=""
KAAPI_GUARDRAILS_URL=""

SMTP_HOST=
SMTP_PORT=
SMTP_TLS=True
SMTP_USER=
SMTP_PASSWORD=
EMAILS_FROM_EMAIL=
EMAILS_FROM_NAME=Kaapi
FRONTEND_HOST=
20 changes: 20 additions & 0 deletions backend/app/api/docs/auth/invite_verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Verify Invitation

Verify an invitation token from a magic link email and log the user in.

## Query Parameters

- **token** (required): The invitation JWT token from the email link.

## Behavior

1. Validates the invitation token (checks signature, expiry, and type).
2. Looks up the user by the email embedded in the token.
3. If the user exists and is inactive (first login), activates the account.
4. Returns a JWT access token with the org/project from the invitation embedded.
5. Sets `access_token` and `refresh_token` as HTTP-only cookies.

## Error Responses

- **400**: Invalid or expired invitation link.
- **404**: User account not found.
18 changes: 18 additions & 0 deletions backend/app/api/docs/auth/magic_link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Request Magic Link Login

Send a magic link login email to the user's email address.

## Request Body

- **email** (required): The user's email address.

## Behavior

1. Checks if the user exists — returns 404 if not.
2. Generates a short-lived login token (15 minutes).
3. Sends an email with a "Sign In Now" button linking to the frontend.

## Error Responses

- **404**: No account found for this email.
- **500**: Email service is not configured or failed to send.
20 changes: 20 additions & 0 deletions backend/app/api/docs/auth/magic_link_verify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Verify Magic Link

Verify a magic link login token and log the user in.

## Query Parameters

- **token** (required): The login JWT token from the email link.

## Behavior

1. Validates the magic link token (checks signature, expiry, and type).
2. Looks up the user by the email embedded in the token.
3. Verifies the user is active.
4. If the user has exactly one project, it is auto-selected and embedded in the JWT.
5. Returns a JWT access token and sets HTTP-only cookies.

## Error Responses

- **400**: Invalid or expired login link.
- **404**: User account not found.
7 changes: 7 additions & 0 deletions backend/app/api/docs/credentials/delete_all_by_org_project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Delete all credentials for a specific organization and project.

Permanently removes all provider credentials associated with the specified organization and project IDs. Requires superuser access.

### Path Parameters:
- **org_id**: Organization ID
- **project_id**: Project ID
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Delete credentials for a specific provider within an organization and project.

Permanently removes credentials for a specific provider from the specified organization and project. Requires superuser access.

### Path Parameters:
- **org_id**: Organization ID
- **project_id**: Project ID
- **provider**: Provider name (e.g., `openai`, `langfuse`, `google`, `sarvamai`, `elevenlabs`)
2 changes: 1 addition & 1 deletion backend/app/api/docs/credentials/get_provider.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Get credentials for a specific provider.

Retrieves decrypted credentials for a specific provider (e.g., `openai`, `langfuse`) for the current organization and project.
Retrieves credentials for a specific provider (e.g., `openai`, `langfuse`) for the current organization and project. Sensitive fields (e.g., `api_key`, `secret_key`) are masked in the response. If credentials for the provider are not configured, `null` is returned.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Get credentials for a specific provider within an organization and project.

Retrieves credentials for a specific provider (e.g., `openai`, `langfuse`) for the specified organization and project. Sensitive fields (e.g., `api_key`, `secret_key`) are masked in the response. If credentials for the provider are not configured, `null` is returned. Requires superuser access.

### Path Parameters:
- **org_id**: Organization ID
- **project_id**: Project ID
- **provider**: Provider name (e.g., `openai`, `langfuse`, `google`, `sarvamai`, `elevenlabs`)
2 changes: 1 addition & 1 deletion backend/app/api/docs/credentials/list.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Get all credentials for current organization and project.

Returns list of all provider credentials associated with your organization and project.
Returns a list of all provider credentials associated with your organization and project. Sensitive fields (e.g., `api_key`, `secret_key`) are masked in the response. If no credentials are configured, an empty list is returned.
12 changes: 12 additions & 0 deletions backend/app/api/docs/credentials/list_by_org_project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Get all credentials for a specific organization and project.

Retrieves all provider credentials associated with the specified organization and project IDs. Sensitive fields (e.g., `api_key`, `secret_key`) are masked in the response. If no credentials are configured, an empty list is returned. Requires superuser access.

### Path Parameters:
- **org_id**: Organization ID
- **project_id**: Project ID

### Supported Providers:
- **LLM:** openai, sarvamai, google(gemini)
- **Observability:** langfuse
- **Audio:** elevenlabs
33 changes: 32 additions & 1 deletion backend/app/api/docs/credentials/update.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
Update credentials for a specific provider.

Updates existing provider credentials for the current organization and project. Provider and credential fields must be provided.
Updates existing provider credentials for the current organization and project. If the credentials for the specified provider don't exist yet, they will be **created** automatically (upsert behavior). The `provider` and `credential` fields are required.

The `credential` field accepts **two formats** (both work the same):

### Nested format (same as create endpoint):
```json
{
"provider": "openai",
"is_active": true,
"credential": {
"openai": {
"api_key": "sk-proj-..."
}
}
}
```

### Flat format:
```json
{
"provider": "openai",
"is_active": true,
"credential": {
"api_key": "sk-proj-..."
}
}
```

### Supported Providers:
- **LLM:** openai, sarvamai, google(gemini)
- **Observability:** langfuse
- **Audio:** elevenlabs
38 changes: 38 additions & 0 deletions backend/app/api/docs/credentials/update_by_org_project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Update credentials for a specific provider within an organization and project.

Updates existing provider credentials for the specified organization and project. If the credentials for the specified provider don't exist yet, they will be **created** automatically (upsert behavior). Requires superuser access.

### Path Parameters:
- **org_id**: Organization ID
- **project_id**: Project ID

The `credential` field accepts **two formats** (both work the same):

### Nested format (same as create endpoint):
```json
{
"provider": "openai",
"is_active": true,
"credential": {
"openai": {
"api_key": "sk-proj-..."
}
}
}
```

### Flat format:
```json
{
"provider": "openai",
"is_active": true,
"credential": {
"api_key": "sk-proj-..."
}
}
```

### Supported Providers:
- **LLM:** openai, sarvamai, google(gemini)
- **Observability:** langfuse
- **Audio:** elevenlabs
170 changes: 169 additions & 1 deletion backend/app/api/routes/auth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from typing import Any

from fastapi import APIRouter, HTTPException, Request, status
from fastapi.responses import JSONResponse
Expand All @@ -9,9 +10,12 @@
from app.core.config import settings
from app.crud import get_user_by_email
from app.crud.auth import get_user_accessible_projects
from app.crud.organization import validate_organization
from app.crud.project import validate_project
from app.models import (
GoogleAuthRequest,
GoogleAuthResponse,
MagicLinkRequest,
Message,
SelectProjectRequest,
Token,
Expand All @@ -20,9 +24,17 @@
build_google_auth_response,
build_token_response,
clear_auth_cookies,
generate_magic_link_token,
validate_refresh_token,
verify_invite_token,
verify_magic_link_token,
)
from app.utils import (
APIResponse,
generate_magic_link_email,
load_description,
send_email,
)
from app.utils import APIResponse, load_description

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -198,3 +210,159 @@ def logout() -> JSONResponse:
response = JSONResponse(content=api_response.model_dump())
clear_auth_cookies(response)
return response


@router.get(
"/invite/verify",
description=load_description("auth/invite_verify.md"),
response_model=APIResponse[Token],
)
def verify_invitation(session: SessionDep, token: str) -> JSONResponse:
"""Verify an invitation token, activate the user, and log them in."""

invite_payload = verify_invite_token(token)
if not invite_payload:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid or expired invitation link",
)

# Verify the org/project referenced by the invite still exist and are active.
# Raises 404 from validate_* if missing.
validate_organization(session=session, org_id=invite_payload.organization_id)
validate_project(session=session, project_id=invite_payload.project_id)

user = get_user_by_email(session=session, email=invite_payload.email)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User account not found. Please contact support.",
)

# Activate user if not already active
if not user.is_active:
user.is_active = True
session.add(user)
session.commit()
session.refresh(user)
logger.info(
f"[verify_invitation] User activated via invite | user_id: {user.id}"
)

response = build_token_response(
user_id=user.id,
organization_id=invite_payload.organization_id,
project_id=invite_payload.project_id,
)

logger.info(
f"[verify_invitation] Invitation verified | user_id: {user.id}, project_id: {invite_payload.project_id}"
)
return response


@router.post(
"/magic-link",
description=load_description("auth/magic_link.md"),
response_model=APIResponse[Message],
)
def request_magic_link(session: SessionDep, body: MagicLinkRequest) -> Any:
"""Send a magic link login email to the user."""

user = get_user_by_email(session=session, email=body.email)
if not user:
logger.info(
f"[request_magic_link] Magic link requested for non-existent email: {body.email}"
)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="No account found for this email.",
)

token = generate_magic_link_token(email=body.email)

if settings.emails_enabled:
try:
email_data = generate_magic_link_email(
email_to=body.email,
magic_link_token=token,
)
send_email(
email_to=body.email,
subject=email_data.subject,
html_content=email_data.html_content,
)
logger.info(
f"[request_magic_link] Magic link email sent | email: {body.email}"
)
except Exception as e:
logger.error(
f"[request_magic_link] Failed to send magic link email | email: {body.email}, error: {e}"
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to send login email. Please try again later.",
)
else:
logger.warning("[request_magic_link] Email sending is not configured")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Email service is not configured",
)

return APIResponse.success_response(
data=Message(message="If an account exists, a login link has been sent.")
)


@router.get(
"/magic-link/verify",
description=load_description("auth/magic_link_verify.md"),
response_model=APIResponse[Token],
)
def verify_magic_link(session: SessionDep, token: str) -> JSONResponse:
"""Verify a magic link token and log the user in."""

email = verify_magic_link_token(token)
if not email:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid or expired login link. Please request a new one.",
)

user = get_user_by_email(session=session, email=email)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User account not found",
)

# Activate user if not already active
if not user.is_active:
user.is_active = True
session.add(user)
session.commit()
session.refresh(user)
logger.info(
f"[verify_magic_link] User activated via magic link | user_id: {user.id}"
)

# Get user's projects to embed in token
available_projects = get_user_accessible_projects(session=session, user_id=user.id)

organization_id = None
project_id = None
if len(available_projects) == 1:
organization_id = available_projects[0]["organization_id"]
project_id = available_projects[0]["project_id"]

response = build_token_response(
user_id=user.id,
organization_id=organization_id,
project_id=project_id,
)

logger.info(
f"[verify_magic_link] User logged in via magic link | user_id: {user.id}"
)
return response
Comment thread
Ayush8923 marked this conversation as resolved.
Loading
Loading