Skip to content
Merged
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
29 changes: 29 additions & 0 deletions wavefront/client/src/api/user-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,33 @@ export class UserService {
},
});
}

async listUsers(): Promise<IApiResponse<{ users: IUser[] }>> {
return this.http.get('/v1/users');
}

async createUser(data: {
email: string;
password: string;
first_name: string;
last_name: string;
}): Promise<IApiResponse<{ user: IUser }>> {
return this.http.post('/v1/users', data);
}

async updateUser(
userId: string,
data: {
email?: string;
password?: string;
first_name?: string;
last_name?: string;
}
): Promise<IApiResponse<{ user: IUser }>> {
return this.http.patch(`/v1/users/${userId}`, data);
}

async deleteUser(userId: string): Promise<IApiResponse<{ message: string }>> {
return this.http.delete(`/v1/users/${userId}`);
}
Comment on lines +27 to +54
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find wavefront/client -type f -name "*.ts" -o -name "*.js" | grep -E "(http|client|axios|api)" | head -20

Repository: rootflo/wavefront

Length of output: 965


🏁 Script executed:

git ls-files wavefront/client/src/api/ | head -20

Repository: rootflo/wavefront

Length of output: 996


🏁 Script executed:

cat -n wavefront/client/src/api/user-service.ts | head -30

Repository: rootflo/wavefront

Length of output: 1047


🏁 Script executed:

find wavefront/client -type f -name "axios*" -o -name "*http*" | grep -E "\.(ts|js)$" | head -20

Repository: rootflo/wavefront

Length of output: 94


🏁 Script executed:

find wavefront/client/src -type d -name "lib"

Repository: rootflo/wavefront

Length of output: 85


🏁 Script executed:

cat -n wavefront/client/src/lib/axios.ts 2>/dev/null || find wavefront/client -name "axios.ts" -exec cat -n {} \;

Repository: rootflo/wavefront

Length of output: 2331


🏁 Script executed:

cat -n wavefront/client/src/config/env.ts 2>/dev/null || find wavefront/client -name "env.ts" -exec cat -n {} \;

Repository: rootflo/wavefront

Length of output: 709


🏁 Script executed:

rg "VITE_BASE_URL|baseURL" wavefront/client --type ts --type js -A 2 -B 2 | head -50

Repository: rootflo/wavefront

Length of output: 1297


🏁 Script executed:

find wavefront/client -name "*.env*" -o -name ".env" | head -10

Repository: rootflo/wavefront

Length of output: 89


🏁 Script executed:

cat -n wavefront/client/src/main.ts 2>/dev/null || find wavefront/client/src -name "main.ts" -exec cat -n {} \;

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

cat -n wavefront/client/.env.sample

Repository: rootflo/wavefront

Length of output: 282


🏁 Script executed:

find wavefront/client -name "*.env*" -type f -exec cat -n {} \;

Repository: rootflo/wavefront

Length of output: 282


🏁 Script executed:

find wavefront/client -name "*prod*" -o -name "*production*" | grep -E "\.(env|config|ts|js)$"

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

cat -n wavefront/client/vite.config.ts 2>/dev/null || find wavefront/client -name "vite.config.*" -exec cat -n {} \;

Repository: rootflo/wavefront

Length of output: 549


🏁 Script executed:

find wavefront -name "docker*" -o -name "*compose*" | head -10

Repository: rootflo/wavefront

Length of output: 120


🏁 Script executed:

cat -n wavefront/server/docker-compose.yml | head -100

Repository: rootflo/wavefront

Length of output: 933


🏁 Script executed:

find wavefront/server/docker -type f -name "*.env*" -o -name "*.sh" | head -10

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

rg "https://" wavefront/client --type ts --type js | head -20

Repository: rootflo/wavefront

Length of output: 2192


🏁 Script executed:

rg "protocol|https|http" wavefront/client/src/lib/axios.ts

Repository: rootflo/wavefront

Length of output: 43


🏁 Script executed:

find wavefront -name "README*" -o -name "DEPLOYMENT*" | xargs grep -l "HTTPS\|https" 2>/dev/null | head -5

Repository: rootflo/wavefront

Length of output: 156


🏁 Script executed:

grep -i "https\|security\|password" wavefront/README.md | head -20

Repository: rootflo/wavefront

Length of output: 500


🏁 Script executed:

cat -n wavefront/client/public/config.js

Repository: rootflo/wavefront

Length of output: 200


HTTPS is environment-configured but not code-enforced; consider adding runtime validation in production.

The axios instance relies on environment configuration for HTTPS enforcement rather than explicit code-level validation. Local development uses http://localhost:8002 while staging uses https://console.staging.rootflo.ai, but there is no runtime check preventing misconfiguration. Since passwords are transmitted in request bodies for the createUser and updateUser methods, consider adding a guard to validate that the baseURL uses HTTPS in production environments, or document this as an operational requirement.

🤖 Prompt for AI Agents
In @wavefront/client/src/api/user-service.ts around lines 27-54, Add a runtime
guard in user-service.ts to ensure the HTTP client uses HTTPS in production:
detect the runtime environment (e.g., NODE_ENV === "production") and validate
the axios/baseURL used by this.http before sending sensitive requests from
createUser and updateUser; if the baseURL is not HTTPS, throw or reject with a
clear error (or fail-fast during service initialization) so passwords are never
sent over plain HTTP in production. Ensure the check references the existing
this.http or its configured baseURL and runs at service construction or at the
start of createUser/updateUser to prevent accidental insecure transmissions.

}
9 changes: 8 additions & 1 deletion wavefront/client/src/components/topbar/Topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IUser } from '@app/pages/types';
import { useAuthStore } from '@app/store';
import { useDashboardStore } from '@app/store/dashboard-store';
import { App } from '@app/types/app';
import { UserIcon } from 'lucide-react';
import { UserIcon, Settings } from 'lucide-react';
import { useEffect } from 'react';
import { useNavigate } from 'react-router';

Expand Down Expand Up @@ -75,6 +75,13 @@ const Topbar = ({ user, apps = [] }: { user: IUser; apps: App[] }) => {
</SelectContent>
</Select>
)}{' '}
<button
onClick={() => navigate('/apps/users')}
className="flex h-10 w-10 cursor-pointer items-center justify-center rounded-full border border-gray-300 hover:bg-gray-50"
title="User Management"
>
<Settings className="h-5 w-5 text-gray-700" />
</button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="border-heading flex w-[180px] cursor-pointer items-center gap-1 rounded-full border p-1.5 capitalize">
Expand Down
6 changes: 6 additions & 0 deletions wavefront/client/src/hooks/data/fetch-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
getWorkflowPipelinesQueryFn,
getWorkflowRunsQueryFn,
getWorkflowsQueryFn,
getUsersQueryFn,
readYamlQueryFn,
} from './query-functions';
import {
Expand Down Expand Up @@ -101,6 +102,7 @@ import {
getWorkflowPipelinesKey,
getWorkflowRunsKey,
getWorkflowsKey,
getUsersKey,
readYamlKey,
} from './query-keys';

Expand Down Expand Up @@ -408,3 +410,7 @@ export const useGetPipelineFiles = (
export const useGetAppById = (appId: string, enabled: boolean = true): UseQueryResult<App | undefined, Error> => {
return useQueryInit<App | undefined>(getAppByIdKey(appId), () => getAppByIdFn(appId), enabled);
};

export const useGetUsers = (): UseQueryResult<IUser[], Error> => {
return useQueryInit(getUsersKey(), getUsersQueryFn, true);
};
40 changes: 40 additions & 0 deletions wavefront/client/src/hooks/data/mutation-functions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import floConsoleService from '@app/api';
import { IUser } from '@app/types/user';

/**
* Agent mutation functions
Expand Down Expand Up @@ -31,3 +32,42 @@ export const updateAppFn = async (data: {
});
return response.data;
};

/**
* User mutation functions
*/
export const createUserMutationFn = async (data: {
email: string;
password: string;
first_name: string;
last_name: string;
}): Promise<IUser> => {
const response = await floConsoleService.userService.createUser(data);
if (!response.data.data) {
throw new Error('Failed to create user');
}
return response.data.data.user;
};

export const updateUserMutationFn = async ({
userId,
data,
}: {
userId: string;
data: {
email?: string;
password?: string;
first_name?: string;
last_name?: string;
};
}): Promise<IUser> => {
const response = await floConsoleService.userService.updateUser(userId, data);
if (!response.data.data) {
throw new Error('Failed to update user');
}
return response.data.data.user;
};

export const deleteUserMutationFn = async (userId: string): Promise<void> => {
await floConsoleService.userService.deleteUser(userId);
};
72 changes: 70 additions & 2 deletions wavefront/client/src/hooks/data/mutation-hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { QueryClient, useMutation, useQueryClient } from '@tanstack/react-query';
import { getAgentKey, getAgentsKey, getAppByIdKey } from './query-keys';
import { deleteAgentMutationFn, updateAgentMutationFn, updateAppFn } from './mutation-functions';
import { getAgentKey, getAgentsKey, getAppByIdKey, getUserKey, getUsersKey } from './query-keys';
import {
createUserMutationFn,
deleteAgentMutationFn,
deleteUserMutationFn,
updateAgentMutationFn,
updateAppFn,
updateUserMutationFn,
} from './mutation-functions';
import { useNotifyStore } from '@app/store';
import { extractErrorMessage } from '@app/lib/utils';

/**
* Hook for deleting an agent
Expand Down Expand Up @@ -70,3 +78,63 @@ export const useUpdateApp = (
},
});
};

/**
* User mutation hooks
*/
export const useCreateUser = () => {
const queryClient = useQueryClient();
const { notifySuccess, notifyError } = useNotifyStore();

return useMutation({
mutationFn: createUserMutationFn,
onSuccess: () => {
notifySuccess('User created successfully');
queryClient.invalidateQueries({ queryKey: getUsersKey() });
},
onError: (error) => {
console.error('Error creating user:', error);
const errorMessage = extractErrorMessage(error);
notifyError(errorMessage || 'Failed to create user');
},
});
};

export const useUpdateUser = (userId: string | undefined) => {
const queryClient = useQueryClient();
const { notifySuccess, notifyError } = useNotifyStore();

return useMutation({
mutationFn: updateUserMutationFn,
onSuccess: () => {
notifySuccess('User updated successfully');
queryClient.invalidateQueries({ queryKey: getUsersKey() });
if (userId) {
queryClient.invalidateQueries({ queryKey: getUserKey(userId) });
}
},
onError: (error) => {
console.error('Error updating user:', error);
const errorMessage = extractErrorMessage(error);
notifyError(errorMessage || 'Failed to update user');
},
});
};

export const useDeleteUser = () => {
const queryClient = useQueryClient();
const { notifySuccess, notifyError } = useNotifyStore();

return useMutation({
mutationFn: deleteUserMutationFn,
onSuccess: () => {
notifySuccess('User deleted successfully');
queryClient.invalidateQueries({ queryKey: getUsersKey() });
},
onError: (error) => {
console.error('Error deleting user:', error);
const errorMessage = extractErrorMessage(error);
notifyError(errorMessage || 'Failed to delete user');
},
});
};
10 changes: 10 additions & 0 deletions wavefront/client/src/hooks/data/query-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SttConfig } from '@app/types/stt-config';
import { TelephonyConfig } from '@app/types/telephony-config';
import { ToolDetails } from '@app/types/tool';
import { TtsConfig } from '@app/types/tts-config';
import { IUser } from '@app/types/user';
import { VoiceAgent } from '@app/types/voice-agent';
import { WorkflowListItem, WorkflowPipelineListItem, WorkflowRunListData } from '@app/types/workflow';

Expand Down Expand Up @@ -370,6 +371,14 @@ const getAppByIdFn = async (appId: string) => {
return data?.app;
};

const getUsersQueryFn = async (): Promise<IUser[]> => {
const response = await floConsoleService.userService.listUsers();
if (response.data?.data?.users && Array.isArray(response.data.data.users)) {
return response.data.data.users;
}
return [];
};

export {
getAgentQueryFn,
getAgentsQueryFn,
Expand Down Expand Up @@ -410,5 +419,6 @@ export {
getWorkflowPipelinesQueryFn,
getWorkflowRunsQueryFn,
getWorkflowsQueryFn,
getUsersQueryFn,
readYamlQueryFn,
};
4 changes: 4 additions & 0 deletions wavefront/client/src/hooks/data/query-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const getPipelinesKey = (appId: string, statusFilter?: string) => {
const getPipelineKey = (appId: string, pipelineId: string) => ['pipeline', appId, pipelineId];
const getPipelineFilesKey = (appId: string, pipelineId: string) => ['pipeline-files', appId, pipelineId];
const getAppByIdKey = (appId: string) => ['app-by-id', appId];
const getUsersKey = () => ['users'];
const getUserKey = (userId: string) => ['user', userId];

export {
getAgentKey,
Expand Down Expand Up @@ -104,4 +106,6 @@ export {
getWorkflowRunsKey,
getWorkflowsKey,
getAppByIdKey,
getUserKey,
getUsersKey,
};
1 change: 0 additions & 1 deletion wavefront/client/src/pages/apps/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
DatasourcesIcon,
ModelInferenceIcon,
ModelRepositoryIcon,
// PermissionIcon,
PhoneIcon,
RagIcon,
WorkflowIcon,
Expand Down
Loading