web/satellite: check if AG name is free to use at the beginning of creation flow

By this change we check if provided access grant name is free to use at the very beginning of the creation flow.

Issue:
https://github.com/storj/storj/issues/5693

Change-Id: I06583bf458cea977cb0a920d55df50f2d19e1599
This commit is contained in:
Vitalii 2023-06-12 16:26:47 +03:00 committed by Storj Robot
parent cc085553c2
commit 782811c634
5 changed files with 68 additions and 13 deletions

View File

@ -129,7 +129,26 @@ export class AccessGrantsApiGql extends BaseGql implements AccessGrantsApi {
}
/**
* Used to delete access grant access grant by name and project ID.
* Fetch all API key names.
*
* @returns string[]
* @throws Error
*/
public async getAllAPIKeyNames(projectId: string): Promise<string[]> {
const path = `${this.ROOT_PATH}/api-key-names?projectID=${projectId}`;
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('Can not get access grant names');
}
const result = await response.json();
return result ? result : [];
}
/**
* Used to delete access grant by name and project ID.
*
* @param name - name of the access grant that will be deleted
* @param projectID - id of the project where access grant was created

View File

@ -99,7 +99,9 @@
:credentials="edgeCredentials"
:name="accessName"
/>
<div v-if="isLoading" class="modal__blur" />
<div v-if="isLoading" class="modal__blur">
<VLoader width="50px" height="50px" />
</div>
</div>
</template>
</VModal>
@ -130,6 +132,7 @@ import { useProjectsStore } from '@/store/modules/projectsStore';
import { useConfigStore } from '@/store/modules/configStore';
import VModal from '@/components/common/VModal.vue';
import VLoader from '@/components/common/VLoader.vue';
import CreateNewAccessStep from '@/components/accessGrants/createFlow/steps/CreateNewAccessStep.vue';
import ChoosePermissionStep from '@/components/accessGrants/createFlow/steps/ChoosePermissionStep.vue';
import AccessEncryptionStep from '@/components/accessGrants/createFlow/steps/AccessEncryptionStep.vue';
@ -157,6 +160,13 @@ const initPermissions = [
Permission.List,
];
/**
* Returns all AG names from store.
*/
const allAGNames = computed((): string[] => {
return agStore.state.allAGNames;
});
/**
* Indicates if user has to be prompt to enter project passphrase.
*/
@ -172,7 +182,7 @@ const storedPassphrase = computed((): string => {
});
const worker = ref<Worker| null>(null);
const isLoading = ref<boolean>(false);
const isLoading = ref<boolean>(true);
const step = ref<CreateAccessStep>(CreateAccessStep.CreateNewAccess);
const selectedAccessTypes = ref<AccessType[]>([]);
const selectedPermissions = ref<Permission[]>(initPermissions);
@ -384,6 +394,11 @@ function setStep(stepArg: CreateAccessStep): void {
* If not then we set regular second step (Permissions).
*/
function setSecondStepBasedOnAccessType(): void {
if (allAGNames.value.includes(accessName.value)) {
notify.error('Provided name is already in use', AnalyticsErrorEventSource.CREATE_AG_MODAL);
return;
}
// Unfortunately local storage updates are not reactive so putting it inside computed property doesn't do anything.
// That's why we explicitly call it here.
const shouldShowInfo = !LocalData.getServerSideEncryptionModalHidden() && selectedAccessTypes.value.includes(AccessType.S3);
@ -626,10 +641,17 @@ onMounted(async () => {
generatedPassphrase.value = generateMnemonic();
try {
await bucketsStore.getAllBucketsNames(projectsStore.state.selectedProject.id);
const projectID = projectsStore.state.selectedProject.id;
await Promise.all([
agStore.getAllAGNames(projectID),
bucketsStore.getAllBucketsNames(projectID),
]);
} catch (error) {
notify.error(`Unable to fetch all bucket names. ${error.message}`, AnalyticsErrorEventSource.CREATE_AG_MODAL);
notify.error(error.message, AnalyticsErrorEventSource.CREATE_AG_MODAL);
}
isLoading.value = false;
});
</script>
@ -677,6 +699,9 @@ onMounted(async () => {
inset: 0;
background-color: rgb(0 0 0 / 10%);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -18,6 +18,7 @@ import { useConfigStore } from '@/store/modules/configStore';
import { DEFAULT_PAGE_LIMIT } from '@/types/pagination';
class AccessGrantsState {
public allAGNames: string[] = [];
public cursor: AccessGrantCursor = new AccessGrantCursor();
public page: AccessGrantsPage = new AccessGrantsPage();
public selectedAccessGrantsIds: string[] = [];
@ -76,6 +77,10 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
state.isAccessGrantsWebWorkerReady = false;
}
async function getAllAGNames(projectID: string): Promise<void> {
state.allAGNames = await api.getAllAPIKeyNames(projectID);
}
async function getAccessGrants(pageNumber: number, projectID: string, limit = DEFAULT_PAGE_LIMIT): Promise<AccessGrantsPage> {
state.cursor.page = pageNumber;
state.cursor.limit = limit;
@ -205,6 +210,7 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
}
function clear(): void {
state.allAGNames = [];
state.cursor = new AccessGrantCursor();
state.page = new AccessGrantsPage();
state.selectedAccessGrantsIds = [];
@ -227,6 +233,7 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
return {
state,
selectedAccessGrants,
getAllAGNames,
startWorker,
stopWorker,
getAccessGrants,

View File

@ -6,14 +6,6 @@ import { DEFAULT_PAGE_LIMIT } from '@/types/pagination';
export type OnHeaderClickCallback = (sortBy: AccessGrantsOrderBy, sortDirection: SortDirection) => Promise<void>;
/**
* AccessGrantsWorker provides access to the WASM module.
*/
export interface AccessGrantsWorkerFactory {
// TODO: this should be converted to a proper interface.
create(): Worker;
}
/**
* Exposes all access grants-related functionality.
*/
@ -50,6 +42,14 @@ export interface AccessGrantsApi {
*/
deleteByNameAndProjectID(name: string, projectID: string): Promise<void>;
/**
* Fetch all API key names.
*
* @returns string[]
* @throws Error
*/
getAllAPIKeyNames(projectId: string): Promise<string[]>
/**
* Get gateway credentials using access grant
*

View File

@ -36,6 +36,10 @@ export class AccessGrantsMock implements AccessGrantsApi {
return Promise.resolve();
}
getAllAPIKeyNames(_projectID: string): Promise<string[]> {
return Promise.resolve([]);
}
getGatewayCredentials(_accessGrant: string, _requestURL: string): Promise<EdgeCredentials> {
return Promise.resolve(new EdgeCredentials('testCredId', new Date(), 'testAccessKeyId', 'testSecret', 'testEndpoint'));
}