<template> <div class="api-token"> <div class="api-token__head"> <div class="api-token__info"> <div class="api-token__name"> <div class="api-token__name-content"> <Editable ref="rename" :value="token.name" @change=" updateToken( token, { name: $event.value }, { name: $event.oldValue } ) " ></Editable> </div> <a ref="contextLink" class="api-token__more" @click.prevent=" $refs.context.toggle($refs.contextLink, 'bottom', 'right', 4) " > <i class="fas fa-ellipsis-h"></i> </a> <Context ref="context"> <div class="api-token__key"> <div class="api-token__key-name"> {{ $t('apiToken.tokenPrefix') }} </div> <div class="api-token__key-value"> <template v-if="tokenVisible"> {{ token.key }} </template> <template v-else>••••••••••••••••••••••••••••••••</template> </div> <a class="api-token__key-visible" :title="$t('apiToken.showOrHide')" @click.prevent="tokenVisible = !tokenVisible" > <i class="fas" :class="tokenVisible ? 'fa-eye-slash' : 'fa-eye'" ></i> </a> <a class="api-token__key-copy" :title="$t('apiToken.copyToClipboard')" @click=";[copyTokenToClipboard(), $refs.copied.show()]" > <i class="fas fa-copy"></i> <Copied ref="copied"></Copied> </a> </div> <ul class="context__menu"> <li> <nuxt-link :to="{ name: 'database-api-docs' }"> <i class="context__menu-icon fas fa-fw fa-book"></i> {{ $t('apiToken.viewAPIDocs') }} </nuxt-link> </li> <li> <a :class="{ 'context__menu-item--loading': rotateLoading, }" @click="rotateKey(token)" > <i class="context__menu-icon fas fa-fw fa-recycle"></i> {{ $t('apiToken.generateNewToken') }} </a> </li> <li> <a @click="enableRename()"> <i class="context__menu-icon fas fa-fw fa-pen"></i> {{ $t('action.rename') }} </a> </li> <li> <a :class="{ 'context__menu-item--loading': deleteLoading, }" @click.prevent="deleteToken(token)" > <i class="context__menu-icon fas fa-fw fa-trash"></i> {{ $t('action.delete') }} </a> </li> </ul> </Context> </div> <div class="api-token__details"> <div class="api-token__group">{{ group.name }}</div> <a class="api-token__expand" @click.prevent="open = !open"> {{ $t('apiToken.showDatabases') }} <i class="fas" :class="{ 'fa-angle-down': !open, 'fa-angle-up': open, }" ></i> </a> </div> </div> <div class="api-token__permissions"> <div v-for="(operationName, operation) in operations" :key="operation" class="api-token__permission" > {{ operationName }} <SwitchInput :value="isActive(operation)" :large="true" @input="toggle(operation, $event)" ></SwitchInput> </div> </div> </div> <div class="api-token__body" :class="{ 'api-token__body--open': open }"> <div v-for="database in databases" :key="database.id"> <div class="api-token__row"> <div class="api-token__database"> {{ database.name }} {{ database.id }} </div> <div class="api-token__permissions"> <div v-for="(operationName, operation) in operations" :key="operation" class="api-token__permission" > <SwitchInput :value="isDatabaseActive(database, operation)" :large="true" @input="toggleDatabase(database, databases, operation, $event)" ></SwitchInput> </div> </div> </div> <div v-for="table in database.tables" :key="table.id" class="api-token__row" > <div class="api-token__table"> {{ table.name }} <small>(id: {{ table.id }})</small> </div> <div class="api-token__permissions"> <div v-for="(operationName, operation) in operations" :key="operation" class="api-token__permission" > <Checkbox :value="isTableActive(table, database, operation)" @input=" toggleTable(table, database, databases, operation, $event) " ></Checkbox> </div> </div> </div> </div> </div> </div> </template> <script> import { notifyIf } from '@baserow/modules/core/utils/error' import { DatabaseApplicationType } from '@baserow/modules/database/applicationTypes' import { copyToClipboard } from '@baserow/modules/database/utils/clipboard' import TokenService from '@baserow/modules/database/services/token' export default { name: 'APIToken', props: { token: { type: Object, required: true, }, }, data() { return { open: false, deleteLoading: false, rotateLoading: false, tokenVisible: false, operations: { create: this.$t('apiToken.create'), read: this.$t('apiToken.read'), update: this.$t('apiToken.update'), delete: this.$t('apiToken.delete'), }, } }, computed: { group() { return this.$store.getters['group/get'](this.token.group) }, databases() { return this.$store.getters['application/getAllOfGroup']( this.group ).filter( (application) => application.type === DatabaseApplicationType.getType() ) }, }, methods: { copyTokenToClipboard() { copyToClipboard(this.token.key) }, enableRename() { this.$refs.context.hide() this.$refs.rename.edit() }, /** * Updates some token properties. If the request fails the changes are going to be * reverted. */ async updateToken(token, values, old) { Object.assign(token, values) try { await TokenService(this.$client).update(token.id, values) } catch (error) { Object.assign(token, old) notifyIf(error, 'token') } }, /** * Asks the backend to rotate the key of the token. */ async rotateKey(token) { this.rotateLoading = true try { const { data } = await TokenService(this.$client).rotateKey(token.id) this.token.key = data.key this.tokenVisible = true this.rotateLoading = false } catch (error) { this.rotateLoading = false notifyIf(error, 'token') } }, /** * Deletes the token and emits a signal to the parent component such that is can * be removed from the list. */ async deleteToken(token) { if (this.deleteLoading) { return } try { await TokenService(this.$client).delete(token.id) this.deleteLoading = false this.$emit('deleted') } catch (error) { this.deleteLoading = false notifyIf(error, 'field') } }, /** * Check if a type (database or table) with the given id exists in the * permissions of the provided operation. */ exists(operation, type, id) { const permissions = this.token.permissions[operation] if (Array.isArray(permissions)) { for (const i in permissions) { if (permissions[i][0] === type && permissions[i][1] === id) { return true } } } return false }, /** * Adds a type (database or table) to the permissions of the given operation. */ add(operation, type, id) { const permissions = this.token.permissions[operation] if (!Array.isArray(permissions)) { this.token.permissions[operation] = [] } if (!this.exists(operation, type, id)) { this.token.permissions[operation].push([type, id]) } }, /** * Removes a type (database or table) to the permissions of the given operation. */ remove(operation, type, id) { let permissions = this.token.permissions[operation] if (!Array.isArray(permissions)) { this.token.permissions[operation] = [] permissions = [] } this.token.permissions[operation] = permissions.filter((permission) => { return !(permission[0] === type && permission[1] === id) }) }, /** * Indicates if the token has permissions to all databases and tables for the given * operation. Returns 2 if there is only partially access. */ isActive(operation) { const value = this.token.permissions[operation] if (value === true) { return true } else if ( // If the value is false or if no permissions have been set we can show the // switch as if is empty. value === false || (Array.isArray(value) && value.length === 0) ) { return false } else { return 2 } }, /** * Indicates if the token has permissions to the given database for the given * operation. Returns 2 if there is only partially access. */ isDatabaseActive(database, operation) { if ( this.isActive(operation) === true || this.exists(operation, 'database', database.id) ) { return true } const tables = database.tables for (const i in tables) { if (this.exists(operation, 'table', tables[i].id)) { return 2 } } return false }, /** * Indicates if the token has permissions to the given table for the given * operation. */ isTableActive(table, database, operation) { return ( this.isActive(operation) === true || this.exists(operation, 'database', database.id) || this.exists(operation, 'table', table.id) ) }, /** * Changes the token permission state of all databases and tables of the given * operation. Also updates the permissions with the backend. */ toggle(operation, value) { const oldPermissions = JSON.parse(JSON.stringify(this.token.permissions)) // We can easily change the value to true or false because the permissions are // now going to be controlled on global (group) level. this.token.permissions[operation] = value this.updateToken( this.token, { permissions: this.token.permissions }, { permissions: oldPermissions } ) }, /** * Changes the token permission state of a provided database and his tables of the * given operation. Also updates the permissions with the backend. */ toggleDatabase(database, sibblings, operation, value) { const oldPermissions = JSON.parse(JSON.stringify(this.token.permissions)) // First we want to add all the databases that already have an active state to // the permissions because the permissions are not going to controlled on // database level. sibblings .filter( (database) => this.isDatabaseActive(database, operation) === true ) .forEach((database) => { this.add(operation, 'database', database.id) }) // Remove all the child table permissions of the database because the // permissions are not going to controlled on database level. database.tables.forEach((table) => { this.remove(operation, 'table', table.id) }) // Depending on the value we either need to remove the database from the list // or add it. if (value) { this.add(operation, 'database', database.id) } else { this.remove(operation, 'database', database.id) } // Updates the permissions with the backend. this.updateToken( this.token, { permissions: this.token.permissions }, { permissions: oldPermissions } ) }, /** * Changes the token permission state of a provided table of the given operation. * Also updates the permissions with the backend. */ toggleTable(table, database, databases, operation, value) { const oldPermissions = JSON.parse(JSON.stringify(this.token.permissions)) // First we want to add all the databases that already have an active state to // the permissions because the permissions are now going to be controlled on // table level. databases .filter( (database) => this.isDatabaseActive(database, operation) === true ) .forEach((database) => { this.add(operation, 'database', database.id) }) // We also want to add all the tables that already have an active state to the // permissions. database.tables .filter( (table) => this.isTableActive(table, database, operation) === true ) .forEach((table) => { this.add(operation, 'table', table.id) }) // Depending on the value we either need to remove the database from the list // or add it. this.remove(operation, 'database', database.id) if (value) { this.add(operation, 'table', table.id) } else { this.remove(operation, 'table', table.id) } // Updates the permissions with the backend. this.updateToken( this.token, { permissions: this.token.permissions }, { permissions: oldPermissions } ) }, }, } </script>