From 0d4e8b663ea571a7a2666c54f1f1a4ffe074e425 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:52:38 +0100 Subject: [PATCH] Refactor user credentials store - Make it scoped by user - Use new API instead of stub --- client/src/api/users.ts | 59 +++++++++++------ client/src/stores/userCredentials.ts | 96 +++++++++++++--------------- 2 files changed, 84 insertions(+), 71 deletions(-) diff --git a/client/src/api/users.ts b/client/src/api/users.ts index f3a9204a1ae8..893bf23efbcf 100644 --- a/client/src/api/users.ts +++ b/client/src/api/users.ts @@ -1,4 +1,4 @@ -import { GalaxyApi } from "@/api"; +import { type components, GalaxyApi } from "@/api"; import { toQuotaUsage } from "@/components/User/DiskUsage/Quota/model"; import { rethrowSimple } from "@/utils/simple-error"; @@ -36,36 +36,53 @@ export async function fetchCurrentUserQuotaSourceUsage(quotaSourceLabel?: string return toQuotaUsage(data); } -// TODO: Temporarily using these interfaces until the new API is implemented -export interface CredentialsDefinition { - name: string; +export type CreateSourceCredentialsPayload = components["schemas"]["CreateSourceCredentialsPayload"]; +export type ServiceCredentialPayload = components["schemas"]["ServiceCredentialPayload"]; +export type ServiceGroupPayload = components["schemas"]["ServiceGroupPayload"]; +export type UserCredentials = components["schemas"]["UserCredentialsResponse"]; + +// TODO: Change API to directly return the correct type to avoid this transformation and additional type definitions. +export function transformToSourceCredentials( + toolId: string, + toolCredentialsDefinition: ServiceCredentialsDefinition[] +): SourceCredentialsDefinition { + return { + sourceType: "tool", + sourceId: toolId, + services: new Map(toolCredentialsDefinition.map((service) => [service.reference, service])), + }; +} + +/** + * Represents the definition of credentials for a particular service. + */ +export interface ServiceCredentialsDefinition { reference: string; + name: string; optional: boolean; multiple: boolean; label?: string; description?: string; -} -export interface UserCredentials extends CredentialsDefinition { - variables: Variable[]; - secrets: Secret[]; + variables: ServiceVariableDefinition[]; + secrets: ServiceVariableDefinition[]; } -export interface ToolCredentialsDefinition extends CredentialsDefinition { - variables: CredentialDetail[]; - secrets: CredentialDetail[]; +/** + * Represents the definition of credentials for a particular source. + * A source can be a tool, a workflow, etc.Base interface for credentials definitions. + * A source may accept multiple services, each with its own credentials. + */ +export interface SourceCredentialsDefinition { + sourceType: string; + sourceId: string; + services: Map; } -export interface CredentialDetail { +/** + * Base interface for credential details. It is used to define the structure of variables and secrets. + */ +export interface ServiceVariableDefinition { name: string; label?: string; description?: string; } - -export interface Secret extends CredentialDetail { - alreadySet: boolean; - value?: string; -} - -export interface Variable extends CredentialDetail { - value?: string; -} diff --git a/client/src/stores/userCredentials.ts b/client/src/stores/userCredentials.ts index 149c6489b69e..e0b72e8792cb 100644 --- a/client/src/stores/userCredentials.ts +++ b/client/src/stores/userCredentials.ts @@ -1,76 +1,72 @@ -import { defineStore } from "pinia"; -import { ref } from "vue"; +import { ref, set } from "vue"; -import { isRegisteredUser } from "@/api"; -import type { ToolCredentialsDefinition, UserCredentials } from "@/api/users"; -import { useUserStore } from "@/stores/userStore"; +import { GalaxyApi } from "@/api"; +import type { CreateSourceCredentialsPayload, UserCredentials } from "@/api/users"; -const SECRET_PLACEHOLDER = "************"; +import { defineScopedStore } from "./scopedStore"; -export const useUserCredentialsStore = defineStore("userCredentialsStore", () => { +export const useUserCredentialsStore = defineScopedStore("userCredentialsStore", (currentUserId: string) => { const userCredentialsForTools = ref>({}); - const userStore = useUserStore(); + function getKey(toolId: string): string { + const userId = ensureUserIsRegistered(); + return `${userId}-${toolId}`; + } function getAllUserCredentialsForTool(toolId: string): UserCredentials[] | undefined { ensureUserIsRegistered(); return userCredentialsForTools.value[toolId]; } - async function fetchAllUserCredentialsForTool( - toolId: string, - toolCredentialsDefinitions: ToolCredentialsDefinition[] - ): Promise { - ensureUserIsRegistered(); + async function fetchAllUserCredentialsForTool(toolId: string): Promise { + const userId = ensureUserIsRegistered(); + + const { data, error } = await GalaxyApi().GET("/api/users/{user_id}/credentials", { + params: { + path: { user_id: userId }, + query: { + source_type: "tool", + source_id: toolId, + }, + }, + }); - //TODO: Implement this. Simulate for now - await new Promise((resolve) => setTimeout(resolve, 1000)); - const simulatedUserCredentials = []; - for (const credentials of toolCredentialsDefinitions) { - const fetchedCredentials = { - ...credentials, - secrets: credentials.secrets.map((secret) => ({ - ...secret, - alreadySet: false, - value: SECRET_PLACEHOLDER, //This value is never set for real - })), - variables: credentials.variables.map((variable) => ({ ...variable })), - }; - simulatedUserCredentials.push(fetchedCredentials); + if (error) { + throw Error(`Failed to fetch user credentials for tool ${toolId}: ${error.err_msg}`); } - userCredentialsForTools.value[toolId] = simulatedUserCredentials; - return simulatedUserCredentials; + + const key = getKey(toolId); + set(userCredentialsForTools.value, key, data); + return data; } async function saveUserCredentialsForTool( - toolId: string, - userCredentials: UserCredentials[] + providedCredentials: CreateSourceCredentialsPayload ): Promise { - ensureUserIsRegistered(); - //TODO: Implement this. Simulate for now - await new Promise((resolve) => setTimeout(resolve, 1000)); + const userId = ensureUserIsRegistered(); + const toolId = providedCredentials.source_id; - const savedUserCredentials: UserCredentials[] = []; - for (const credentials of userCredentials) { - const savedCredentials = { - ...credentials, - secrets: credentials.secrets.map((secret) => ({ - ...secret, - alreadySet: !!secret.value && secret.value !== SECRET_PLACEHOLDER, - value: SECRET_PLACEHOLDER, - })), - variables: credentials.variables.map((variable) => ({ ...variable })), - }; - savedUserCredentials.push(savedCredentials); + const { data, error } = await GalaxyApi().POST("/api/users/{user_id}/credentials", { + params: { + path: { user_id: userId }, + }, + body: providedCredentials, + }); + + if (error) { + throw Error(`Failed to save user credentials for tool ${toolId}: ${error.err_msg}`); } - userCredentialsForTools.value[toolId] = savedUserCredentials; - return savedUserCredentials; + + const key = getKey(toolId); + set(userCredentialsForTools.value, key, data); + return data; } - function ensureUserIsRegistered() { - if (!isRegisteredUser(userStore.currentUser)) { + function ensureUserIsRegistered(): string { + if (currentUserId === "anonymous") { throw new Error("Only registered users can have tool credentials"); } + return currentUserId; } return {