web/satellite: migrate AuthorizeArea to use SFC composition api

Migrate component to use SFC composition api

Change-Id: I7ff1f7f9ff64419cba2feac96130b78be048605b
This commit is contained in:
Vitalii 2023-03-22 12:51:49 +02:00 committed by Storj Robot
parent 8d64330dfe
commit 9a87da1b8a

View File

@ -87,8 +87,8 @@
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { Validator } from '@/utils/validation';
import { RouteConfig } from '@/router';
@ -102,255 +102,14 @@ import { FetchState } from '@/utils/constants/fetchStateEnum';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { OAuthClient, OAuthClientsAPI } from '@/api/oauthClients';
import { AnalyticsHttpApi } from '@/api/analytics';
import { useNotify, useRouter, useStore } from '@/utils/hooks';
import VInput from '@/components/common/VInput.vue';
import LogoIcon from '@/../static/images/logo.svg';
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const oauthClientsAPI = new OAuthClientsAPI();
// @vue/component
@Component({
components: {
VInput,
LogoIcon,
},
})
export default class Authorize extends Vue {
private requestErr = '';
private oauthData: {
client_id?: string;
redirect_uri?: string;
state?: string;
response_type?: string;
scope?: string;
} = {};
private clientKey = '';
private client: OAuthClient = {
id: '',
redirectURL: '',
appName: '',
appLogoURL: '',
};
private projects: Record<string, Project> = {};
private buckets: Array<string> = [];
private selectedProjectID = '';
private selectedBucketName = '';
private providedPassphrase = '';
private scope = '';
private valid = false;
private projectErr = '';
private bucketErr = '';
private passphraseErr = '';
private actions = '';
private bucketExists = false;
private worker: Worker;
public readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
private async ensureLogin(): Promise<void> {
try {
await this.$store.dispatch(USER_ACTIONS.GET);
} catch (error) {
if (!(error instanceof ErrorUnauthorized)) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_FETCH_STATE, FetchState.ERROR);
await this.$notify.error(error.message, null);
}
const query = new URLSearchParams(this.oauthData).toString();
const path = `${RouteConfig.Authorize.path}?${query}#${this.clientKey}`;
this.analytics.pageVisit(`${RouteConfig.Login.path}?return_url=${encodeURIComponent(path)}`);
await this.$router.push(`${RouteConfig.Login.path}?return_url=${encodeURIComponent(path)}`);
return;
}
}
private async ensureWorker(): Promise<void> {
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.STOP_ACCESS_GRANTS_WEB_WORKER);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.SET_ACCESS_GRANTS_WEB_WORKER);
} catch (error) {
await this.$notify.error(`Unable to set access grants wizard. ${error.message}`, null);
return;
}
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
this.worker.onerror = (error: ErrorEvent) => this.$notify.error(error.message, null);
}
private async verifyClientConfiguration(): Promise<void> {
const clientID: string = this.oauthData.client_id ?? '';
const redirectURL: string = this.oauthData.redirect_uri ?? '';
const state: string = this.oauthData.state ?? '';
const responseType: string = this.oauthData.response_type ?? '';
const scope: string = this.oauthData.scope ?? '';
if (!clientID || !redirectURL) {
this.requestErr = 'Both client_id and redirect_uri must be provided.';
return;
}
let client: OAuthClient;
try {
client = await oauthClientsAPI.get(clientID);
} catch (e) {
this.requestErr = e.message;
return;
}
let err: { [key: string]: string } | null = null;
if (!state || !responseType || !scope) {
err = {
error_description: 'The request is missing a required parameter (state, response_type, or scope).',
};
} else if (!this.clientKey) {
err = {
error_description: 'An encryption key must be provided in the fragment of the request.',
};
} else if (!redirectURL.startsWith(client.redirectURL)) {
err = {
error_description: 'The provided redirect url does not match the one in our system.',
};
}
if (err) {
location.href = `${redirectURL}?${(new URLSearchParams(err)).toString()}`;
return;
}
this.client = client;
// initialize the form
this.setBucket(slugify(this.client.appName));
this.actions = formatObjectPermissions(scope);
}
private async loadProjects(): Promise<void> {
await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
const projects = {};
for (const project of this.$store.getters.projects) {
projects[project.name] = project;
}
this.projects = projects;
}
/**
* Lifecycle hook after initial render.
* Makes activated banner visible on successful account activation.
*/
public async mounted(): Promise<void> {
this.oauthData = this.$route.query;
this.clientKey = this.$route.hash ? this.$route.hash.substring(1) : '';
await this.ensureLogin();
await this.ensureWorker();
await this.verifyClientConfiguration();
if (this.requestErr) {
return;
}
await this.loadProjects();
}
public async setProject(value: string): Promise<void> {
if (!this.projects[value]) {
this.projectErr = 'project does not exist';
return;
}
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.projects[value].id);
await this.$store.dispatch(BUCKET_ACTIONS.FETCH_ALL_BUCKET_NAMES);
this.selectedProjectID = this.$store.getters.selectedProject.id;
this.buckets = this.$store.state.bucketUsageModule.allBucketNames.sort();
this.setBucket(this.selectedBucketName);
}
public setBucket(value: string): void {
this.selectedBucketName = value;
this.bucketExists = this.selectedProjectID.length === 0 || value.length === 0 || this.buckets.includes(value);
this.setScope();
}
public setPassphrase(value: string): void {
this.providedPassphrase = value;
this.setScope();
}
public async setScope(): Promise<void> {
if (!this.validate()) {
return;
}
this.worker.postMessage({
'type': 'DeriveAndEncryptRootKey',
'passphrase': this.providedPassphrase,
'projectID': this.selectedProjectID,
'aesKey': this.clientKey,
});
const event: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (event.data.error) {
await this.$notify.error(event.data.error, null);
return;
}
const scope = this.oauthData.scope,
project = this.selectedProjectID,
bucket = this.selectedBucketName,
cubbyhole = event.data.value;
this.scope = `${scope} project:${project} bucket:${bucket} cubbyhole:${cubbyhole}`;
}
public async onDeny(): Promise<void> {
location.href = `${this.oauthData.redirect_uri}?${new URLSearchParams({
error_description: 'The resource owner or authorization server denied the request',
}).toString()}`;
}
private validate(): boolean {
this.projectErr = '';
this.bucketErr = '';
this.passphraseErr = '';
if (this.selectedProjectID === '') {
this.projectErr = 'Missing project.';
}
if (!Validator.bucketName(this.selectedBucketName)) {
this.bucketErr = 'Name must contain only lowercase latin characters, numbers, a hyphen or a period';
}
if (this.providedPassphrase === '') {
this.passphraseErr = 'A passphrase must be provided.';
}
this.valid = this.projectErr === '' &&
this.bucketErr === '' &&
this.passphraseErr === '';
return this.valid;
}
}
const validPerms = {
'list': true,
'read': true,
@ -358,6 +117,39 @@ const validPerms = {
'delete': true,
};
const store = useStore();
const notify = useNotify();
const router = useRouter();
const worker = ref<Worker | null>(null);
const valid = ref<boolean>(false);
const bucketExists = ref<boolean>(false);
const actions = ref<string>('');
const clientKey = ref<string>('');
const selectedProjectID = ref<string>('');
const selectedBucketName = ref<string>('');
const providedPassphrase = ref<string>('');
const scope = ref<string>('');
const buckets = ref<string[]>([]);
const requestErr = ref<string>('');
const projectErr = ref<string>('');
const bucketErr = ref<string>('');
const passphraseErr = ref<string>('');
const projects = ref<Record<string, Project>>({});
const client = ref<OAuthClient>({
id: '',
redirectURL: '',
appName: '',
appLogoURL: '',
});
const oauthData = ref<{
client_id?: string;
redirect_uri?: string;
state?: string;
response_type?: string;
scope?: string;
}>({});
function slugify(name: string): string {
name = name.toLowerCase();
name = name.replace(/\s+/g, '-');
@ -389,6 +181,205 @@ function formatObjectPermissions(scope: string): string {
return `${perms.slice(0, perms.length - 1).join(', ')}, and ${perms[perms.length - 1]}`;
}
async function ensureLogin(): Promise<void> {
try {
await store.dispatch(USER_ACTIONS.GET);
} catch (error) {
if (!(error instanceof ErrorUnauthorized)) {
await store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, FetchState.ERROR);
await notify.error(error.message, null);
}
const query = new URLSearchParams(oauthData.value).toString();
const path = `${RouteConfig.Authorize.path}?${query}#${clientKey.value}`;
analytics.pageVisit(`${RouteConfig.Login.path}?return_url=${encodeURIComponent(path)}`);
await router.push(`${RouteConfig.Login.path}?return_url=${encodeURIComponent(path)}`);
}
}
async function ensureWorker(): Promise<void> {
try {
await store.dispatch(ACCESS_GRANTS_ACTIONS.STOP_ACCESS_GRANTS_WEB_WORKER);
await store.dispatch(ACCESS_GRANTS_ACTIONS.SET_ACCESS_GRANTS_WEB_WORKER);
} catch (error) {
await notify.error(`Unable to set access grants wizard. ${error.message}`, null);
return;
}
worker.value = store.state.accessGrantsModule.accessGrantsWebWorker;
if (worker.value) {
worker.value.onerror = (error: ErrorEvent) => notify.error(error.message, null);
}
}
async function verifyClientConfiguration(): Promise<void> {
const clientID: string = oauthData.value.client_id ?? '';
const redirectURL: string = oauthData.value.redirect_uri ?? '';
const state: string = oauthData.value.state ?? '';
const responseType: string = oauthData.value.response_type ?? '';
const scope: string = oauthData.value.scope ?? '';
if (!clientID || !redirectURL) {
requestErr.value = 'Both client_id and redirect_uri must be provided.';
return;
}
let oAuthClient: OAuthClient;
try {
oAuthClient = await oauthClientsAPI.get(clientID);
} catch (error) {
requestErr.value = error.message;
return;
}
let err: { [key: string]: string } | null = null;
if (!state || !responseType || !scope) {
err = {
error_description: 'The request is missing a required parameter (state, response_type, or scope).',
};
} else if (!clientKey.value) {
err = {
error_description: 'An encryption key must be provided in the fragment of the request.',
};
} else if (!redirectURL.startsWith(oAuthClient.redirectURL)) {
err = {
error_description: 'The provided redirect url does not match the one in our system.',
};
}
if (err) {
location.href = `${redirectURL}?${(new URLSearchParams(err)).toString()}`;
return;
}
client.value = { ...oAuthClient };
// initialize the form
setBucket(slugify(client.value.appName));
actions.value = formatObjectPermissions(scope);
}
async function loadProjects(): Promise<void> {
await store.dispatch(PROJECTS_ACTIONS.FETCH);
const newProjects: Record<string, Project> = {};
for (const project of store.getters.projects) {
newProjects[project.name] = project;
}
projects.value = { ...newProjects };
}
async function setProject(value: string): Promise<void> {
if (!projects.value[value]) {
projectErr.value = 'project does not exist';
return;
}
await store.dispatch(PROJECTS_ACTIONS.SELECT, projects.value[value].id);
await store.dispatch(BUCKET_ACTIONS.FETCH_ALL_BUCKET_NAMES);
selectedProjectID.value = store.getters.selectedProject.id;
buckets.value = store.state.bucketUsageModule.allBucketNames.sort();
setBucket(selectedBucketName.value);
}
function setBucket(value: string): void {
selectedBucketName.value = value;
bucketExists.value = selectedProjectID.value.length === 0 || value.length === 0 || buckets.value.includes(value);
setScope();
}
function setPassphrase(value: string): void {
providedPassphrase.value = value;
setScope();
}
async function setScope(): Promise<void> {
if (!validate() || !worker.value) {
return;
}
worker.value.postMessage({
'type': 'DeriveAndEncryptRootKey',
'passphrase': providedPassphrase.value,
'projectID': selectedProjectID.value,
'aesKey': clientKey.value,
});
const event: MessageEvent = await new Promise(resolve => {
if (worker.value) {
worker.value.onmessage = resolve;
}
});
if (event.data.error) {
await notify.error(event.data.error, null);
return;
}
const oAuthScope = oauthData.value.scope,
project = selectedProjectID.value,
bucket = selectedBucketName.value,
cubbyhole = event.data.value;
scope.value = `${oAuthScope} project:${project} bucket:${bucket} cubbyhole:${cubbyhole}`;
}
async function onDeny(): Promise<void> {
location.href = `${oauthData.value.redirect_uri}?${new URLSearchParams({
error_description: 'The resource owner or authorization server denied the request',
}).toString()}`;
}
function validate(): boolean {
projectErr.value = '';
bucketErr.value = '';
passphraseErr.value = '';
if (selectedProjectID.value === '') {
projectErr.value = 'Missing project.';
}
if (!Validator.bucketName(selectedBucketName.value)) {
bucketErr.value = 'Name must contain only lowercase latin characters, numbers, a hyphen or a period';
}
if (providedPassphrase.value === '') {
passphraseErr.value = 'A passphrase must be provided.';
}
valid.value = projectErr.value === '' &&
bucketErr.value === '' &&
passphraseErr.value === '';
return valid.value;
}
/**
* Lifecycle hook after initial render.
* Makes activated banner visible on successful account activation.
*/
onMounted(async () => {
oauthData.value = { ...router.currentRoute.query };
clientKey.value = router.currentRoute.hash ? router.currentRoute.hash.substring(1) : '';
await ensureLogin();
await ensureWorker();
await verifyClientConfiguration();
if (requestErr.value) {
return;
}
await loadProjects();
});
</script>
<style scoped lang="scss">