Compare commits
15 Commits
@nhost/das
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa544cc696 | ||
|
|
7f959b3544 | ||
|
|
bf5056f14b | ||
|
|
b3e674ced0 | ||
|
|
1f9720fa25 | ||
|
|
747bc1104a | ||
|
|
887c168b1b | ||
|
|
038d903555 | ||
|
|
deb14b510b | ||
|
|
8ee23c9303 | ||
|
|
bca1835ecd | ||
|
|
eb55408f85 | ||
|
|
d539a103d9 | ||
|
|
1bc3c30f85 | ||
|
|
4be6406a1e |
@@ -1,5 +1,17 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 0.11.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1f9720fa: fix(dashboard): apply select permissions properly
|
||||
|
||||
## 0.11.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- deb14b51: fix(dashboard): don't break billing form
|
||||
|
||||
## 0.11.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "0.11.3",
|
||||
"version": "0.11.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -24,7 +24,7 @@ export function CountrySelector({ value, onChange }: CountrySelectorProps) {
|
||||
placeholder="Select Country"
|
||||
>
|
||||
{countries?.map((country) => (
|
||||
<Option key={country.name} value={country.name}>
|
||||
<Option key={country.name} value={country.code}>
|
||||
{country.name}
|
||||
</Option>
|
||||
))}
|
||||
|
||||
@@ -181,6 +181,7 @@ export default function EditPermissionsForm({
|
||||
|
||||
return (
|
||||
<RolePermissionEditorForm
|
||||
resourceVersion={metadata?.resourceVersion}
|
||||
disabled={disabled}
|
||||
schema={schema}
|
||||
table={table}
|
||||
@@ -203,7 +204,7 @@ export default function EditPermissionsForm({
|
||||
sx={{ backgroundColor: 'background.default' }}
|
||||
>
|
||||
<div className="flex-auto">
|
||||
<Box className="grid grid-flow-row gap-6 content-start overflow-y-auto p-6 border-b-1">
|
||||
<Box className="grid grid-flow-row content-start gap-6 overflow-y-auto border-b-1 p-6">
|
||||
<div className="grid grid-flow-row gap-2">
|
||||
<Text component="h2" className="!font-bold">
|
||||
Roles & Actions overview
|
||||
@@ -215,24 +216,24 @@ export default function EditPermissionsForm({
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-flow-col gap-4 items-center justify-start">
|
||||
<div className="grid grid-flow-col items-center justify-start gap-4">
|
||||
<Text
|
||||
variant="subtitle2"
|
||||
className="grid items-center grid-flow-col gap-1"
|
||||
className="grid grid-flow-col items-center gap-1"
|
||||
>
|
||||
full access <FullPermissionIcon />
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
variant="subtitle2"
|
||||
className="grid items-center grid-flow-col gap-1"
|
||||
className="grid grid-flow-col items-center gap-1"
|
||||
>
|
||||
partial access <PartialPermissionIcon />
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
variant="subtitle2"
|
||||
className="grid items-center grid-flow-col gap-1"
|
||||
className="grid grid-flow-col items-center gap-1"
|
||||
>
|
||||
no access <NoPermissionIcon />
|
||||
</Text>
|
||||
@@ -262,7 +263,7 @@ export default function EditPermissionsForm({
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody className="rounded-sm+ block border-1">
|
||||
<TableBody className="block rounded-sm+ border-1">
|
||||
<RolePermissionsRow
|
||||
name="admin"
|
||||
disabled
|
||||
|
||||
@@ -66,6 +66,10 @@ export interface RolePermissionEditorFormValues {
|
||||
* Whether the mutation should be restricted to trusted backends.
|
||||
*/
|
||||
backendOnly?: boolean;
|
||||
/**
|
||||
* Computed fields to be allowed for the role.
|
||||
*/
|
||||
computedFields?: string[];
|
||||
}
|
||||
|
||||
export interface RolePermissionEditorFormProps {
|
||||
@@ -85,6 +89,10 @@ export interface RolePermissionEditorFormProps {
|
||||
* The role that is being edited.
|
||||
*/
|
||||
role: string;
|
||||
/**
|
||||
* The resource version of the metadata.
|
||||
*/
|
||||
resourceVersion: number;
|
||||
/**
|
||||
* The action that is being edited.
|
||||
*/
|
||||
@@ -155,6 +163,7 @@ export default function RolePermissionEditorForm({
|
||||
schema,
|
||||
table,
|
||||
role,
|
||||
resourceVersion,
|
||||
action,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
@@ -189,6 +198,7 @@ export default function RolePermissionEditorForm({
|
||||
permission?.subscription_root_fields?.length > 0,
|
||||
queryRootFields: permission?.query_root_fields || [],
|
||||
subscriptionRootFields: permission?.subscription_root_fields || [],
|
||||
computedFields: permission?.computed_fields || [],
|
||||
columnPresets: getColumnPresets(permission?.set || {}),
|
||||
backendOnly: permission?.backend_only || false,
|
||||
},
|
||||
@@ -213,13 +223,18 @@ export default function RolePermissionEditorForm({
|
||||
action,
|
||||
mode: permission ? 'update' : 'insert',
|
||||
originalPermission: permission,
|
||||
resourceVersion,
|
||||
permission: {
|
||||
set: convertToColumnPresetObject(values.columnPresets),
|
||||
columns: values.columns,
|
||||
limit: values.limit,
|
||||
allow_aggregations: values.allowAggregations,
|
||||
query_root_fields: values.queryRootFields,
|
||||
subscription_root_fields: values.subscriptionRootFields,
|
||||
query_root_fields:
|
||||
values.queryRootFields.length > 0 ? values.queryRootFields : null,
|
||||
subscription_root_fields:
|
||||
values.subscriptionRootFields.length > 0
|
||||
? values.subscriptionRootFields
|
||||
: null,
|
||||
filter:
|
||||
action !== 'insert'
|
||||
? convertToHasuraPermissions(values.filter as RuleGroup)
|
||||
@@ -229,6 +244,10 @@ export default function RolePermissionEditorForm({
|
||||
? convertToHasuraPermissions(values.filter as RuleGroup)
|
||||
: permission?.check,
|
||||
backend_only: values.backendOnly,
|
||||
computed_fields:
|
||||
permission?.computed_fields.length > 0
|
||||
? permission?.computed_fields
|
||||
: null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -266,6 +285,7 @@ export default function RolePermissionEditorForm({
|
||||
const deletePermissionPromise = managePermission({
|
||||
role,
|
||||
action,
|
||||
resourceVersion,
|
||||
originalPermission: permission,
|
||||
mode: 'delete',
|
||||
});
|
||||
@@ -331,10 +351,10 @@ export default function RolePermissionEditorForm({
|
||||
className="flex flex-auto flex-col content-between overflow-hidden border-t-1"
|
||||
sx={{ backgroundColor: 'background.default' }}
|
||||
>
|
||||
<div className="grid grid-flow-row gap-6 content-start flex-auto py-4 overflow-auto">
|
||||
<div className="grid flex-auto grid-flow-row content-start gap-6 overflow-auto py-4">
|
||||
<PermissionSettingsSection
|
||||
title="Selected role & action"
|
||||
className="justify-between grid-flow-col"
|
||||
className="grid-flow-col justify-between"
|
||||
>
|
||||
<div className="grid grid-flow-col gap-4">
|
||||
<Text>
|
||||
@@ -387,7 +407,7 @@ export default function RolePermissionEditorForm({
|
||||
{action !== 'select' && <BackendOnlySection disabled={disabled} />}
|
||||
</div>
|
||||
|
||||
<Box className="grid flex-shrink-0 sm:grid-flow-col sm:justify-between gap-2 border-t-1 p-2">
|
||||
<Box className="grid flex-shrink-0 gap-2 border-t-1 p-2 sm:grid-flow-col sm:justify-between">
|
||||
<Button
|
||||
variant="borderless"
|
||||
color="secondary"
|
||||
@@ -398,7 +418,7 @@ export default function RolePermissionEditorForm({
|
||||
</Button>
|
||||
|
||||
{!disabled && (
|
||||
<Box className="grid grid-flow-row sm:grid-flow-col gap-2">
|
||||
<Box className="grid grid-flow-row gap-2 sm:grid-flow-col">
|
||||
{Boolean(permission) && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
|
||||
@@ -11,7 +11,6 @@ import { inputClasses } from '@/ui/v2/Input';
|
||||
import Option from '@/ui/v2/Option';
|
||||
import getPermissionVariablesArray from '@/utils/settings/getPermissionVariablesArray';
|
||||
import { useGetAppCustomClaimsQuery } from '@/utils/__generated__/graphql';
|
||||
import clsx from 'clsx';
|
||||
import { useController, useFormContext, useWatch } from 'react-hook-form';
|
||||
import useRuleGroupEditor from './useRuleGroupEditor';
|
||||
|
||||
@@ -214,7 +213,7 @@ export default function RuleValueInput({
|
||||
freeSolo={!isHasuraInput}
|
||||
autoSelect={!isHasuraInput}
|
||||
autoHighlight={isHasuraInput}
|
||||
filterSelectedOptions
|
||||
open
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
if (typeof value === 'string') {
|
||||
return option.value.toLowerCase() === (value as string).toLowerCase();
|
||||
@@ -230,7 +229,7 @@ export default function RuleValueInput({
|
||||
sx: sharedInputSx,
|
||||
},
|
||||
formControl: { className: '!bg-transparent' },
|
||||
paper: { className: clsx(!isHasuraInput && 'hidden') },
|
||||
paper: { className: 'empty:border-transparent' },
|
||||
}}
|
||||
fullWidth
|
||||
loading={loading}
|
||||
|
||||
@@ -17,6 +17,10 @@ export interface ManagePermissionVariables {
|
||||
* The action to manage the permission for.
|
||||
*/
|
||||
action: DatabaseAction;
|
||||
/**
|
||||
* The resource version of the metadata.
|
||||
*/
|
||||
resourceVersion: number;
|
||||
/**
|
||||
* Permission to insert or update.
|
||||
*/
|
||||
@@ -45,6 +49,7 @@ export default async function managePermission({
|
||||
permission,
|
||||
role,
|
||||
action,
|
||||
resourceVersion,
|
||||
mode = 'update',
|
||||
}: ManagePermissionOptions & ManagePermissionVariables) {
|
||||
if (mode !== 'delete' && !permission) {
|
||||
@@ -87,7 +92,7 @@ export default async function managePermission({
|
||||
args,
|
||||
type: 'bulk',
|
||||
source: dataSource,
|
||||
version: 1,
|
||||
resource_version: resourceVersion,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ export interface ManagePermissionMigrationVariables {
|
||||
* The action to manage the permission for.
|
||||
*/
|
||||
action: DatabaseAction;
|
||||
/**
|
||||
* The resource version of the metadata.
|
||||
*/
|
||||
resourceVersion: number;
|
||||
/**
|
||||
* Permission to insert or update.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,12 @@ import fetch from 'cross-fetch';
|
||||
|
||||
export interface FetchMetadataOptions
|
||||
extends Omit<MutationOrQueryBaseOptions, 'schema' | 'table'> {}
|
||||
export interface FetchMetadataReturnType extends HasuraMetadataSource {}
|
||||
export interface FetchMetadataReturnType extends Partial<HasuraMetadataSource> {
|
||||
/**
|
||||
* The resource version of the metadata.
|
||||
*/
|
||||
resourceVersion: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Hasura metadata using the Metadata API.
|
||||
@@ -33,8 +38,9 @@ export default async function fetchMetadata({
|
||||
}),
|
||||
});
|
||||
|
||||
const responseData: Record<string, HasuraMetadata> | QueryError =
|
||||
await response.json();
|
||||
const responseData:
|
||||
| { metadata: HasuraMetadata; resource_version: number }
|
||||
| QueryError = await response.json();
|
||||
|
||||
if (!response.ok || 'error' in responseData) {
|
||||
if ('internal' in responseData) {
|
||||
@@ -48,9 +54,11 @@ export default async function fetchMetadata({
|
||||
}
|
||||
}
|
||||
|
||||
const { metadata } = responseData;
|
||||
const { metadata, resource_version: resourceVersion } = responseData;
|
||||
const currentSource =
|
||||
metadata?.sources?.find((source) => source.name === dataSource) || null;
|
||||
|
||||
return (
|
||||
metadata?.sources?.find((source) => source.name === dataSource) || null
|
||||
);
|
||||
return currentSource
|
||||
? { ...currentSource, resourceVersion }
|
||||
: { resourceVersion };
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface HasuraMetadataPermission {
|
||||
allow_aggregations: boolean;
|
||||
query_root_fields: string[];
|
||||
subscription_root_fields: string[];
|
||||
computed_fields: string[];
|
||||
set: Record<string, any>;
|
||||
backend_only: boolean;
|
||||
}>;
|
||||
|
||||
@@ -5,6 +5,9 @@ sidebar_label: 'Overview'
|
||||
image: /img/og/graphql.png
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
A GraphQL API is automatically and instantly available based on the tables and columns in your [database](/database).
|
||||
|
||||
The GraphQL API has support for inserting, selecting, updating, and deleting data, which usually accounts for 80% of all API operations you need.
|
||||
@@ -21,11 +24,11 @@ Building your GraphQL API is a lot of work, but with Nhost it's easy because eve
|
||||
|
||||
## Endpoint
|
||||
|
||||
The GraphQL API is available at `https://[subdomain].nhost.run/v1/graphql` When using the [CLI](/cli) the GraphQL API is available at `http://localhost:1337/v1/graphql`.
|
||||
The GraphQL API is available at `https://[subdomain].graphql.[region].nhost.run/v1` When using the [CLI](/cli) the GraphQL API is available at `http://localhost:1337/v1/graphql`.
|
||||
|
||||
## GraphQL Clients for JavaScript
|
||||
|
||||
The Nhost JavaScript client comes with a simple [GraphQL client](/reference/javascript/nhost-js/graphql) that works well for the backend or simple applications.
|
||||
The [Nhost JavaScript client](/reference/javascript) comes with a simple [GraphQL client](/reference/javascript/nhost-js/graphql) that works well for the backend or simple applications.
|
||||
|
||||
When building more complex frontend applications, we recommend using a more advanced GraphQL client such as:
|
||||
|
||||
@@ -38,7 +41,7 @@ When building more complex frontend applications, we recommend using a more adva
|
||||
|
||||
## Queries
|
||||
|
||||
A GraphQL query is used to fetch data from the database.
|
||||
A query is used to fetch data from the GraphQL API.
|
||||
|
||||
:::tip
|
||||
The [Queries documentation from Hasura](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/index/) is applicable since we're using Hasura's GraphQL Engine for your project.
|
||||
@@ -46,7 +49,10 @@ The [Queries documentation from Hasura](https://hasura.io/docs/latest/graphql/co
|
||||
|
||||
**Example:** A GraphQL query to select `title`, `body`, and `isCompleted` for every row in the `todos` table.
|
||||
|
||||
```graphql title=GraphQL
|
||||
<Tabs >
|
||||
<TabItem value="request" label="Request" default>
|
||||
|
||||
```graphql
|
||||
query GetTodos {
|
||||
todos {
|
||||
title
|
||||
@@ -56,9 +62,10 @@ query GetTodos {
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
</TabItem>
|
||||
<TabItem value="response" label="Response">
|
||||
|
||||
```json title=Response
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"todos": [
|
||||
@@ -72,13 +79,19 @@ query GetTodos {
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Filtering and sorting
|
||||
|
||||
More complex queries utilize filters, limits, sorting, and aggregation.
|
||||
|
||||
Here's an example of a more complex GraphQL query that selects all items in the `todos` table that are not completed, with the total number of comments and the last five comments:
|
||||
|
||||
```graphql title=GraphQL
|
||||
<Tabs >
|
||||
<TabItem value="request" label="Request" default>
|
||||
|
||||
```graphql
|
||||
query GetTodosWithLatestComments {
|
||||
todos(where: { isCompleted: { _eq: false } }) {
|
||||
title
|
||||
@@ -97,7 +110,10 @@ query GetTodosWithLatestComments {
|
||||
}
|
||||
```
|
||||
|
||||
```json title=Response
|
||||
</TabItem>
|
||||
<TabItem value="response" label="Response">
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"todos": [
|
||||
@@ -127,6 +143,9 @@ query GetTodosWithLatestComments {
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Mutations
|
||||
|
||||
A GraphQL mutation is used to insert, upsert, update, or delete data.
|
||||
@@ -139,7 +158,10 @@ The [Mutations documentation from Hasura](https://hasura.io/docs/latest/graphql/
|
||||
|
||||
**Example:** A GraphQL mutation to insert data:
|
||||
|
||||
```graphql title=GraphQL
|
||||
<Tabs >
|
||||
<TabItem value="request" label="Request" default>
|
||||
|
||||
```graphql
|
||||
mutation InsertTodo {
|
||||
insert_todos(
|
||||
objects: [{ title: "Delete Firebase account", body: "Migrate to Nhost", isCompleted: false }]
|
||||
@@ -154,7 +176,10 @@ mutation InsertTodo {
|
||||
}
|
||||
```
|
||||
|
||||
```json title=Reponse
|
||||
</TabItem>
|
||||
<TabItem value="response" label="Response">
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"insert_todos": [
|
||||
@@ -169,6 +194,9 @@ mutation InsertTodo {
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Insert Multiple Rows
|
||||
|
||||
Use an array of objects to insert multiple rows at the same time.
|
||||
@@ -303,9 +331,7 @@ mutation DeleteDoneTodos {
|
||||
}
|
||||
```
|
||||
|
||||
If you have set up foreign keys which will restrict a delete violation, you will get an error and will not be able to delete the data until all violations are solved. The simplest way to solve this is by set `On Delete Violation` to `CASCADE` when you set up a foreign Key.
|
||||
|
||||
---
|
||||
If you have set up foreign keys which will restrict a delete violation, you will get an error and will not be able to delete the data until all violations are solved. The simplest way to solve this is by set `On Delete Violation` to `CASCADE` when you set up a foreign key.
|
||||
|
||||
## Subscriptions
|
||||
|
||||
|
||||
@@ -8,35 +8,19 @@ The GraphQL API is protected by a role-based permission system.
|
||||
|
||||
For each **role**, you create **rules** for the **select**, **insert**, **update**, and **delete** operations.
|
||||
|
||||
**Example:** Let's say you have a `posts` table, and you want users to only access their own posts. This is how you would do it:
|
||||
**Example:** Let's say you have a `posts` table with `id` `title` and `user_id` columns, and you want users to only access their own posts. This is how you would do it:
|
||||
|
||||
```sql title="Posts Table"
|
||||
CREATE TABLE "public"."posts" (
|
||||
"id" serial NOT NULL PRIMARY KEY,
|
||||
"title" text NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
);
|
||||
```
|
||||

|
||||
|
||||
```json title="Hasura Permission Rule"
|
||||
{
|
||||
"user_id": {
|
||||
"_eq": "X-Hasura-User-Id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The rule above make it so users can only select posts where the value of `user_id` is equal (`_eq`) to their user ID (`x-hasura-user-id`).
|
||||
The permission above makes sure users can only select their own posts, because the value of `user_id` must be equal (`_eq`) to the authenticated user's ID (`x-hasura-user-id`).
|
||||
|
||||
## What is `x-hasura-user-id`?
|
||||
|
||||
`x-hasura-user-id` is a permission variable that is used to create permission rules in Hasura. The permission variable comes from the [access token](/authentication/tokens#access-token) that authenticated users have.
|
||||
|
||||
The `x-hasura-user-id` permission variable is always available for all authenticated users. You can add [permission variables](#permission-variables) to create more complex permission rules unique to your project.
|
||||
`x-hasura-user-id` is a permission variable that is used to create permission rules. The permission variable comes from the [access token](/authentication/tokens#access-token) that all authenticated users have. You can add [custom permission variables](#permission-variables) to create more complex permission rules unique to your project.
|
||||
|
||||
## Permission Variables
|
||||
|
||||
You can add permission variables in the Nhost Dashboard under **Settings -> Roles and permissions**. These permission variables are automatically added to users' [access tokens](/authentication/tokens#access-token). This way, permission variables are available when creating permissions for your GraphQL API in the Hasura Console.
|
||||
You can add permission variables in the Nhost Dashboard under **Settings -> Roles and permissions -> Permission Variables**. These permission variables are automatically added to users' [access tokens](/authentication/tokens#access-token). This way, permission variables are available when creating permissions for your GraphQL API.
|
||||
|
||||

|
||||
|
||||
@@ -137,37 +121,28 @@ GraphQL requests from unauthenticated users resolve permissions using the `publi
|
||||
Here is a popular approach for insert permission for authenticated users.
|
||||
|
||||
1. At the top of the page, click **"insert"** on the **"user"** role.
|
||||
2. Select **"Without any checks"**.
|
||||
3. Select the columns you want to allow users to insert.
|
||||
|
||||
In our example, we only mark `title`, because the other columns should not be inserted by the user.
|
||||
|
||||
We also want every new record's `user_id` value to be set to the ID of the user making the request. We can tell Hasura to do this using **Column presets**.
|
||||
|
||||
4. Under **Column presets**, set `user_id` to `x-hasura-user-id`.
|
||||
1. Select **"Without any checks"**.
|
||||
1. Select the columns you want to allow users to insert. In our example, we only mark `title`, because that's the only column that should be inserted by the user. The `id` is automatically generated by the database and `user_id` is set using a column preset.
|
||||
1. Under **Column presets**, set `user_id` to `x-hasura-user-id`. This way, every new record's `user_id` value is set to the ID of the user making the request.
|
||||
|
||||
Now, authenticated users are allowed to insert posts. Users are allowed to add a title when inserting a post. The post's `id` is automatically generated by the database and the `user_id` is automatically set to the user's id using the `user_id = x-hasura-user-id` column preset.
|
||||
|
||||
## Select, Update and Delete Permissions
|
||||
|
||||
Select, update, and delete permissions usually follows the same pattern. Here's an example of how to add select permissions:
|
||||
Select, update, and delete permissions usually follow the same pattern. Here's an example of how to add select permissions:
|
||||
|
||||

|
||||

|
||||
|
||||
One of the most common permission requirements is that authenticated users should only be able to read their own data. This is how to do that:
|
||||
|
||||
1. Go to **Hasura Console**
|
||||
1. Select your table and open the **Permissions** tab
|
||||
1. At the top of the page, click **"select"** on the **"user"** role.
|
||||
1. Go to the **Database** section in the Nhost Dashboard.
|
||||
1. In the context menu of the table you want to edit, click on **Edit Permissions**.
|
||||
1. Click on the **role** and **operation** you want to set.
|
||||
1. Select **"With custom check"** to create a new rule
|
||||
1. Enter `user_id`, `_eq` and `x-hasura-user-id` into the rule form.
|
||||
|
||||
This means that in order for users to read data, the user ID value in the database row must be the same as the user ID in the access token.
|
||||
|
||||
To further refine this rule, do the following:
|
||||
|
||||
1. Limit the number of rows to 100 (or some other relevant number).
|
||||
1. Select the columns you want the user to be able to read. In our case, we'll allow the user to read all columns.
|
||||
1. Enter `user_id`, `_eq` and `x-hasura-user-id` into the rule form. This means that in order for users to read data, the user ID value in the database row must be the same as the user ID in the access token.
|
||||
1. **Limit the number of rows** to 100 (or some other relevant number).
|
||||
1. Select the **columns** you want the user to be able to read. In our case, we'll allow the user to read all columns.
|
||||
1. Click **Save**.
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
BIN
docs/static/img/graphql/permissions/dashboard-menu.png
vendored
Normal file
BIN
docs/static/img/graphql/permissions/dashboard-menu.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 978 KiB |
BIN
docs/static/img/graphql/permissions/permission-example.png
vendored
Normal file
BIN
docs/static/img/graphql/permissions/permission-example.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 958 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 967 KiB |
@@ -33,7 +33,7 @@ export interface MultipleFilesHookResult extends MultipleFilesUploadState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the hook `useFileUpload` to upload multiple files.
|
||||
* Use the hook `useMultipleFilesUpload` to upload multiple files.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
|
||||
Reference in New Issue
Block a user