Skip to content

Commit 299ac98

Browse files
committed
Allow 'key_creator' users to fully manage team keys
1 parent 6e892d0 commit 299ac98

File tree

4 files changed

+30
-20
lines changed

4 files changed

+30
-20
lines changed

app/api/private_ai_keys.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ def _validate_permissions_and_get_ownership_info(
5555
owner_id = current_user.id
5656

5757
# Fail fast without having to do DB lookups
58-
personal_users : list[UserRole] = ["key_creator", "user"] # 'user' is the non-admin system user
59-
if user_role in personal_users:
58+
team_users : list[UserRole] = [UserRole.TEAM_ADMIN, UserRole.KEY_CREATOR]
59+
if user_role == UserRole.DEFAULT:
6060
if owner_id != current_user.id or team_id is not None:
6161
raise HTTPException(
6262
status_code=status.HTTP_403_FORBIDDEN,
6363
detail="Not authorized to perform this action"
6464
)
65-
elif user_role == "admin": # roles always refer to the team role, so admin is a team admin
65+
elif user_role in team_users:
6666
if team_id is not None and team_id != current_user.team_id:
6767
raise HTTPException(
6868
status_code=status.HTTP_403_FORBIDDEN,
@@ -455,7 +455,7 @@ async def list_private_ai_keys(
455455
else:
456456
# Check if user is a team admin
457457
if current_user.team_id is not None:
458-
if current_user.role == "admin":
458+
if current_user.role == UserRole.TEAM_ADMIN:
459459
# Get all users in the team
460460
team_users = db.query(DBUser).filter(DBUser.team_id == current_user.team_id).all()
461461
team_user_ids = [user.id for user in team_users]
@@ -551,22 +551,25 @@ def _get_key_if_allowed(key_id: int, current_user: DBUser, user_role: UserRole,
551551
)
552552

553553
# Check if user has permission to view the key
554+
team_users : list[UserRole] = [UserRole.TEAM_ADMIN, UserRole.KEY_CREATOR]
554555
if current_user.is_admin:
555556
# System admin can view any key
556557
pass
557-
elif user_role == "admin":
558-
# Team admin can only view keys from their team
558+
elif user_role in team_users:
559+
# No cross team access
559560
if private_ai_key.team_id is not None:
560561
# For team-owned keys, check if it belongs to the admin's team
561562
if private_ai_key.team_id != current_user.team_id:
563+
logger.warning(f"User {current_user.id} trying to delete across teams.")
562564
raise HTTPException(
563565
status_code=status.HTTP_404_NOT_FOUND,
564566
detail="Private AI Key not found"
565567
)
566568
else:
567-
# For user-owned keys, check if the owner is in the admin's team
569+
# For user-owned keys, check if the owner is in the viewer's team
568570
owner = db.query(DBUser).filter(DBUser.id == private_ai_key.owner_id).first()
569571
if not owner or owner.team_id != current_user.team_id:
572+
logger.warning(f"Keys owned by different teams")
570573
raise HTTPException(
571574
status_code=status.HTTP_404_NOT_FOUND,
572575
detail="Private AI Key not found"

app/core/resource_limits.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@
33
from app.db.models import DBTeam, DBUser, DBPrivateAIKey, DBTeamProduct, DBProduct
44
from fastapi import HTTPException, status
55
from typing import Optional
6-
from datetime import datetime, UTC
76
import logging
87

98
logger = logging.getLogger(__name__)
109

1110
# Default limits across all customers and products
12-
DEFAULT_USER_COUNT = 100
13-
DEFAULT_KEYS_PER_USER = 5
14-
DEFAULT_TOTAL_KEYS = 500
15-
DEFAULT_SERVICE_KEYS = 100
16-
DEFAULT_VECTOR_DB_COUNT = 100
11+
DEFAULT_USER_COUNT = 1
12+
DEFAULT_KEYS_PER_USER = 1
13+
DEFAULT_TOTAL_KEYS = 6
14+
DEFAULT_SERVICE_KEYS = 5
15+
DEFAULT_VECTOR_DB_COUNT = 5 # Setting to match service keys for drupal module trial
1716
DEFAULT_KEY_DURATION = 30
1817
DEFAULT_MAX_SPEND = 27.0
1918
DEFAULT_RPM_PER_KEY = 500

frontend/src/app/private-ai-keys/page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { useState } from 'react';
44
import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
55
import { Card, CardContent } from '@/components/ui/card';
66
import { useToast } from '@/hooks/use-toast';
7-
import { get, post } from '@/utils/api';
7+
import { get, post, del } from '@/utils/api';
88
import { PrivateAIKeysTable } from '@/components/private-ai-keys-table';
99
import { CreateAIKeyDialog } from '@/components/create-ai-key-dialog';
10-
import { useAuth } from '@/hooks/use-auth';
10+
import { getUserRole, useAuth } from '@/hooks/use-auth';
1111
import { usePrivateAIKeysData } from '@/hooks/use-private-ai-keys-data';
1212

1313

@@ -32,6 +32,7 @@ export default function DashboardPage() {
3232
const queryClient = useQueryClient();
3333
const { user } = useAuth();
3434
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
35+
const mutatingRoles = ["admin", "key_creator", "system_admin"]
3536

3637
// Fetch private AI keys using React Query
3738
const { data: privateAIKeys = [] } = useQuery<PrivateAIKey[]>({
@@ -76,7 +77,7 @@ export default function DashboardPage() {
7677
// Delete key mutation
7778
const deleteKeyMutation = useMutation({
7879
mutationFn: async (keyId: number) => {
79-
const response = await post(`/private-ai-keys/${keyId}/delete`, {});
80+
const response = await del(`/private-ai-keys/${keyId}`, {});
8081
return response.json();
8182
},
8283
onSuccess: () => {
@@ -107,8 +108,8 @@ export default function DashboardPage() {
107108
team_id?: number
108109
}) => {
109110
const endpoint = key_type === 'full' ? '/private-ai-keys' :
110-
key_type === 'llm' ? '/private-ai-keys/token' :
111-
'/private-ai-keys/vector-db';
111+
key_type === 'llm' ? '/private-ai-keys/token' :
112+
'/private-ai-keys/vector-db';
112113
const payload: { region_id: number; name: string; owner_id?: number; team_id?: number } = { region_id, name };
113114
if (owner_id) payload.owner_id = owner_id;
114115
if (team_id) payload.team_id = team_id;
@@ -176,7 +177,7 @@ export default function DashboardPage() {
176177
onDelete={(keyId) => deleteKeyMutation.mutate(keyId)}
177178
isLoading={createKeyMutation.isPending}
178179
showOwner={true}
179-
allowModification={false}
180+
allowModification={mutatingRoles.includes(getUserRole(user))}
180181
onUpdateBudget={(keyId, budgetDuration) => updateBudgetMutation.mutate({ keyId, budgetDuration })}
181182
isDeleting={deleteKeyMutation.isPending}
182183
isUpdatingBudget={updateBudgetMutation.isPending}

frontend/src/hooks/use-auth.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,11 @@ export const useAuth = create<AuthState>((set) => ({
2525
export const isTeamAdmin = (user: User | null): boolean => {
2626
if (!user) return false;
2727
return !user.is_admin && user.team_id !== null && user.role === 'admin';
28-
};
28+
};
29+
30+
export const getUserRole = (user: User | null): string => {
31+
if (!user) return "none";
32+
if (user.is_admin) return "system_admin";
33+
if (!user.role) return "user";
34+
return user.role;
35+
}

0 commit comments

Comments
 (0)