Skip to content

Commit

Permalink
web/satellite: check if AG name is free to use at the beginning of cr…
Browse files Browse the repository at this point in the history
…eation flow

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

Issue:
#5693

Change-Id: I06583bf458cea977cb0a920d55df50f2d19e1599
  • Loading branch information
VitaliiShpital authored and Storj Robot committed Jun 13, 2023
1 parent cc08555 commit 782811c
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 13 deletions.
21 changes: 20 additions & 1 deletion web/satellite/src/api/accessGrants.ts
Expand Up @@ -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
Expand Down
Expand Up @@ -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>
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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>

Expand Down Expand Up @@ -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>
7 changes: 7 additions & 0 deletions web/satellite/src/store/modules/accessGrantsStore.ts
Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -205,6 +210,7 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
}

function clear(): void {
state.allAGNames = [];
state.cursor = new AccessGrantCursor();
state.page = new AccessGrantsPage();
state.selectedAccessGrantsIds = [];
Expand All @@ -227,6 +233,7 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
return {
state,
selectedAccessGrants,
getAllAGNames,
startWorker,
stopWorker,
getAccessGrants,
Expand Down
16 changes: 8 additions & 8 deletions web/satellite/src/types/accessGrants.ts
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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
*
Expand Down
4 changes: 4 additions & 0 deletions web/satellite/tests/unit/mock/api/accessGrants.ts
Expand Up @@ -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'));
}
Expand Down

0 comments on commit 782811c

Please sign in to comment.