Skip to content

Commit

Permalink
Refactor user credentials store
Browse files Browse the repository at this point in the history
- Make it scoped by user
- Use new API instead of stub
  • Loading branch information
davelopez committed Dec 20, 2024
1 parent 7f31751 commit 0d4e8b6
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 71 deletions.
59 changes: 38 additions & 21 deletions client/src/api/users.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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<string, ServiceCredentialsDefinition>;
}

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;
}
96 changes: 46 additions & 50 deletions client/src/stores/userCredentials.ts
Original file line number Diff line number Diff line change
@@ -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<Record<string, UserCredentials[]>>({});

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<UserCredentials[]> {
ensureUserIsRegistered();
async function fetchAllUserCredentialsForTool(toolId: string): Promise<UserCredentials[]> {
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<UserCredentials[]> {
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 {
Expand Down

0 comments on commit 0d4e8b6

Please sign in to comment.