Compare commits

..

25 Commits

Author SHA1 Message Date
github-actions[bot]
f33e07b191 chore: update versions (#2538)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/hasura-auth-js@2.3.0

### Minor Changes

-   017f1a6: feat: add elevated permission examples

## @nhost/react@3.2.0

### Minor Changes

-   017f1a6: feat: add elevated permission examples

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost/vue@2.2.0

### Minor Changes

-   017f1a6: feat: add elevated permission examples

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost/apollo@6.0.5

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost/react-apollo@9.0.0

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0
    -   @nhost/apollo@6.0.5

## @nhost/react-urql@6.0.0

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0

## @nhost/nextjs@2.1.2

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0

## @nhost/nhost-js@3.0.5

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/hasura-auth-js@2.3.0

## @nhost-examples/react-apollo@0.3.0

### Minor Changes

-   017f1a6: feat: add elevated permission examples

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0
    -   @nhost/react-apollo@9.0.0

## @nhost-examples/vue-apollo@0.2.0

### Minor Changes

-   017f1a6: feat: add elevated permission examples

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/vue@2.2.0
    -   @nhost/nhost-js@3.0.5
    -   @nhost/apollo@6.0.5

## @nhost/dashboard@1.6.9

### Patch Changes

-   @nhost/react-apollo@9.0.0
-   @nhost/nextjs@2.1.2

## @nhost-examples/cli@0.1.6

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost-examples/codegen-react-apollo@0.1.14

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0
    -   @nhost/react-apollo@9.0.0

## @nhost-examples/codegen-react-query@0.1.15

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0

## @nhost-examples/codegen-react-urql@0.0.11

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0
    -   @nhost/react-urql@6.0.0

## @nhost-examples/multi-tenant-one-to-many@2.0.4

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost-examples/nextjs@0.1.16

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0
    -   @nhost/react-apollo@9.0.0
    -   @nhost/nextjs@2.1.2

## @nhost-examples/node-storage@0.0.8

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost-examples/nextjs-server-components@0.2.2

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost-examples/react-gqty@1.0.4

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0

## @nhost-examples/vue-quickstart@0.0.13

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/vue@2.2.0
    -   @nhost/apollo@6.0.5

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-14 15:12:34 +01:00
Hassan Ben Jobrane
017f1a6c7b feat: add elevate workflow to react-apollo and vue-apollo example projects (#2521)
part-2 of https://github.com/nhost/nhost/issues/2394

---------

Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-02-14 14:52:43 +01:00
github-actions[bot]
93957c8af3 chore: update versions (#2537)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/hasura-storage-js@2.4.0

### Minor Changes

-   2505b2e: fix: fix headers sent with getPresignedUrl

## @nhost/apollo@6.0.4

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost/react-apollo@8.0.1

### Patch Changes

-   @nhost/apollo@6.0.4
-   @nhost/react@3.1.1

## @nhost/react-urql@5.0.1

### Patch Changes

-   @nhost/react@3.1.1

## @nhost/nextjs@2.1.1

### Patch Changes

-   @nhost/react@3.1.1

## @nhost/nhost-js@3.0.4

### Patch Changes

-   Updated dependencies [2505b2e]
    -   @nhost/hasura-storage-js@2.4.0

## @nhost/react@3.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost/vue@2.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost/dashboard@1.6.8

### Patch Changes

-   @nhost/react-apollo@8.0.1
-   @nhost/nextjs@2.1.1

## @nhost-examples/cli@0.1.5

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost-examples/codegen-react-apollo@0.1.13

### Patch Changes

-   @nhost/react@3.1.1
-   @nhost/react-apollo@8.0.1

## @nhost-examples/codegen-react-query@0.1.14

### Patch Changes

-   @nhost/react@3.1.1

## @nhost-examples/codegen-react-urql@0.0.10

### Patch Changes

-   @nhost/react@3.1.1
-   @nhost/react-urql@5.0.1

## @nhost-examples/multi-tenant-one-to-many@2.0.3

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost-examples/nextjs@0.1.15

### Patch Changes

-   @nhost/react@3.1.1
-   @nhost/react-apollo@8.0.1
-   @nhost/nextjs@2.1.1

## @nhost-examples/node-storage@0.0.7

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost-examples/nextjs-server-components@0.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost-examples/react-apollo@0.2.1

### Patch Changes

-   @nhost/react@3.1.1
-   @nhost/react-apollo@8.0.1

## @nhost-examples/react-gqty@1.0.3

### Patch Changes

-   @nhost/react@3.1.1

## @nhost-examples/vue-apollo@0.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.4
-   @nhost/apollo@6.0.4
-   @nhost/vue@2.1.1

## @nhost-examples/vue-quickstart@0.0.12

### Patch Changes

-   @nhost/apollo@6.0.4
-   @nhost/vue@2.1.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-14 10:29:57 +01:00
Nuno Pato
2505b2e26b fix: fix headers sent with getPresignedUrl (#2535) 2024-02-13 23:33:09 -01:00
Nuno Pato
5f4b4d2acc chore: update dependencies (#2536)
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-02-13 20:29:47 -01:00
github-actions[bot]
71a8ce4446 chore: update versions (#2524)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/docs@2.4.0

### Minor Changes

-   791b729: fix: remove auth method

## @nhost/dashboard@1.6.7

### Patch Changes

-   5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-12 16:33:16 +01:00
Hassan Ben Jobrane
5ef5189898 fix(dashboard): resolve change plan modal cache issue (#2532)
related to https://github.com/nhost/nhost/issues/2530
2024-02-12 16:08:22 +01:00
Nuno Pato
791b7295fb fix: docs: remove auth method (#2475)
- https://github.com/nhost/nhost/issues/2474
2024-02-08 10:36:51 -01:00
github-actions[bot]
25bc4b7fd6 chore: update versions (#2501)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/hasura-auth-js@2.2.0

### Minor Changes

- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with
integration into react-apollo and vue-apollo examples

## @nhost/hasura-storage-js@2.3.0

### Minor Changes

-   d3d1424: feat: Add support for authenticated download of files

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost/nextjs@2.1.0

### Minor Changes

-   b19ffed: chore: update peerDependency to support nextjs 14

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0

## @nhost/react@3.1.0

### Minor Changes

- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with
integration into react-apollo and vue-apollo examples

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost/vue@2.1.0

### Minor Changes

- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with
integration into react-apollo and vue-apollo examples

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost/apollo@6.0.3

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost/google-translation@0.0.8

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost/react-apollo@8.0.0

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0
    -   @nhost/apollo@6.0.3

## @nhost/react-urql@5.0.0

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0

## @nhost/stripe-graphql-js@1.0.7

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost/nhost-js@3.0.3

### Patch Changes

-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
-   Updated dependencies [d3d1424]
    -   @nhost/hasura-auth-js@2.2.0
    -   @nhost/hasura-storage-js@2.3.0
    -   @nhost/graphql-js@0.1.5

## @nhost/docs@2.3.0

### Minor Changes

-   d3d1424: feat: Add support for authenticated download of files

### Patch Changes

-   e5bab6a: chore: update dependencies
- 2ae5ea8: fix: indicate that custom domains for postgres doesn't
require configuration

## @nhost-examples/nextjs-server-components@0.2.0

### Minor Changes

-   d3d1424: feat: Add support for authenticated download of files

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost-examples/react-apollo@0.2.0

### Minor Changes

- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with
integration into react-apollo and vue-apollo examples

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0
    -   @nhost/react-apollo@8.0.0

## @nhost-examples/vue-apollo@0.1.0

### Minor Changes

- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with
integration into react-apollo and vue-apollo examples

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/vue@2.1.0
    -   @nhost/apollo@6.0.3
    -   @nhost/nhost-js@3.0.3

## @nhost/dashboard@1.6.6

### Patch Changes

-   3ba485e: fix: added discord.com to connect-src
-   e5bab6a: chore: update dependencies
-   Updated dependencies [b19ffed]
-   Updated dependencies [e5bab6a]
    -   @nhost/nextjs@2.1.0
    -   @nhost/react-apollo@8.0.0

## @nhost-examples/cli@0.1.4

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost-examples/codegen-react-apollo@0.1.12

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0
    -   @nhost/react-apollo@8.0.0

## @nhost-examples/codegen-react-query@0.1.13

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0

## @nhost-examples/codegen-react-urql@0.0.9

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0
    -   @nhost/react-urql@5.0.0

## @nhost-examples/multi-tenant-one-to-many@2.0.2

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost-examples/nextjs@0.1.14

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [b19ffed]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0
    -   @nhost/nextjs@2.1.0
    -   @nhost/react-apollo@8.0.0

## @nhost-examples/node-storage@0.0.6

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost-examples/sveltekit@0.2.3

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost-examples/react-gqty@1.0.2

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0

## @nhost-examples/serverless-functions@0.0.11

### Patch Changes

-   8c34c69: chore: update nodemailer version to `6.9.9`
-   Updated dependencies [e5bab6a]
    -   @nhost/stripe-graphql-js@1.0.7

## @nhost-examples/vue-quickstart@0.0.11

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/vue@2.1.0
    -   @nhost/apollo@6.0.3

## @nhost/docgen@0.1.13

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost/sync-versions@0.0.10

### Patch Changes

-   e5bab6a: chore: update dependencies

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-02-06 14:41:47 +01:00
Hassan Ben Jobrane
da20159ec5 chore(examples): update auth version to 0.25.0 in nhost.toml (#2518) 2024-02-06 14:09:30 +01:00
David Barroso
2ae5ea8bc1 fix (docs): indicate that custom domains for postgres doesn't require configuration (#2506) 2024-02-06 11:54:18 +01:00
David Barroso
3ba485e582 fix (dashboard): added discord.com to connect-src (#2516) 2024-02-06 11:29:27 +01:00
David Barroso
e5bab6a061 chore: update dependencies (#2505)
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-02-06 10:25:48 +01:00
David Barroso
be64353145 Create SECURITY.md (#2515) 2024-02-06 10:00:38 +01:00
Hassan Ben Jobrane
2f5913c78d fix(examples): revert back auth version to 0.24.1 (#2512) 2024-02-05 17:18:52 +01:00
Nuno Pato
757ddd901c chore: docs: use hyphen instead of underscore (#2511) 2024-02-05 13:46:13 -01:00
Hassan Ben Jobrane
1a61c658a7 feat: add elevate func to the SDKs and the react-apollo example project (#2500)
part-1 of https://github.com/nhost/nhost/issues/2394
2024-02-05 13:25:14 +01:00
Nuno Pato
d3d14245c7 feat: hasura-storage-js: add authenticated download of files (#2507) 2024-02-05 11:24:34 -01:00
Alexander Mart
53d2f9d3e0 doc: fix method name in example code at refresh-session.mdx doc page (#2468)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-02-03 15:54:08 +01:00
Hassan Ben Jobrane
8c34c69e79 chore: update nodemailer (#2510) 2024-02-02 20:31:29 +01:00
Jared Prather
b19ffed273 chore: Update peerDependency next to ^14.0.0 (#2354)
Fixes: #2351 

Summary of next 14 changes
[here](https://nextjs.org/docs/pages/building-your-application/upgrading/version-14#v14-summary)

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-01-30 18:25:02 +01:00
Nuno Pato
859efa988a fix: docs: small fixes (#2486) 2024-01-26 17:15:06 -01:00
github-actions[bot]
3202b6b897 chore: update versions (#2495)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.6.5

### Patch Changes

- ba73bb4: fix: update ErrorToast component to show the internal graphql
error
- d5337ff: fix: utilize accumulator in the creation of validation schema
within data grid utils

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-26 18:32:57 +01:00
Hassan Ben Jobrane
ba73bb4003 fix(dashboard): show internal error in toast message (#2496) 2024-01-26 17:42:18 +01:00
Hassan Ben Jobrane
d5337ff5bd fix(dashboard): fix bug with validation schema for create record form (#2494) 2024-01-26 16:50:28 +01:00
173 changed files with 7387 additions and 5230 deletions

View File

@@ -0,0 +1,82 @@
---
name: "gen: update depenendencies"
on:
schedule:
- cron: '0 2 1 * *'
jobs:
run:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure aws
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-be
aws-region: eu-central-1
- uses: nixbuild/nix-quick-install-action@v26
with:
nix_version: 2.16.2
nix_conf: |
experimental-features = nix-command flakes
sandbox = false
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
substituters = https://cache.nixos.org/?priority=40 s3://nhost-nix-cache?region=eu-central-1&priority=50
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ${{ secrets.NIX_CACHE_PUB_KEY }}
- name: Cache nix store
uses: actions/cache@v4
with:
path: /nix
key: nix-update-deps-${{ hashFiles('flakes.nix', 'flake.lock') }}
- name: Update nix flakes
run: nix flake update
- name: Update dependencies
run: |
nix develop -c bash -c "
pnpm dedupe
pnpm update -r
pnpm dedupe
"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update dependencies
committer: GitHub <noreply@github.com>
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
signoff: false
branch: automated/update-deps
delete-branch: true
title: '[Scheduled] Update dependencies'
body: |
Dependencies updated
Note - If you see this PR and the checks haven't run, close and reopen the PR. See https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs
labels: |
dependencies
draft: false
- name: "Cache nix store on s3"
run: |
echo ${{ secrets.NIX_CACHE_PRIV_KEY }} > cache-priv-key.pem
nix build .\#devShells.x86_64-linux.default
nix store sign --key-file cache-priv-key.pem --all
nix copy --to s3://nhost-nix-cache\?region=eu-central-1 .\#devShells.x86_64-linux.default
- run: rm cache-priv-key.pem
if: always()

7
SECURITY.md Normal file
View File

@@ -0,0 +1,7 @@
# Security Policy
## Reporting a Vulnerability
At Nhost, we take security vulnerabilities seriously and appreciate the assistance of the community in bringing any issues to our attention. If you discover a security vulnerability, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/nhost/nhost/security/advisories/new) tab.
Once you have submitted the report, we will promptly conduct a thorough investigation within 72 hours. In case we need further information, we may contact you for additional details. Rest assured that addressing the reported vulnerability in a timely manner is our top priority.

View File

@@ -1,5 +1,43 @@
# @nhost/dashboard
## 1.6.9
### Patch Changes
- @nhost/react-apollo@9.0.0
- @nhost/nextjs@2.1.2
## 1.6.8
### Patch Changes
- @nhost/react-apollo@8.0.1
- @nhost/nextjs@2.1.1
## 1.6.7
### Patch Changes
- 5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug
## 1.6.6
### Patch Changes
- 3ba485e: fix: added discord.com to connect-src
- e5bab6a: chore: update dependencies
- Updated dependencies [b19ffed]
- Updated dependencies [e5bab6a]
- @nhost/nextjs@2.1.0
- @nhost/react-apollo@8.0.0
## 1.6.5
### Patch Changes
- ba73bb4: fix: update ErrorToast component to show the internal graphql error
- d5337ff: fix: utilize accumulator in the creation of validation schema within data grid utils
## 1.6.4
### Patch Changes

View File

@@ -4,10 +4,10 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
});
const { version } = require('./package.json');
const cspHeader = `
default-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run;
script-src 'self' 'unsafe-eval' 'unsafe-inline' cdn.segment.com js.stripe.com;
connect-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run discord.com;
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run;
font-src 'self' data:;
@@ -18,7 +18,7 @@ const cspHeader = `
frame-src 'self' js.stripe.com;
block-all-mixed-content;
upgrade-insecure-requests;
`
`;
module.exports = withBundleAnalyzer({
reactStrictMode: true,
@@ -40,7 +40,7 @@ module.exports = withBundleAnalyzer({
headers: [
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
value: 'SAMEORIGIN',
},
{
key: 'Content-Security-Policy',
@@ -48,7 +48,7 @@ module.exports = withBundleAnalyzer({
},
],
},
]
];
},
async redirects() {
return [

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "1.6.4",
"version": "1.6.9",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -19,7 +19,7 @@
"e2e": "pnpm install-browsers && pnpm playwright test"
},
"dependencies": {
"@apollo/client": "^3.8.9",
"@apollo/client": "^3.9.4",
"@codemirror/lang-sql": "^6.5.5",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.3",
@@ -33,8 +33,8 @@
"@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^3.3.4",
"@mui/base": "5.0.0-beta.31",
"@mui/material": "^5.15.4",
"@mui/system": "^5.15.4",
"@mui/material": "^5.15.7",
"@mui/system": "^5.15.7",
"@mui/x-date-pickers": "^5.0.20",
"@nhost/nextjs": "workspace:*",
"@nhost/react-apollo": "workspace:*",
@@ -43,24 +43,24 @@
"@stripe/stripe-js": "^1.54.2",
"@tailwindcss/forms": "^0.5.7",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.11.6",
"@tanstack/react-virtual": "^3.0.1",
"@tanstack/react-table": "^8.11.7",
"@tanstack/react-virtual": "^3.0.2",
"@uiw/codemirror-theme-github": "^4.21.21",
"@uiw/react-codemirror": "^4.21.21",
"analytics-node": "^6.2.0",
"bcryptjs": "^2.4.3",
"clsx": "^1.2.1",
"date-fns": "^2.30.0",
"framer-motion": "^10.18.0",
"generate-password": "^1.7.1",
"graphiql": "^3.1.0",
"graphql": "16.8.1",
"graphql-request": "^6.1.0",
"framer-motion": "^10.17.9",
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.14.3",
"just-kebab-case": "^4.2.0",
"lodash.debounce": "^4.0.8",
"next": "^14.0.4",
"next": "^14.1.0",
"next-seo": "^6.4.0",
"node-pg-format": "^1.3.5",
"pluralize": "^8.0.0",
@@ -68,9 +68,9 @@
"react-children-utilities": "^2.10.0",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.49.3",
"react-hook-form": "^7.50.0",
"react-hot-toast": "^2.4.1",
"react-intersection-observer": "^9.5.3",
"react-intersection-observer": "^9.5.4",
"react-is": "18.2.0",
"react-loading-skeleton": "^2.2.0",
"react-markdown": "^9.0.1",
@@ -85,13 +85,13 @@
"slugify": "^1.6.6",
"stripe": "^10.17.0",
"tailwind-merge": "^1.14.0",
"utility-types": "^3.10.0",
"utility-types": "^3.11.0",
"validator": "^13.11.0",
"yup": "^1.3.3",
"yup-password": "^0.2.2"
},
"devDependencies": {
"@babel/core": "^7.23.7",
"@babel/core": "^7.23.9",
"@faker-js/faker": "^7.6.0",
"@graphql-codegen/cli": "^3.3.1",
"@graphql-codegen/typescript": "^3.0.4",
@@ -106,34 +106,34 @@
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "^6.5.16",
"@storybook/manager-webpack5": "^6.5.16",
"@storybook/react": "^6.5.16",
"@storybook/react": "^7.6.15",
"@storybook/testing-library": "^0.2.2",
"@tailwindcss/typography": "^0.5.10",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.1.2",
"@testing-library/react": "^14.2.0",
"@testing-library/user-event": "^14.5.2",
"@types/ace": "^0.0.48",
"@types/bcryptjs": "^2.4.6",
"@types/jest": "^29.5.11",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^16.18.70",
"@types/node": "^16.18.78",
"@types/pluralize": "^0.0.30",
"@types/react": "^18.2.47",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.18",
"@types/react-table": "^7.7.19",
"@types/shell-quote": "^1.7.5",
"@types/testing-library__jest-dom": "^5.14.9",
"@types/validator": "^13.11.8",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^0.32.4",
"autoprefixer": "^10.4.16",
"autoprefixer": "^10.4.17",
"babel-loader": "^8.3.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"csstype": "^3.1.3",
"dotenv": "^16.3.1",
"dotenv": "^16.4.1",
"encoding": "^0.1.13",
"eslint": "^8.56.0",
"eslint-config-airbnb": "19.0.4",
@@ -145,7 +145,7 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^22.1.0",
"lint-staged": "^15.2.0",
"lint-staged": "^15.2.1",
"msw": "^1.3.2",
"msw-storybook-addon": "^1.10.0",
"node-fetch": "^3.3.2",
@@ -161,7 +161,7 @@
"ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"vite": "^5.0.12",
"vite-tsconfig-paths": "^4.2.3",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "^0.32.4"
},
"browserslist": {

View File

@@ -46,7 +46,16 @@ export default function ErrorToast({
error,
};
const msg = error?.graphQLErrors?.at(0)?.message || errorMessage;
const internalError = error?.graphQLErrors?.at(0)?.extensions?.internal as {
error: {
message: string;
};
};
const msg =
internalError?.error?.message ||
error?.graphQLErrors?.at(0).message ||
errorMessage;
return (
<AnimatePresence>

View File

@@ -114,7 +114,7 @@ export function createDynamicValidationSchema(
['time', 'timetz', 'interval'].includes(column.specificType)
) {
return {
...schema,
...currentSchema,
[column.id]: createTextValidationSchema(details).matches(
/^\d{2}:\d{2}(:\d{2})?$/,
'This is not a valid time (e.g: HH:MM:SS / HH:MM).',
@@ -124,14 +124,14 @@ export function createDynamicValidationSchema(
if (column.type === 'date') {
return {
...schema,
...currentSchema,
[column.id]: createDateValidationSchema(details),
};
}
if (column.type === 'boolean') {
return {
...schema,
...currentSchema,
[column.id]: createBooleanValidationSchema(details),
};
}
@@ -141,13 +141,13 @@ export function createDynamicValidationSchema(
(column.specificType === 'jsonb' || column.specificType === 'json')
) {
return {
...schema,
...currentSchema,
[column.id]: createJSONValidationSchema(details),
};
}
return {
...schema,
...currentSchema,
[column.id]: createTextValidationSchema(details),
};
}, {});

View File

@@ -25,12 +25,12 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
return (
<button
type="button"
className="my-4 grid w-full grid-flow-col items-center justify-between gap-2 px-1"
className="grid items-center justify-between w-full grid-flow-col gap-2 px-1 my-4"
onClick={setPlan}
tabIndex={-1}
>
<div className="grid grid-flow-row gap-y-0.5">
<div className="grid grid-flow-col items-center justify-start gap-2">
<div className="grid items-center justify-start grid-flow-col gap-2">
<Checkbox
onChange={setPlan}
checked={selectedPlanId === planId}
@@ -40,7 +40,7 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
<Text
variant="h3"
component="p"
className="self-center text-left font-medium"
className="self-center font-medium text-left"
>
Upgrade to {planName}
</Text>
@@ -156,7 +156,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
if (pollingCurrentProject) {
return (
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
<Box className="w-full max-w-xl p-6 mx-auto text-left rounded-lg">
<div className="flex flex-col">
<div className="mx-auto">
<Image
@@ -179,7 +179,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
<Button
variant="outlined"
color="secondary"
className="mx-auto mt-4 w-full max-w-sm"
className="w-full max-w-sm mx-auto mt-4"
onClick={() => {
if (close) {
close();
@@ -196,7 +196,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
}
return (
<Box className="w-full max-w-xl rounded-lg p-6 text-left">
<Box className="w-full max-w-xl p-6 text-left rounded-lg">
<BaseDialog
open={showPaymentModal}
onClose={() => setShowPaymentModal(false)}
@@ -241,7 +241,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
))}
</div>
<div className="mt-2 grid grid-flow-row gap-2">
<div className="grid grid-flow-row gap-2 mt-2">
<Button
onClick={handleChangePlanClick}
disabled={!selectedPlan}

View File

@@ -1,5 +1,22 @@
# @nhost/docs
## 2.4.0
### Minor Changes
- 791b729: fix: remove auth method
## 2.3.0
### Minor Changes
- d3d1424: feat: Add support for authenticated download of files
### Patch Changes
- e5bab6a: chore: update dependencies
- 2ae5ea8: fix: indicate that custom domains for postgres doesn't require configuration
## 2.2.0
### Minor Changes

View File

@@ -14,14 +14,14 @@ If you are using the Nhost CLI for local development, as of [v0.12.0](https://gi
<Step title="Start nhost">
Run `nhost up`:
![nhost up](/images/guides/ai/local_development/nhost_up.png)
![nhost up](/images/guides/ai/local-development/nhost_up.png)
After starting the service the first thing you will notice is that there is a new `ai` service running.
</Step>
<Step title="Commit metadata changes">
As you start the AI service metadata changes may be proposed:
![git status](/images/guides/ai/local_development/git_status.png)
![git status](/images/guides/ai/local-development/git_status.png)
We strongly recommmend you to commit them to your git repository so they can be deployed alongside your application.
</Step>
@@ -32,11 +32,11 @@ If you are using the Nhost CLI for local development, as of [v0.12.0](https://gi
If you add [auto-embeddings](/guides/ai/auto-embeddings) configuration locally and want to synchronize them with the cloud we recommend inserting them using a migration rather than with the auto-embeddings UI:
![migration](/images/guides/ai/local_development/migration.png)
![migration](/images/guides/ai/local-development/migration.png)
And then running `nhost up` to download the updated metadata. Afterwards you should see both database migrations and functions' metadata changes in your local project:
![git status](/images/guides/ai/local_development/git_status_functions.png)
![git status](/images/guides/ai/local-development/git_status_functions.png)
Pushing them to your deployment branch will also deploy them to your cloud project.

View File

@@ -1,6 +1,6 @@
---
title: Configuration Overlays
description: description
description: Learn how to use Configuration Overlays
icon: circles-overlap
---

View File

@@ -1,6 +1,6 @@
---
title: Local Development
description: description
description: Learn how to start a development instance of Nhost
icon: code
---

View File

@@ -1,6 +1,6 @@
---
title: Migrate Existing Projects
description: description
title: Migrate to Nhost Config
description: Learn how to migrate old projects to use the new configuration file
icon: arrow-down-to-bracket
---

View File

@@ -1,6 +1,6 @@
---
title: 'Running multiple projects at the same time'
description: 'description'
title: 'Running Multiple Projects'
description: Learn how to run multiple Nhost projects locally
icon: clone
---

View File

@@ -1,6 +1,6 @@
---
title: 'Seeds'
description: description
description: Learn about using seeds to populate your local database
icon: peapod
---

View File

@@ -1,6 +1,6 @@
---
title: CLI & CI Deployments
description: description
description: Learn how to deploy your Run services using the CLI
icon: rocket-launch
---

View File

@@ -1,6 +1,6 @@
---
title: Configuration
description: Configure
description: Learn how to configure a Run service
icon: gear
---

View File

@@ -1,6 +1,6 @@
---
title: Healthchecks
description: description
description: Learn how to enable healthchecks for your services
icon: heart-pulse
---

View File

@@ -1,6 +1,6 @@
---
title: Networking
description: descriptioon
description: Learn how networking works with Run
icon: network-wired
---

View File

@@ -1,6 +1,6 @@
---
title: Registry
description: registru
description: Learn how to use a private registry for your images
icon: box-archive
---

View File

@@ -1,6 +1,6 @@
---
title: Resources
description: description
description: Learn about compute resources
icon: gauge-max
---

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 916 KiB

After

Width:  |  Height:  |  Size: 916 KiB

View File

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 393 KiB

View File

@@ -102,7 +102,7 @@
"group": "AI",
"pages": [
"guides/ai/enabling-service",
"guides/ai/local_development",
"guides/ai/local-development",
"guides/ai/auto-embeddings",
"guides/ai/assistants",
"guides/ai/dev-assistant"
@@ -319,12 +319,13 @@
"group": "Storage",
"pages": [
"reference/javascript/storage/hasura-storage-client",
"reference/javascript/storage/delete",
"reference/javascript/storage/upload",
"reference/javascript/storage/download",
"reference/javascript/storage/get-presigned-url",
"reference/javascript/storage/get-public-url",
"reference/javascript/storage/delete",
"reference/javascript/storage/set-access-token",
"reference/javascript/storage/set-admin-secret",
"reference/javascript/storage/upload"
"reference/javascript/storage/set-admin-secret"
]
},
{
@@ -335,6 +336,14 @@
"reference/javascript/graphql/set-access-token",
"reference/javascript/graphql/request"
]
},
{
"group": "Functions ",
"pages": [
"reference/javascript/functions/create-functions-client",
"reference/javascript/functions/call",
"reference/javascript/functions/set-access-token"
]
}
]
},

View File

@@ -1,11 +1,11 @@
{
"name": "@nhost/docs",
"version": "2.2.0",
"version": "2.4.0",
"private": true,
"scripts": {
"start": "mintlify dev"
},
"devDependencies": {
"mintlify": "^4.0.112"
"mintlify": "^4.0.121"
}
}

View File

@@ -25,7 +25,7 @@ The following examples assume we are configuring custom domains at `*.custom-dom
<Tab title="Config File">
The first step is to add a CNAME record in your DNS provider for each of the services you want a custom domain for. You can find the instructions in the dashboard tab.
For Hasura, Auth, Functions, and PostgreSQL, custom domains are defined in the default `./nhost/config.toml` as follows:
For Hasura, Auth, and Functions, custom domains are defined in the default `./nhost/config.toml` as follows:
```toml
[[hasura.resources.networking.ingresses]]
@@ -34,14 +34,15 @@ fqdn = ['hasura.custom-domain.com']
[[auth.resources.networking.ingresses]]
fqdn = ['auth.custom-domain.com']
[[postgres.resources.networking.ingresses]]
fqdn = ['postgres.custom-domain.com']
[[functions.resources.networking.ingresses]]
fqdn = ['functions.custom-domain.com']
```
For Run services, typically in nhost-service.toml specific to the service:
For PostgreSQL there is nothing to configure on Nhost's side so as long as you have a CNAME properly configured it should work.
<Note>To connect to your backend using your custom domains instead of the subdomain and region you will have to pass the verious service URLs to the client. You can find the relevant parameters to pass to the client in the reference documentation (i.e. [react](/reference/react/nhost-client#nhostclient))</Note>
For Run services, typically in an `nhost-service.toml` specific to the service:
```toml
name = 'my-service'

View File

@@ -1,12 +1,14 @@
---
title: Functions
description: Server-side code that works as API endpoints
description: Server-side code that works as API endpoints
icon: code
---
Nhost Functions are server-side JavaScript or TypeScript functions that are a great option for handling things like Event Triggers for async workflows, as well as to integrate with external service providers like Stripe or Slack.
<Tip>For more sophisticated use-cases and control over the runtime, use [Nhost Run](/product/run).</Tip>
<Tip>
For more sophisticated use-cases and control over the runtime, use [Nhost Run](/product/run).
</Tip>
## Hello World
@@ -21,7 +23,5 @@ Deploying functions is as easy as pushing your code!
## Additional Resources
<CardGroup cols={2}>
<Card title="Learn how to use Functions" href="/guides/functions/getting-started">
</Card>
<Card title="Learn how to use Functions" href="/guides/functions/overview"></Card>
</CardGroup>

View File

@@ -9,10 +9,10 @@ Note: The Nhost client automatically refreshes the session when the user is auth
```ts
// Refresh the session with the the current internal refresh token.
nhost.auth.refreshToken()
nhost.auth.refreshSession()
// Refresh the session with an external refresh token.
nhost.auth.refreshToken(refreshToken)
nhost.auth.refreshSession(refreshToken)
```
## Parameters

View File

@@ -0,0 +1,70 @@
---
title: call()
sidebarTitle: call()
---
Use `nhost.functions.call` to call (sending a POST request to) a serverless function. Use generic
types to specify the expected response data, request body and error message.
```ts
await nhost.functions.call('send-welcome-email', {
email: 'joe@example.com',
name: 'Joe Doe'
})
```
## Parameters
---
**<span className="parameter-name">url</span>** <span className="optional-status">required</span> <code>string</code>
---
**<span className="parameter-name">body</span>** <span className="optional-status">optional</span> <code>null &#124; TBody</code>
---
**<span className="parameter-name">config</span>** <span className="optional-status">optional</span> <code>NhostFunctionCallConfig</code>
---
## Examples
### Without generic types
```ts
await nhost.functions.call('send-welcome-email', {
email: 'joe@example.com',
name: 'Joe Doe'
})
```
### Using generic types
```ts
type Data = {
message: string
}
type Body = {
email: string
name: string
}
type ErrorMessage = {
details: string
}
// The function will only accept a body of type `Body`
const { res, error } = await nhost.functions.call<Data, Body, ErrorMessage>(
'send-welcome-email',
{ email: 'joe@example.com', name: 'Joe Doe' }
)
// Now the response data is typed as `Data`
console.log(res?.data.message)
// Now the error message is typed as `ErrorMessage`
console.log(error?.message.details)
```

View File

@@ -0,0 +1,31 @@
---
title: createFunctionsClient()
sidebarTitle: createFunctionsClient()
---
Creates a client for Functions from either a subdomain or a URL
## Parameters
---
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> [`NhostClientConstructorParams`](/reference/javascript/nhost-js/types/nhost-client-constructor-params)
| Property | Type | Required | Notes |
| :----------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------ | :------: | :--------------------------------------------------------------------------------------------------------------------------------------- |
| <span className="parameter-name"><span className="light-grey">params.</span>adminSecret</span> | <code>string</code> | | When set, the admin secret is sent as a header, `x-hasura-admin-secret`, for all requests to GraphQL, Storage, and Serverless Functions. |
| <span className="parameter-name"><span className="light-grey">params.</span>functionsUrl</span> | <code>string</code> | | |
| <span className="parameter-name"><span className="light-grey">params.</span>storageUrl</span> | <code>string</code> | | |
| <span className="parameter-name"><span className="light-grey">params.</span>graphqlUrl</span> | <code>string</code> | | |
| <span className="parameter-name"><span className="light-grey">params.</span>authUrl</span> | <code>string</code> | | |
| <span className="parameter-name"><span className="light-grey">params.</span>region</span> | <code>string</code> | | Project region (e.g. `eu-central-1`) Project region is not required during local development (when `subdomain` is `localhost`) |
| <span className="parameter-name"><span className="light-grey">params.</span>subdomain</span> | <code>string</code> | | Project subdomain (e.g. `ieingiwnginwnfnegqwvdqwdwq`) Use `localhost` during local development |
| <span className="parameter-name"><span className="light-grey">params.</span>devTools</span> | <code>boolean</code> | | Activate devTools e.g. the ability to connect to the xstate inspector |
| <span className="parameter-name"><span className="light-grey">params.</span>autoSignIn</span> | <code>boolean</code> | | When set to true, will parse the url on startup to check if it contains a refresh token to start the session with |
| <span className="parameter-name"><span className="light-grey">params.</span>autoRefreshToken</span> | <code>boolean</code> | | When set to true, will automatically refresh token before it expires |
| <span className="parameter-name"><span className="light-grey">params.</span>clientStorage</span> | [`ClientStorage`](/reference/javascript/nhost-js/types/client-storage) | | Object where the refresh token will be persisted and read locally. |
| <span className="parameter-name"><span className="light-grey">params.</span>clientStorageType</span> | [`ClientStorageType`](/reference/javascript/nhost-js/types/client-storage-type) | | Define a way to get information about the refresh token and its exipration date. |
| <span className="parameter-name"><span className="light-grey">params.</span>refreshIntervalTime</span> | <code>number</code> | | Time interval until token refreshes, in seconds |
| <span className="parameter-name"><span className="light-grey">params.</span>start</span> | <code>boolean</code> | | |
---

View File

@@ -0,0 +1,13 @@
---
title: NhostFunctionsClient
---
# `NhostFunctionsClient`
## Parameters
---
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> <code>NhostFunctionsConstructorParams</code>
---

View File

@@ -0,0 +1,18 @@
---
title: setAccessToken()
sidebarTitle: setAccessToken()
---
Use `nhost.functions.setAccessToken` to a set an access token to be used in subsequent functions requests. Note that if you're signin in users with `nhost.auth.signIn()` the access token will be set automatically.
```ts
nhost.functions.setAccessToken('some-access-token')
```
## Parameters
---
**<span className="parameter-name">accessToken</span>** <span className="optional-status">required</span> <code>undefined &#124; string</code>
---

View File

@@ -1,4 +0,0 @@
---
title: createFileUploadMachine()
sidebarTitle: createFileUploadMachine()
---

View File

@@ -1,4 +0,0 @@
---
title: createMultipleFilesUploadMachine()
sidebarTitle: createMultipleFilesUploadMachine()
---

View File

@@ -1,16 +0,0 @@
---
title: uploadFilePromise()
sidebarTitle: uploadFilePromise()
---
## Parameters
---
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> <code>[`FileUploadConfig`](/reference/javascript/storage/types/file-upload-config) &amp; Partial&lt;StorageUploadFileParams&gt;</code>
---
**<span className="parameter-name">interpreter</span>** <span className="optional-status">required</span> <code>ActorRefWithDeprecatedState&lt;FileUploadContext, &#123; type: "ADD", file: File, id: string, bucketId: string, name: string &#125; &#124; &#123; type: "UPLOAD", file: File, id: string, name: string, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number &#125; &#124; &#123; type: "UPLOAD_DONE", id: string, bucketId: string &#125; &#124; &#123; type: "UPLOAD_ERROR", error: StorageErrorPayload &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "DESTROY" &#125;, &#123; value: any, context: FileUploadContext &#125;, ResolveTypegenMeta&lt;Typegen0, &#123; type: "ADD", file: File, id: string, bucketId: string, name: string &#125; &#124; &#123; type: "UPLOAD", file: File, id: string, name: string, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number &#125; &#124; &#123; type: "UPLOAD_DONE", id: string, bucketId: string &#125; &#124; &#123; type: "UPLOAD_ERROR", error: StorageErrorPayload &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "DESTROY" &#125;, BaseActionObject, ServiceMap&gt;&gt; &#124; Interpreter&lt;FileUploadContext, any, &#123; type: "ADD", file: File, id: string, bucketId: string, name: string &#125; &#124; &#123; type: "UPLOAD", file: File, id: string, name: string, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number &#125; &#124; &#123; type: "UPLOAD_DONE", id: string, bucketId: string &#125; &#124; &#123; type: "UPLOAD_ERROR", error: StorageErrorPayload &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "DESTROY" &#125;, &#123; value: any, context: FileUploadContext &#125;, ResolveTypegenMeta&lt;Typegen0, &#123; type: "ADD", file: File, id: string, bucketId: string, name: string &#125; &#124; &#123; type: "UPLOAD", file: File, id: string, name: string, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number &#125; &#124; &#123; type: "UPLOAD_DONE", id: string, bucketId: string &#125; &#124; &#123; type: "UPLOAD_ERROR", error: StorageErrorPayload &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "DESTROY" &#125;, BaseActionObject, ServiceMap&gt;&gt;</code>
---

View File

@@ -1,16 +0,0 @@
---
title: uploadMultipleFilesPromise()
sidebarTitle: uploadMultipleFilesPromise()
---
## Parameters
---
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> <code>[`FileUploadConfig`](/reference/javascript/storage/types/file-upload-config) &amp; [`UploadMultipleFilesActionParams`](/reference/javascript/storage/types/upload-multiple-files-action-params)</code>
---
**<span className="parameter-name">service</span>** <span className="optional-status">required</span> <code>Interpreter&lt;MultipleFilesUploadContext, any, &#123; type: "ADD", files: AnyFileList, bucketId: string &#125; &#124; &#123; type: "UPLOAD", files: AnyFileList, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", additions: number &#125; &#124; &#123; type: "UPLOAD_DONE" &#125; &#124; &#123; type: "UPLOAD_ERROR" &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "REMOVE" &#125; &#124; &#123; type: "CLEAR" &#125;, &#123; value: any, context: MultipleFilesUploadContext &#125;, ResolveTypegenMeta&lt;Typegen0, &#123; type: "ADD", files: AnyFileList, bucketId: string &#125; &#124; &#123; type: "UPLOAD", files: AnyFileList, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", additions: number &#125; &#124; &#123; type: "UPLOAD_DONE" &#125; &#124; &#123; type: "UPLOAD_ERROR" &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "REMOVE" &#125; &#124; &#123; type: "CLEAR" &#125;, BaseActionObject, ServiceMap&gt;&gt;</code>
---

View File

@@ -0,0 +1,27 @@
---
title: download()
sidebarTitle: download()
---
Use `nhost.storage.download` to download a file. To download a file the user must have permission to select the file in the `storage.files` table.
```ts
const { file, error } = await nhost.storage.download({ fileId: '<File-ID>' })
```
## Parameters
---
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> [`StorageDownloadFileParams`](/reference/javascript/storage/types/storage-download-file-params)
| Property | Type | Required | Notes |
| :----------------------------------------------------------------------------------------- | :---------------------------------------- | :------: | :----------------------------------------------------------- |
| <span className="parameter-name"><span className="light-grey">params.</span>fileId</span> | <code>string</code> | ✔️ | |
| <span className="parameter-name"><span className="light-grey">params.</span>blur</span> | <code>number</code> | | Image blur, between 0 and 100 |
| <span className="parameter-name"><span className="light-grey">params.</span>quality</span> | <code>number</code> | | Image quality, between 1 and 100, 100 being the best quality |
| <span className="parameter-name"><span className="light-grey">params.</span>height</span> | <code>number</code> | | Image height, in pixels |
| <span className="parameter-name"><span className="light-grey">params.</span>width</span> | <code>number</code> | | Image width, in pixels |
| <span className="parameter-name"><span className="light-grey">params.</span>headers</span> | <code>Record&lt;string, string&gt;</code> | | Optional headers to be sent with the request |
---

View File

@@ -0,0 +1,45 @@
---
title: StorageDownloadFileParams
sidebarTitle: StorageDownloadFileParams
description: No description provided.
---
# `StorageDownloadFileParams`
## Parameters
---
**<span className="parameter-name">fileId</span>** <span className="optional-status">required</span> <code>string</code>
---
**<span className="parameter-name">blur</span>** <span className="optional-status">optional</span> <code>number</code>
Image blur, between 0 and 100
---
**<span className="parameter-name">quality</span>** <span className="optional-status">optional</span> <code>number</code>
Image quality, between 1 and 100, 100 being the best quality
---
**<span className="parameter-name">height</span>** <span className="optional-status">optional</span> <code>number</code>
Image height, in pixels
---
**<span className="parameter-name">width</span>** <span className="optional-status">optional</span> <code>number</code>
Image width, in pixels
---
**<span className="parameter-name">headers</span>** <span className="optional-status">optional</span> <code>Record&lt;string, string&gt;</code>
Optional headers to be sent with the request
---

View File

@@ -0,0 +1,13 @@
---
title: StorageDownloadFileResponse
sidebarTitle: StorageDownloadFileResponse
description: No description provided.
---
# `StorageDownloadFileResponse`
```ts
type StorageDownloadFileResponse =
| { file: Blob; error: null }
| { file: null; error: Error }
```

View File

@@ -11,18 +11,9 @@ info:
servers:
- url: https://local.auth.nhost.run/v1
description: API Server
security:
- AdminSecret: []
- BearerAuth: []
components:
securitySchemes:
AdminSecret:
type: apiKey
in: header
name: X-Hasura-Admin-Secret
description: Hasura Admin Secret
BearerAuth:
scheme: bearer
type: http
@@ -1524,7 +1515,6 @@ paths:
schema:
$ref: '#/components/schemas/DisabledEndpointError'
description: The feature is not activated
security: []
summary: Sign In TOTP
tags:
- Authentication

View File

@@ -1,5 +1,24 @@
# @nhost-examples/cli
## 0.1.6
### Patch Changes
- @nhost/nhost-js@3.0.5
## 0.1.5
### Patch Changes
- @nhost/nhost-js@3.0.4
## 0.1.4
### Patch Changes
- e5bab6a: chore: update dependencies
- @nhost/nhost-js@3.0.3
## 0.1.3
### Patch Changes

View File

@@ -1,15 +1,15 @@
{
"name": "@nhost-examples/cli",
"version": "0.1.3",
"version": "0.1.6",
"main": "src/index.mjs",
"private": true,
"scripts": {
"start": "node src/index.mjs"
},
"dependencies": {
"@nhost/nhost-js": "^3.0.2",
"@nhost/nhost-js": "workspace:^",
"commander": "^10.0.1",
"dotenv": "^16.3.1",
"dotenv": "^16.4.1",
"graphql": "16.8.1",
"graphql-tag": "^2.12.6",
"pino": "^8.17.2"

View File

@@ -1,5 +1,30 @@
# @nhost-examples/codegen-react-apollo
## 0.1.14
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
- @nhost/react-apollo@9.0.0
## 0.1.13
### Patch Changes
- @nhost/react@3.1.1
- @nhost/react-apollo@8.0.1
## 0.1.12
### Patch Changes
- e5bab6a: chore: update dependencies
- Updated dependencies [1a61c65]
- Updated dependencies [e5bab6a]
- @nhost/react@3.1.0
- @nhost/react-apollo@8.0.0
## 0.1.11
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/codegen-react-apollo",
"version": "0.1.11",
"version": "0.1.14",
"private": true,
"scripts": {
"codegen": "graphql-codegen",
@@ -15,9 +15,9 @@
]
},
"dependencies": {
"@apollo/client": "^3.8.9",
"@nhost/react": "^3.0.2",
"@nhost/react-apollo": "^7.0.2",
"@apollo/client": "^3.9.4",
"@nhost/react": "workspace:^",
"@nhost/react-apollo": "workspace:^",
"clsx": "^1.2.1",
"graphql": "16.8.1",
"react": "^18.2.0",
@@ -28,11 +28,11 @@
"@graphql-codegen/client-preset": "^1.3.0",
"@graphql-typed-document-node/core": "^3.2.0",
"@tailwindcss/forms": "^0.5.7",
"@types/node": "^18.19.6",
"@types/react": "^18.2.47",
"@types/node": "^18.19.12",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.16",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"typescript": "^4.9.5",

View File

@@ -1,5 +1,27 @@
# @nhost-examples/codegen-react-query
## 0.1.15
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
## 0.1.14
### Patch Changes
- @nhost/react@3.1.1
## 0.1.13
### Patch Changes
- e5bab6a: chore: update dependencies
- Updated dependencies [1a61c65]
- Updated dependencies [e5bab6a]
- @nhost/react@3.1.0
## 0.1.12
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/codegen-react-query",
"version": "0.1.12",
"version": "0.1.15",
"private": true,
"scripts": {
"codegen": "graphql-codegen",
@@ -15,7 +15,7 @@
]
},
"dependencies": {
"@nhost/react": "^3.0.2",
"@nhost/react": "workspace:^",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query-devtools": "^4.36.1",
"clsx": "^1.2.1",
@@ -29,10 +29,10 @@
"@graphql-codegen/client-preset": "^1.3.0",
"@graphql-typed-document-node/core": "^3.2.0",
"@tailwindcss/forms": "^0.5.7",
"@types/node": "^16.18.70",
"@types/react": "^18.2.47",
"@types/node": "^16.18.78",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.18",
"autoprefixer": "^10.4.16",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",

View File

@@ -1,5 +1,30 @@
# @nhost-examples/react-urql
## 0.0.11
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
- @nhost/react-urql@6.0.0
## 0.0.10
### Patch Changes
- @nhost/react@3.1.1
- @nhost/react-urql@5.0.1
## 0.0.9
### Patch Changes
- e5bab6a: chore: update dependencies
- Updated dependencies [1a61c65]
- Updated dependencies [e5bab6a]
- @nhost/react@3.1.0
- @nhost/react-urql@5.0.0
## 0.0.8
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/codegen-react-urql",
"private": true,
"version": "0.0.8",
"version": "0.0.11",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
@@ -9,8 +9,8 @@
"codegen": "graphql-codegen"
},
"dependencies": {
"@nhost/react": "^3.0.2",
"@nhost/react-urql": "^4.0.2",
"@nhost/react": "workspace:^",
"@nhost/react-urql": "workspace:^",
"clsx": "^1.2.1",
"graphql": "16.8.1",
"react": "18.2.0",
@@ -22,11 +22,11 @@
"@graphql-codegen/client-preset": "^1.3.0",
"@graphql-typed-document-node/core": "^3.2.0",
"@tailwindcss/forms": "^0.5.7",
"@types/node": "^16.18.70",
"@types/react": "^18.2.47",
"@types/node": "^16.18.78",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.16",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"typescript": "^4.9.5",

View File

@@ -1,5 +1,24 @@
# @nhost-examples/multi-tenant-one-to-many
## 2.0.4
### Patch Changes
- @nhost/nhost-js@3.0.5
## 2.0.3
### Patch Changes
- @nhost/nhost-js@3.0.4
## 2.0.2
### Patch Changes
- e5bab6a: chore: update dependencies
- @nhost/nhost-js@3.0.3
## 2.0.1
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/multi-tenant-one-to-many",
"private": true,
"version": "2.0.1",
"version": "2.0.4",
"description": "",
"main": "index.js",
"scripts": {},
@@ -14,6 +14,6 @@
"typescript": "^4.9.5"
},
"dependencies": {
"@nhost/nhost-js": "^3.0.2"
"@nhost/nhost-js": "workspace:^"
}
}

View File

@@ -1,5 +1,34 @@
# @nhost-examples/nextjs
## 0.1.16
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
- @nhost/react-apollo@9.0.0
- @nhost/nextjs@2.1.2
## 0.1.15
### Patch Changes
- @nhost/react@3.1.1
- @nhost/react-apollo@8.0.1
- @nhost/nextjs@2.1.1
## 0.1.14
### Patch Changes
- e5bab6a: chore: update dependencies
- Updated dependencies [1a61c65]
- Updated dependencies [b19ffed]
- Updated dependencies [e5bab6a]
- @nhost/react@3.1.0
- @nhost/nextjs@2.1.0
- @nhost/react-apollo@8.0.0
## 0.1.13
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/nextjs",
"version": "0.1.13",
"version": "0.1.16",
"private": true,
"scripts": {
"dev": "next dev",
@@ -15,24 +15,24 @@
"verify:fix": "run-p prettier:fix lint:fix"
},
"dependencies": {
"@apollo/client": "^3.8.9",
"@apollo/client": "^3.9.4",
"@mantine/core": "^4.2.12",
"@mantine/hooks": "^4.2.12",
"@mantine/next": "^4.2.12",
"@mantine/notifications": "^4.2.12",
"@nhost/nextjs": "^2.0.2",
"@nhost/react": "^3.0.2",
"@nhost/react-apollo": "^7.0.2",
"@nhost/nextjs": "workspace:^",
"@nhost/react": "workspace:^",
"@nhost/react-apollo": "workspace:^",
"graphql": "16.8.1",
"next": "^14.0.4",
"next": "^14.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.12.0"
},
"devDependencies": {
"@next/bundle-analyzer": "^12.3.4",
"@types/node": "^16.18.70",
"@types/react": "^18.2.47",
"@types/node": "^16.18.78",
"@types/react": "^18.2.50",
"@xstate/inspect": "^0.6.5",
"eslint-config-next": "12.0.10",
"typescript": "^4.9.5",

View File

@@ -1,5 +1,24 @@
# @nhost-examples/node-storage
## 0.0.8
### Patch Changes
- @nhost/nhost-js@3.0.5
## 0.0.7
### Patch Changes
- @nhost/nhost-js@3.0.4
## 0.0.6
### Patch Changes
- e5bab6a: chore: update dependencies
- @nhost/nhost-js@3.0.3
## 0.0.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/node-storage",
"version": "0.0.5",
"version": "0.0.8",
"private": true,
"description": "This is an example of how to use the Storage with Node.js",
"main": "src/index.mjs",
@@ -11,15 +11,15 @@
"author": "",
"license": "ISC",
"dependencies": {
"@nhost/nhost-js": "^3.0.2",
"dotenv": "^16.3.1",
"@nhost/nhost-js": "workspace:^",
"dotenv": "^16.4.1",
"form-data": "^4.0.0",
"fs-extra": "^11.2.0",
"node-fetch": "^3.3.2",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/node": "^18.19.6",
"@types/uuid": "^9.0.7"
"@types/node": "^18.19.12",
"@types/uuid": "^9.0.8"
}
}

View File

@@ -1,5 +1,28 @@
# @nhost-examples/nextjs-server-components
## 0.2.2
### Patch Changes
- @nhost/nhost-js@3.0.5
## 0.2.1
### Patch Changes
- @nhost/nhost-js@3.0.4
## 0.2.0
### Minor Changes
- d3d1424: feat: Add support for authenticated download of files
### Patch Changes
- e5bab6a: chore: update dependencies
- @nhost/nhost-js@3.0.3
## 0.1.5
### Patch Changes

View File

@@ -1,5 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true
}
}
module.exports = nextConfig

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/nextjs-server-components",
"version": "0.1.5",
"version": "0.2.2",
"private": true,
"scripts": {
"dev": "next dev",
@@ -9,7 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@apollo/client": "^3.8.9",
"@apollo/client": "^3.9.4",
"@nhost/nhost-js": "workspace:^",
"autoprefixer": "10.4.15",
"cookies-next": "^3.0.0",
@@ -18,10 +18,10 @@
"form-data": "^4.0.0",
"graphql": "16.8.1",
"js-cookie": "^3.0.5",
"next": "^14.0.4",
"next": "^14.1.0",
"postcss": "^8.4.33",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3",
"typescript": "5.2.2",
@@ -30,7 +30,7 @@
"devDependencies": {
"@types/js-cookie": "^3.0.6",
"@types/node": "20.5.6",
"@types/react": "18.2.47",
"@types/react-dom": "18.2.7"
"@types/react": "^18.2.47",
"@types/react-dom": "^18.2.7"
}
}

View File

@@ -2,7 +2,7 @@
import { DetailedHTMLProps, HTMLProps } from 'react'
// @ts-ignore
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
import { useFormStatus } from 'react-dom'
export default function Input({
id,

View File

@@ -2,12 +2,12 @@
import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react'
// @ts-ignore
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
import { useFormStatus } from 'react-dom'
import { twMerge } from 'tailwind-merge'
type ButtonProps = {
type?: 'button' | 'submit' | 'reset' | undefined;
} & DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
type?: 'button' | 'submit' | 'reset' | undefined
} & DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
export default function SubmitButton({
disabled,
@@ -16,7 +16,7 @@ export default function SubmitButton({
children,
...rest
}: ButtonProps) {
const { pending } = useFormStatus();
const { pending } = useFormStatus()
return (
<button
@@ -33,5 +33,5 @@ export default function SubmitButton({
>
{children}
</button>
);
}
)
}

View File

@@ -32,6 +32,20 @@ const TodoItem = ({ todo }: { todo: Todo }) => {
await deleteTodo(todo.id)
}
const handleDownloadAttachment = async () => {
if (todo.attachment) {
const response = await nhost.storage.download({ fileId: todo.attachment.id })
if (response.file) {
const url = window.URL.createObjectURL(response.file)
const a = document.createElement('a')
a.href = url
a.download = todo.title
a.click()
window.URL.revokeObjectURL(url)
}
}
}
return (
<div
className={twMerge(
@@ -74,6 +88,12 @@ const TodoItem = ({ todo }: { todo: Todo }) => {
</Link>
)}
<button onClick={handleDownloadAttachment} className="w-6 h-6">
<svg xmlns="http://www.w3.org/2000/svg" strokeWidth={1.5} viewBox="0 0 512 512">
<path d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z" />
</svg>
</button>
<button onClick={handleDeleteTodo} className="w-6 h-6">
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -54,16 +54,17 @@ insert_permissions:
permission:
check: {}
columns:
- is_uploaded
- size
- bucket_id
- etag
- mime_type
- name
- id
- created_at
- updated_at
- id
- bucket_id
- name
- size
- mime_type
- etag
- is_uploaded
- uploaded_by_user_id
- metadata
- role: user
permission:
check: {}
@@ -78,16 +79,17 @@ select_permissions:
- role: public
permission:
columns:
- is_uploaded
- size
- bucket_id
- etag
- mime_type
- name
- id
- created_at
- updated_at
- id
- bucket_id
- name
- size
- mime_type
- etag
- is_uploaded
- uploaded_by_user_id
- metadata
filter: {}
- role: user
permission:

View File

@@ -146,7 +146,7 @@ version = '14.6-20230406-2'
[provider]
[storage]
version = '0.4.0'
version = '0.5.1'
[observability]
[observability.grafana]

View File

@@ -1,5 +1,10 @@
---
## 0.2.3
### Patch Changes
- e5bab6a: chore: update dependencies
## 0.2.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/sveltekit",
"version": "0.2.2",
"version": "0.2.3",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -16,11 +16,11 @@
},
"devDependencies": {
"@nhost/nhost-js": "2.2.18",
"@playwright/test": "^1.41.0",
"@playwright/test": "^1.41.1",
"@sveltejs/adapter-auto": "^2.1.1",
"@sveltejs/kit": "^1.30.3",
"@types/js-cookie": "^3.0.6",
"autoprefixer": "^10.4.16",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-svelte": "^2.35.1",

View File

@@ -1,5 +1,38 @@
# @nhost-examples/react-apollo
## 0.3.0
### Minor Changes
- 017f1a6: feat: add elevated permission examples
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
- @nhost/react-apollo@9.0.0
## 0.2.1
### Patch Changes
- @nhost/react@3.1.1
- @nhost/react-apollo@8.0.1
## 0.2.0
### Minor Changes
- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with integration into react-apollo and vue-apollo examples
### Patch Changes
- e5bab6a: chore: update dependencies
- Updated dependencies [1a61c65]
- Updated dependencies [e5bab6a]
- @nhost/react@3.1.0
- @nhost/react-apollo@8.0.0
## 0.1.18
### Patch Changes

View File

@@ -1,5 +1,5 @@
schema:
- http://local.graphql.nhost.run/v1:
- https://local.hasura.nhost.run/v1/graphql:
headers:
x-hasura-admin-secret: nhost-admin-secret
x-hasura-role: user
@@ -15,7 +15,6 @@ generates:
bigint: number
citext: string
timestamptz: string
plugins:
- typescript
- typescript-operations

View File

@@ -46,4 +46,4 @@ delete_permissions:
permission:
filter:
user_id:
_eq: X-Hasura-User-Id
_eq: x-hasura-auth-elevated

View File

@@ -0,0 +1,62 @@
table:
name: notes
schema: public
configuration:
column_config:
created_at:
custom_name: createdAt
updated_at:
custom_name: updatedAt
custom_column_names:
created_at: createdAt
updated_at: updatedAt
custom_root_fields:
delete: deleteNotes
delete_by_pk: deleteNote
insert: inserNotes
insert_one: insertNote
select_aggregate: notesAggregate
select_by_pk: note
update: updateNotes
update_by_pk: updateNote
object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id
insert_permissions:
- role: user
permission:
check:
user_id:
_eq: x-hasura-auth-elevated
set:
user_id: x-hasura-User-Id
columns:
- content
select_permissions:
- role: user
permission:
columns:
- content
- created_at
- id
- updated_at
filter:
user_id:
_eq: X-Hasura-User-Id
allow_aggregations: true
update_permissions:
- role: user
permission:
columns:
- content
filter:
user_id:
_eq: x-hasura-auth-elevated
check: null
delete_permissions:
- role: user
permission:
filter:
user_id:
_eq: x-hasura-auth-elevated

View File

@@ -0,0 +1,42 @@
table:
name: virus
schema: storage
configuration:
column_config:
created_at:
custom_name: createdAt
file_id:
custom_name: fileId
filename:
custom_name: filename
id:
custom_name: id
updated_at:
custom_name: updatedAt
user_session:
custom_name: userSession
virus:
custom_name: virus
custom_column_names:
created_at: createdAt
file_id: fileId
filename: filename
id: id
updated_at: updatedAt
user_session: userSession
virus: virus
custom_name: virus
custom_root_fields:
delete: deleteViruses
delete_by_pk: deleteVirus
insert: insertViruses
insert_one: insertVirus
select: viruses
select_aggregate: virusesAggregate
select_by_pk: virus
update: updateViruses
update_by_pk: updateVirus
object_relationships:
- name: file
using:
foreign_key_constraint_on: file_id

View File

@@ -7,6 +7,8 @@
- "!include auth_user_roles.yaml"
- "!include auth_user_security_keys.yaml"
- "!include auth_users.yaml"
- "!include public_notes.yaml"
- "!include public_todos.yaml"
- "!include storage_buckets.yaml"
- "!include storage_files.yaml"
- "!include storage_virus.yaml"

View File

@@ -0,0 +1 @@
DROP TABLE "public"."notes";

View File

@@ -0,0 +1,2 @@
CREATE TABLE "public"."notes" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "content" text NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "user_id" uuid NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"));
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -1,7 +1,7 @@
[global]
[hasura]
version = 'v2.25.1-ce'
version = 'v2.33.4-ce'
adminSecret = '{{ secrets.HASURA_GRAPHQL_ADMIN_SECRET }}'
webhookSecret = '{{ secrets.NHOST_WEBHOOK_SECRET }}'
@@ -28,11 +28,14 @@ httpPoolSize = 100
version = 18
[auth]
version = '0.22.1'
version = '0.26.0'
[auth.elevatedPrivileges]
mode = 'required'
[auth.redirections]
clientUrl = 'https://react-apollo.example.nhost.io/'
allowedUrls = ['https://react-apollo.example.nhost.io/profile', 'http://localhost:30000']
allowedUrls = ['https://react-apollo.example.nhost.io/profile', 'http://localhost:3000']
[auth.signUp]
enabled = true
@@ -149,12 +152,12 @@ enabled = true
issuer = 'nhost'
[postgres]
version = '14.6-20230406-2'
version = '14.6-20240129-1'
[provider]
[storage]
version = '0.3.4'
version = '0.6.0'
[observability]
[observability.grafana]

View File

@@ -1,22 +1,22 @@
{
"name": "@nhost-examples/react-apollo",
"version": "0.1.18",
"version": "0.3.0",
"private": true,
"dependencies": {
"@apollo/client": "^3.8.9",
"@apollo/client": "^3.9.4",
"@mantine/core": "^4.2.12",
"@mantine/dropzone": "^4.2.12",
"@mantine/hooks": "^4.2.12",
"@mantine/notifications": "^4.2.12",
"@mantine/prism": "^4.2.12",
"@nhost/react": "^3.0.2",
"@nhost/react-apollo": "^7.0.2",
"@nhost/react": "workspace:^",
"@nhost/react-apollo": "workspace:^",
"graphql": "16.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.12.0",
"react-router": "^6.21.2",
"react-router-dom": "^6.21.2",
"react-router": "^6.21.3",
"react-router-dom": "^6.21.3",
"tabler-icons-react": "^1.56.0"
},
"scripts": {
@@ -54,12 +54,12 @@
"@nuintun/qrcode": "^3.4.0",
"@playwright/test": "1.31.0",
"@types/pngjs": "^6.0.4",
"@types/react": "^18.2.47",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.18",
"@types/totp-generator": "^0.0.4",
"@vitejs/plugin-react": "^3.1.0",
"@xstate/inspect": "^0.6.5",
"dotenv": "^16.3.1",
"dotenv": "^16.4.1",
"jsqr": "^1.4.0",
"pngjs": "^7.0.0",
"totp-generator": "^0.0.13",

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ import { SignUpPage } from './sign-up'
import { StoragePage } from './Storage'
import './App.css?inline'
import { NotesPage } from './components/notes'
const title = 'Nhost with React and Apollo'
function App() {
@@ -94,6 +95,16 @@ function App() {
</AuthGate>
}
/>
<Route
path="/secret-notes"
element={
<AuthGate>
<NotesPage />
</AuthGate>
}
/>
<Route
path="/storage"
element={

View File

@@ -1,10 +1,20 @@
import { FaFile, FaHouseUser, FaQuestion, FaSignOutAlt } from 'react-icons/fa'
import { FaFile, FaHouseUser, FaQuestion, FaSignOutAlt, FaLock } from 'react-icons/fa'
import { SiApollographql } from 'react-icons/si'
import { useLocation, useNavigate } from 'react-router'
import { Link } from 'react-router-dom'
import { showNotification } from '@mantine/notifications'
import { Group, MantineColor, Navbar, Text, ThemeIcon, UnstyledButton } from '@mantine/core'
import { useAuthenticated, useSignOut } from '@nhost/react'
import {
Button,
Card,
Group,
MantineColor,
Navbar,
Text,
ThemeIcon,
UnstyledButton
} from '@mantine/core'
import { useAuthenticated, useElevateSecurityKeyEmail, useSignOut, useUserData } from '@nhost/react'
interface MenuItemProps {
icon: React.ReactNode
color?: MantineColor
@@ -52,16 +62,52 @@ const MenuItem: React.FC<MenuItemProps> = ({ icon, color, label, link, action })
const data: MenuItemProps[] = [
{ icon: <FaHouseUser size={16} />, label: 'Home', link: '/' },
{ icon: <FaHouseUser size={16} />, label: 'Profile', link: '/profile' },
{ icon: <FaLock size={16} />, label: 'Secret Notes', link: '/secret-notes' },
{ icon: <FaFile size={16} />, label: 'Storage', link: '/storage' },
{ icon: <SiApollographql size={16} />, label: 'Apollo', link: '/apollo' },
{ icon: <FaQuestion size={16} />, label: 'About', link: '/about' }
]
export default function NavBar() {
const authenticated = useAuthenticated()
const { signOut } = useSignOut()
const userData = useUserData()
const navigate = useNavigate()
const { signOut } = useSignOut()
const authenticated = useAuthenticated()
const { elevateEmailSecurityKey, elevated } = useElevateSecurityKeyEmail()
const handleElevate = async () => {
if (!authenticated) {
showNotification({
color: 'red',
title: 'Logged out',
message: 'Please login first'
})
return
}
if (userData?.email) {
const { elevated, isError } = await elevateEmailSecurityKey(userData.email)
if (elevated) {
showNotification({
title: 'Success',
message: 'You now have an elevated permission'
})
}
if (isError) {
showNotification({
color: 'red',
title: 'Failed',
message: 'Could not elevate permission'
})
}
}
}
const links = data.map((link) => <MenuItem {...link} key={link.label} />)
return (
<Navbar width={{ sm: 300, lg: 400, base: 100 }} aria-label="main navigation">
<Navbar.Section grow mt="md">
@@ -77,6 +123,13 @@ export default function NavBar() {
/>
)}
</Navbar.Section>
<Card p="lg" m="sm">
<Group position="apart">
<span>Elevated permissions: {String(elevated)}</span>
<Button onClick={handleElevate}>Elevate</Button>
</Group>
</Card>
</Navbar>
)
}

View File

@@ -18,7 +18,9 @@ const LoadingComponent: React.FC<React.PropsWithChildren<{ connectionAttempts: n
export const AuthGate: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const { isLoading, isAuthenticated, connectionAttempts } = useAuthenticationStatus()
const location = useLocation()
if (isLoading) {
return <LoadingComponent connectionAttempts={connectionAttempts} />
}

View File

@@ -0,0 +1,220 @@
import {
DeleteNoteMutation,
InsertNoteMutation,
NotesListQuery,
SecurityKeysQuery
} from 'src/generated'
import { gql, useMutation } from '@apollo/client'
import {
Button,
Card,
Container,
Grid,
Loader,
TextInput,
Title,
Group,
ActionIcon
} from '@mantine/core'
import { useInputState } from '@mantine/hooks'
import { showNotification } from '@mantine/notifications'
import { useAuthQuery } from '@nhost/react-apollo'
import { useElevateSecurityKeyEmail, useUserData } from '@nhost/react'
import { FaTrash } from 'react-icons/fa'
import { SECURITY_KEYS_LIST } from 'src/utils'
import { useState } from 'react'
const NOTES_LIST = gql`
query notesList {
notes {
id
content
}
}
`
const INSERT_NOTE = gql`
mutation insertNote($content: String!) {
insertNote(object: { content: $content }) {
id
content
}
}
`
const DELETE_NOTE = gql`
mutation deleteNote($noteId: uuid!) {
deleteNote(id: $noteId) {
id
content
}
}
`
export const NotesPage: React.FC = () => {
const userData = useUserData()
const { loading, data } = useAuthQuery<NotesListQuery>(NOTES_LIST, {
pollInterval: 5000,
fetchPolicy: 'cache-and-network'
})
const [content, setContent] = useInputState('')
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
const [userHasSecurityKey, setUserHasSecurityKey] = useState(false)
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
variables: { userId: userData?.id },
onCompleted: ({ authUserSecurityKeys }) => {
setUserHasSecurityKey(authUserSecurityKeys?.length > 0)
}
})
const [addNoteMutation] = useMutation<InsertNoteMutation>(INSERT_NOTE)
const [deleteNoteMutation] = useMutation<DeleteNoteMutation>(DELETE_NOTE)
const checkElevatedPermission = async () => {
if (!elevated && userHasSecurityKey) {
const { elevated } = await elevateEmailSecurityKey(userData?.email as string)
if (!elevated) {
throw new Error('Permissions were not elevated')
}
}
}
const add = async () => {
if (!content) return
try {
await checkElevatedPermission()
} catch (error) {
showNotification({
title: 'Error',
message: 'Could not elevate permissions'
})
return
}
addNoteMutation({
variables: { content },
onCompleted: () => setContent(''),
onError: (error) => {
showNotification({
color: 'red',
title: error.networkError ? 'Network error' : 'Error',
message: error.message
})
},
update: (cache, { data }) => {
cache.modify({
fields: {
notes(existingNotes = []) {
const newNoteRef = cache.writeFragment({
data: data?.insertNote,
fragment: gql`
fragment NewNote on notes {
id
content
}
`
})
return [...existingNotes, newNoteRef]
}
}
})
}
})
}
const deleteNote = async (noteId: string) => {
if (!noteId) return
try {
await checkElevatedPermission()
} catch (error) {
showNotification({
title: 'Error',
message: 'Could not elevate permissions'
})
return
}
deleteNoteMutation({
variables: { noteId },
onCompleted: () => setContent(''),
onError: (error) => {
showNotification({
color: 'red',
title: error.networkError ? 'Network error' : 'Error',
message: error.message
})
},
update: (cache, { data }) => {
const deletedNoteId = data?.deleteNote?.id
if (deletedNoteId) {
cache.modify({
fields: {
notes(existingNotes = [], { readField }) {
// @ts-ignore
return existingNotes.filter((noteRef) => noteId !== readField('id', noteRef))
}
}
})
}
}
})
}
return (
<Container>
{loading && <Loader />}
<Card shadow="sm" p="lg" m="sm">
<Title>Secret Notes</Title>
<Grid>
<Grid.Col span={10}>
<TextInput
value={content}
onChange={setContent}
autoFocus
onKeyDown={(e) => e.code === 'Enter' && add()}
/>
</Grid.Col>
<Grid.Col span={2}>
<Button
fullWidth
onClick={(e: React.MouseEvent) => {
e.preventDefault()
add()
}}
>
Add
</Button>
</Grid.Col>
</Grid>
<ul style={{ paddingLeft: 12 }}>
{data?.notes.map((note) => (
<li key={note.id}>
<Group position="apart">
<span>{note.content}</span>
<ActionIcon
size={21}
onClick={(event) => {
event.preventDefault()
deleteNote(note.id)
}}
>
<FaTrash />
</ActionIcon>
</Group>
</li>
))}
</ul>
</Card>
</Container>
)
}

View File

@@ -75,6 +75,132 @@ export type StringComparisonExp = {
_similar?: InputMaybe<Scalars['String']>;
};
export enum AuthRefreshTokenTypesEnum {
/** Personal access token */
Pat = 'pat',
/** Regular refresh token */
Regular = 'regular'
}
/** Boolean expression to compare columns of type "authRefreshTokenTypes_enum". All fields are combined with logical 'AND'. */
export type AuthRefreshTokenTypesEnumComparisonExp = {
_eq?: InputMaybe<AuthRefreshTokenTypesEnum>;
_in?: InputMaybe<Array<AuthRefreshTokenTypesEnum>>;
_is_null?: InputMaybe<Scalars['Boolean']>;
_neq?: InputMaybe<AuthRefreshTokenTypesEnum>;
_nin?: InputMaybe<Array<AuthRefreshTokenTypesEnum>>;
};
/** User refresh tokens. Hasura auth uses them to rotate new access tokens as long as the refresh token is not expired. Don't modify its structure as Hasura Auth relies on it to function properly. */
export type AuthRefreshTokens = {
__typename?: 'authRefreshTokens';
createdAt: Scalars['timestamptz'];
expiresAt: Scalars['timestamptz'];
id: Scalars['uuid'];
metadata?: Maybe<Scalars['jsonb']>;
type: AuthRefreshTokenTypesEnum;
/** An object relationship */
user: Users;
userId: Scalars['uuid'];
};
/** User refresh tokens. Hasura auth uses them to rotate new access tokens as long as the refresh token is not expired. Don't modify its structure as Hasura Auth relies on it to function properly. */
export type AuthRefreshTokensMetadataArgs = {
path?: InputMaybe<Scalars['String']>;
};
/** order by aggregate values of table "auth.refresh_tokens" */
export type AuthRefreshTokensAggregateOrderBy = {
count?: InputMaybe<OrderBy>;
max?: InputMaybe<AuthRefreshTokensMaxOrderBy>;
min?: InputMaybe<AuthRefreshTokensMinOrderBy>;
};
/** Boolean expression to filter rows from the table "auth.refresh_tokens". All fields are combined with a logical 'AND'. */
export type AuthRefreshTokensBoolExp = {
_and?: InputMaybe<Array<AuthRefreshTokensBoolExp>>;
_not?: InputMaybe<AuthRefreshTokensBoolExp>;
_or?: InputMaybe<Array<AuthRefreshTokensBoolExp>>;
createdAt?: InputMaybe<TimestamptzComparisonExp>;
expiresAt?: InputMaybe<TimestamptzComparisonExp>;
id?: InputMaybe<UuidComparisonExp>;
metadata?: InputMaybe<JsonbComparisonExp>;
type?: InputMaybe<AuthRefreshTokenTypesEnumComparisonExp>;
user?: InputMaybe<UsersBoolExp>;
userId?: InputMaybe<UuidComparisonExp>;
};
/** order by max() on columns of table "auth.refresh_tokens" */
export type AuthRefreshTokensMaxOrderBy = {
createdAt?: InputMaybe<OrderBy>;
expiresAt?: InputMaybe<OrderBy>;
id?: InputMaybe<OrderBy>;
userId?: InputMaybe<OrderBy>;
};
/** order by min() on columns of table "auth.refresh_tokens" */
export type AuthRefreshTokensMinOrderBy = {
createdAt?: InputMaybe<OrderBy>;
expiresAt?: InputMaybe<OrderBy>;
id?: InputMaybe<OrderBy>;
userId?: InputMaybe<OrderBy>;
};
/** response of any mutation on the table "auth.refresh_tokens" */
export type AuthRefreshTokensMutationResponse = {
__typename?: 'authRefreshTokens_mutation_response';
/** number of rows affected by the mutation */
affected_rows: Scalars['Int'];
/** data from the rows affected by the mutation */
returning: Array<AuthRefreshTokens>;
};
/** Ordering options when selecting data from "auth.refresh_tokens". */
export type AuthRefreshTokensOrderBy = {
createdAt?: InputMaybe<OrderBy>;
expiresAt?: InputMaybe<OrderBy>;
id?: InputMaybe<OrderBy>;
metadata?: InputMaybe<OrderBy>;
type?: InputMaybe<OrderBy>;
user?: InputMaybe<UsersOrderBy>;
userId?: InputMaybe<OrderBy>;
};
/** select columns of table "auth.refresh_tokens" */
export enum AuthRefreshTokensSelectColumn {
/** column name */
CreatedAt = 'createdAt',
/** column name */
ExpiresAt = 'expiresAt',
/** column name */
Id = 'id',
/** column name */
Metadata = 'metadata',
/** column name */
Type = 'type',
/** column name */
UserId = 'userId'
}
/** Streaming cursor of the table "authRefreshTokens" */
export type AuthRefreshTokensStreamCursorInput = {
/** Stream column input with initial value */
initial_value: AuthRefreshTokensStreamCursorValueInput;
/** cursor ordering */
ordering?: InputMaybe<CursorOrdering>;
};
/** Initial value of the column from where the streaming should start */
export type AuthRefreshTokensStreamCursorValueInput = {
createdAt?: InputMaybe<Scalars['timestamptz']>;
expiresAt?: InputMaybe<Scalars['timestamptz']>;
id?: InputMaybe<Scalars['uuid']>;
metadata?: InputMaybe<Scalars['jsonb']>;
type?: InputMaybe<AuthRefreshTokenTypesEnum>;
userId?: InputMaybe<Scalars['uuid']>;
};
/** User webauthn security keys. Don't modify its structure as Hasura Auth relies on it to function properly. */
export type AuthUserSecurityKeys = {
__typename?: 'authUserSecurityKeys';
@@ -144,6 +270,21 @@ export enum AuthUserSecurityKeysSelectColumn {
UserId = 'userId'
}
/** Streaming cursor of the table "authUserSecurityKeys" */
export type AuthUserSecurityKeysStreamCursorInput = {
/** Stream column input with initial value */
initial_value: AuthUserSecurityKeysStreamCursorValueInput;
/** cursor ordering */
ordering?: InputMaybe<CursorOrdering>;
};
/** Initial value of the column from where the streaming should start */
export type AuthUserSecurityKeysStreamCursorValueInput = {
id?: InputMaybe<Scalars['uuid']>;
nickname?: InputMaybe<Scalars['String']>;
userId?: InputMaybe<Scalars['uuid']>;
};
/** Boolean expression to compare columns of type "citext". All fields are combined with logical 'AND'. */
export type CitextComparisonExp = {
_eq?: InputMaybe<Scalars['citext']>;
@@ -177,6 +318,14 @@ export type CitextComparisonExp = {
_similar?: InputMaybe<Scalars['citext']>;
};
/** ordering argument of a cursor */
export enum CursorOrdering {
/** ascending ordering of the cursor */
Asc = 'ASC',
/** descending ordering of the cursor */
Desc = 'DESC'
}
/** columns and relationships of "storage.files" */
export type Files = {
__typename?: 'files';
@@ -264,7 +413,7 @@ export type FilesOrderBy = {
uploadedByUserId?: InputMaybe<OrderBy>;
};
/** primary key columns input for table: files */
/** primary key columns input for table: storage.files */
export type FilesPkColumnsInput = {
id: Scalars['uuid'];
};
@@ -307,6 +456,28 @@ export type FilesSetInput = {
uploadedByUserId?: InputMaybe<Scalars['uuid']>;
};
/** Streaming cursor of the table "files" */
export type FilesStreamCursorInput = {
/** Stream column input with initial value */
initial_value: FilesStreamCursorValueInput;
/** cursor ordering */
ordering?: InputMaybe<CursorOrdering>;
};
/** Initial value of the column from where the streaming should start */
export type FilesStreamCursorValueInput = {
bucketId?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
etag?: InputMaybe<Scalars['String']>;
id?: InputMaybe<Scalars['uuid']>;
isUploaded?: InputMaybe<Scalars['Boolean']>;
mimeType?: InputMaybe<Scalars['String']>;
name?: InputMaybe<Scalars['String']>;
size?: InputMaybe<Scalars['Int']>;
updatedAt?: InputMaybe<Scalars['timestamptz']>;
uploadedByUserId?: InputMaybe<Scalars['uuid']>;
};
/** update columns of table "storage.files" */
export enum FilesUpdateColumn {
/** column name */
@@ -336,6 +507,7 @@ export type FilesUpdates = {
_inc?: InputMaybe<FilesIncInput>;
/** sets the columns of the filtered rows to the given values */
_set?: InputMaybe<FilesSetInput>;
/** filter the rows which have to be updated */
where: FilesBoolExp;
};
@@ -370,6 +542,10 @@ export type JsonbComparisonExp = {
/** mutation root */
export type MutationRoot = {
__typename?: 'mutation_root';
/** delete single row from the table: "auth.refresh_tokens" */
deleteAuthRefreshToken?: Maybe<AuthRefreshTokens>;
/** delete data from the table: "auth.refresh_tokens" */
deleteAuthRefreshTokens?: Maybe<AuthRefreshTokensMutationResponse>;
/** delete single row from the table: "auth.user_security_keys" */
deleteAuthUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
/** delete data from the table: "auth.user_security_keys" */
@@ -378,14 +554,22 @@ export type MutationRoot = {
deleteFile?: Maybe<Files>;
/** delete data from the table: "storage.files" */
deleteFiles?: Maybe<FilesMutationResponse>;
/** delete single row from the table: "notes" */
deleteNote?: Maybe<Notes>;
/** delete data from the table: "notes" */
deleteNotes?: Maybe<NotesMutationResponse>;
/** delete single row from the table: "todos" */
deleteTodo?: Maybe<Todos>;
/** delete data from the table: "todos" */
deleteTodos?: Maybe<TodosMutationResponse>;
/** insert data into the table: "notes" */
inserNotes?: Maybe<NotesMutationResponse>;
/** insert a single row into the table: "storage.files" */
insertFile?: Maybe<Files>;
/** insert data into the table: "storage.files" */
insertFiles?: Maybe<FilesMutationResponse>;
/** insert a single row into the table: "notes" */
insertNote?: Maybe<Notes>;
/** insert a single row into the table: "todos" */
insertTodo?: Maybe<Todos>;
/** insert data into the table: "todos" */
@@ -394,17 +578,35 @@ export type MutationRoot = {
updateFile?: Maybe<Files>;
/** update data of the table: "storage.files" */
updateFiles?: Maybe<FilesMutationResponse>;
/** update single row of the table: "notes" */
updateNote?: Maybe<Notes>;
/** update data of the table: "notes" */
updateNotes?: Maybe<NotesMutationResponse>;
/** update single row of the table: "todos" */
updateTodo?: Maybe<Todos>;
/** update data of the table: "todos" */
updateTodos?: Maybe<TodosMutationResponse>;
/** update multiples rows of table: "storage.files" */
update_files_many?: Maybe<Array<Maybe<FilesMutationResponse>>>;
/** update multiples rows of table: "notes" */
update_notes_many?: Maybe<Array<Maybe<NotesMutationResponse>>>;
/** update multiples rows of table: "todos" */
update_todos_many?: Maybe<Array<Maybe<TodosMutationResponse>>>;
};
/** mutation root */
export type MutationRootDeleteAuthRefreshTokenArgs = {
id: Scalars['uuid'];
};
/** mutation root */
export type MutationRootDeleteAuthRefreshTokensArgs = {
where: AuthRefreshTokensBoolExp;
};
/** mutation root */
export type MutationRootDeleteAuthUserSecurityKeyArgs = {
id: Scalars['uuid'];
@@ -429,6 +631,18 @@ export type MutationRootDeleteFilesArgs = {
};
/** mutation root */
export type MutationRootDeleteNoteArgs = {
id: Scalars['uuid'];
};
/** mutation root */
export type MutationRootDeleteNotesArgs = {
where: NotesBoolExp;
};
/** mutation root */
export type MutationRootDeleteTodoArgs = {
id: Scalars['uuid'];
@@ -441,6 +655,13 @@ export type MutationRootDeleteTodosArgs = {
};
/** mutation root */
export type MutationRootInserNotesArgs = {
objects: Array<NotesInsertInput>;
on_conflict?: InputMaybe<NotesOnConflict>;
};
/** mutation root */
export type MutationRootInsertFileArgs = {
object: FilesInsertInput;
@@ -455,6 +676,13 @@ export type MutationRootInsertFilesArgs = {
};
/** mutation root */
export type MutationRootInsertNoteArgs = {
object: NotesInsertInput;
on_conflict?: InputMaybe<NotesOnConflict>;
};
/** mutation root */
export type MutationRootInsertTodoArgs = {
object: TodosInsertInput;
@@ -485,6 +713,20 @@ export type MutationRootUpdateFilesArgs = {
};
/** mutation root */
export type MutationRootUpdateNoteArgs = {
_set?: InputMaybe<NotesSetInput>;
pk_columns: NotesPkColumnsInput;
};
/** mutation root */
export type MutationRootUpdateNotesArgs = {
_set?: InputMaybe<NotesSetInput>;
where: NotesBoolExp;
};
/** mutation root */
export type MutationRootUpdateTodoArgs = {
_set?: InputMaybe<TodosSetInput>;
@@ -505,11 +747,167 @@ export type MutationRootUpdateFilesManyArgs = {
};
/** mutation root */
export type MutationRootUpdateNotesManyArgs = {
updates: Array<NotesUpdates>;
};
/** mutation root */
export type MutationRootUpdateTodosManyArgs = {
updates: Array<TodosUpdates>;
};
/** columns and relationships of "notes" */
export type Notes = {
__typename?: 'notes';
content: Scalars['String'];
createdAt: Scalars['timestamptz'];
id: Scalars['uuid'];
updatedAt: Scalars['timestamptz'];
/** An object relationship */
user: Users;
};
/** aggregated selection of "notes" */
export type NotesAggregate = {
__typename?: 'notes_aggregate';
aggregate?: Maybe<NotesAggregateFields>;
nodes: Array<Notes>;
};
/** aggregate fields of "notes" */
export type NotesAggregateFields = {
__typename?: 'notes_aggregate_fields';
count: Scalars['Int'];
max?: Maybe<NotesMaxFields>;
min?: Maybe<NotesMinFields>;
};
/** aggregate fields of "notes" */
export type NotesAggregateFieldsCountArgs = {
columns?: InputMaybe<Array<NotesSelectColumn>>;
distinct?: InputMaybe<Scalars['Boolean']>;
};
/** Boolean expression to filter rows from the table "notes". All fields are combined with a logical 'AND'. */
export type NotesBoolExp = {
_and?: InputMaybe<Array<NotesBoolExp>>;
_not?: InputMaybe<NotesBoolExp>;
_or?: InputMaybe<Array<NotesBoolExp>>;
content?: InputMaybe<StringComparisonExp>;
createdAt?: InputMaybe<TimestamptzComparisonExp>;
id?: InputMaybe<UuidComparisonExp>;
updatedAt?: InputMaybe<TimestamptzComparisonExp>;
user?: InputMaybe<UsersBoolExp>;
};
/** unique or primary key constraints on table "notes" */
export enum NotesConstraint {
/** unique or primary key constraint on columns "id" */
NotesPkey = 'notes_pkey'
}
/** input type for inserting data into table "notes" */
export type NotesInsertInput = {
content?: InputMaybe<Scalars['String']>;
};
/** aggregate max on columns */
export type NotesMaxFields = {
__typename?: 'notes_max_fields';
content?: Maybe<Scalars['String']>;
createdAt?: Maybe<Scalars['timestamptz']>;
id?: Maybe<Scalars['uuid']>;
updatedAt?: Maybe<Scalars['timestamptz']>;
};
/** aggregate min on columns */
export type NotesMinFields = {
__typename?: 'notes_min_fields';
content?: Maybe<Scalars['String']>;
createdAt?: Maybe<Scalars['timestamptz']>;
id?: Maybe<Scalars['uuid']>;
updatedAt?: Maybe<Scalars['timestamptz']>;
};
/** response of any mutation on the table "notes" */
export type NotesMutationResponse = {
__typename?: 'notes_mutation_response';
/** number of rows affected by the mutation */
affected_rows: Scalars['Int'];
/** data from the rows affected by the mutation */
returning: Array<Notes>;
};
/** on_conflict condition type for table "notes" */
export type NotesOnConflict = {
constraint: NotesConstraint;
update_columns?: Array<NotesUpdateColumn>;
where?: InputMaybe<NotesBoolExp>;
};
/** Ordering options when selecting data from "notes". */
export type NotesOrderBy = {
content?: InputMaybe<OrderBy>;
createdAt?: InputMaybe<OrderBy>;
id?: InputMaybe<OrderBy>;
updatedAt?: InputMaybe<OrderBy>;
user?: InputMaybe<UsersOrderBy>;
};
/** primary key columns input for table: notes */
export type NotesPkColumnsInput = {
id: Scalars['uuid'];
};
/** select columns of table "notes" */
export enum NotesSelectColumn {
/** column name */
Content = 'content',
/** column name */
CreatedAt = 'createdAt',
/** column name */
Id = 'id',
/** column name */
UpdatedAt = 'updatedAt'
}
/** input type for updating data in table "notes" */
export type NotesSetInput = {
content?: InputMaybe<Scalars['String']>;
};
/** Streaming cursor of the table "notes" */
export type NotesStreamCursorInput = {
/** Stream column input with initial value */
initial_value: NotesStreamCursorValueInput;
/** cursor ordering */
ordering?: InputMaybe<CursorOrdering>;
};
/** Initial value of the column from where the streaming should start */
export type NotesStreamCursorValueInput = {
content?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
id?: InputMaybe<Scalars['uuid']>;
updatedAt?: InputMaybe<Scalars['timestamptz']>;
};
/** update columns of table "notes" */
export enum NotesUpdateColumn {
/** column name */
Content = 'content'
}
export type NotesUpdates = {
/** sets the columns of the filtered rows to the given values */
_set?: InputMaybe<NotesSetInput>;
/** filter the rows which have to be updated */
where: NotesBoolExp;
};
/** column ordering options */
export enum OrderBy {
/** in ascending order, nulls last */
@@ -528,6 +926,10 @@ export enum OrderBy {
export type QueryRoot = {
__typename?: 'query_root';
/** fetch data from the table: "auth.refresh_tokens" using primary key columns */
authRefreshToken?: Maybe<AuthRefreshTokens>;
/** fetch data from the table: "auth.refresh_tokens" */
authRefreshTokens: Array<AuthRefreshTokens>;
/** fetch data from the table: "auth.user_security_keys" using primary key columns */
authUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
/** fetch data from the table: "auth.user_security_keys" */
@@ -536,6 +938,12 @@ export type QueryRoot = {
file?: Maybe<Files>;
/** fetch data from the table: "storage.files" */
files: Array<Files>;
/** fetch data from the table: "notes" using primary key columns */
note?: Maybe<Notes>;
/** fetch data from the table: "notes" */
notes: Array<Notes>;
/** fetch aggregated fields from the table: "notes" */
notesAggregate: NotesAggregate;
/** fetch data from the table: "todos" using primary key columns */
todo?: Maybe<Todos>;
/** fetch data from the table: "todos" */
@@ -549,6 +957,20 @@ export type QueryRoot = {
};
export type QueryRootAuthRefreshTokenArgs = {
id: Scalars['uuid'];
};
export type QueryRootAuthRefreshTokensArgs = {
distinct_on?: InputMaybe<Array<AuthRefreshTokensSelectColumn>>;
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
order_by?: InputMaybe<Array<AuthRefreshTokensOrderBy>>;
where?: InputMaybe<AuthRefreshTokensBoolExp>;
};
export type QueryRootAuthUserSecurityKeyArgs = {
id: Scalars['uuid'];
};
@@ -577,6 +999,29 @@ export type QueryRootFilesArgs = {
};
export type QueryRootNoteArgs = {
id: Scalars['uuid'];
};
export type QueryRootNotesArgs = {
distinct_on?: InputMaybe<Array<NotesSelectColumn>>;
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
order_by?: InputMaybe<Array<NotesOrderBy>>;
where?: InputMaybe<NotesBoolExp>;
};
export type QueryRootNotesAggregateArgs = {
distinct_on?: InputMaybe<Array<NotesSelectColumn>>;
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
order_by?: InputMaybe<Array<NotesOrderBy>>;
where?: InputMaybe<NotesBoolExp>;
};
export type QueryRootTodoArgs = {
id: Scalars['uuid'];
};
@@ -615,24 +1060,67 @@ export type QueryRootUsersArgs = {
export type SubscriptionRoot = {
__typename?: 'subscription_root';
/** fetch data from the table: "auth.refresh_tokens" using primary key columns */
authRefreshToken?: Maybe<AuthRefreshTokens>;
/** fetch data from the table: "auth.refresh_tokens" */
authRefreshTokens: Array<AuthRefreshTokens>;
/** fetch data from the table in a streaming manner: "auth.refresh_tokens" */
authRefreshTokens_stream: Array<AuthRefreshTokens>;
/** fetch data from the table: "auth.user_security_keys" using primary key columns */
authUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
/** fetch data from the table: "auth.user_security_keys" */
authUserSecurityKeys: Array<AuthUserSecurityKeys>;
/** fetch data from the table in a streaming manner: "auth.user_security_keys" */
authUserSecurityKeys_stream: Array<AuthUserSecurityKeys>;
/** fetch data from the table: "storage.files" using primary key columns */
file?: Maybe<Files>;
/** fetch data from the table: "storage.files" */
files: Array<Files>;
/** fetch data from the table in a streaming manner: "storage.files" */
files_stream: Array<Files>;
/** fetch data from the table: "notes" using primary key columns */
note?: Maybe<Notes>;
/** fetch data from the table: "notes" */
notes: Array<Notes>;
/** fetch aggregated fields from the table: "notes" */
notesAggregate: NotesAggregate;
/** fetch data from the table in a streaming manner: "notes" */
notes_stream: Array<Notes>;
/** fetch data from the table: "todos" using primary key columns */
todo?: Maybe<Todos>;
/** fetch data from the table: "todos" */
todos: Array<Todos>;
/** fetch aggregated fields from the table: "todos" */
todosAggregate: TodosAggregate;
/** fetch data from the table in a streaming manner: "todos" */
todos_stream: Array<Todos>;
/** fetch data from the table: "auth.users" using primary key columns */
user?: Maybe<Users>;
/** fetch data from the table: "auth.users" */
users: Array<Users>;
/** fetch data from the table in a streaming manner: "auth.users" */
users_stream: Array<Users>;
};
export type SubscriptionRootAuthRefreshTokenArgs = {
id: Scalars['uuid'];
};
export type SubscriptionRootAuthRefreshTokensArgs = {
distinct_on?: InputMaybe<Array<AuthRefreshTokensSelectColumn>>;
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
order_by?: InputMaybe<Array<AuthRefreshTokensOrderBy>>;
where?: InputMaybe<AuthRefreshTokensBoolExp>;
};
export type SubscriptionRootAuthRefreshTokensStreamArgs = {
batch_size: Scalars['Int'];
cursor: Array<InputMaybe<AuthRefreshTokensStreamCursorInput>>;
where?: InputMaybe<AuthRefreshTokensBoolExp>;
};
@@ -650,6 +1138,13 @@ export type SubscriptionRootAuthUserSecurityKeysArgs = {
};
export type SubscriptionRootAuthUserSecurityKeysStreamArgs = {
batch_size: Scalars['Int'];
cursor: Array<InputMaybe<AuthUserSecurityKeysStreamCursorInput>>;
where?: InputMaybe<AuthUserSecurityKeysBoolExp>;
};
export type SubscriptionRootFileArgs = {
id: Scalars['uuid'];
};
@@ -664,6 +1159,43 @@ export type SubscriptionRootFilesArgs = {
};
export type SubscriptionRootFilesStreamArgs = {
batch_size: Scalars['Int'];
cursor: Array<InputMaybe<FilesStreamCursorInput>>;
where?: InputMaybe<FilesBoolExp>;
};
export type SubscriptionRootNoteArgs = {
id: Scalars['uuid'];
};
export type SubscriptionRootNotesArgs = {
distinct_on?: InputMaybe<Array<NotesSelectColumn>>;
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
order_by?: InputMaybe<Array<NotesOrderBy>>;
where?: InputMaybe<NotesBoolExp>;
};
export type SubscriptionRootNotesAggregateArgs = {
distinct_on?: InputMaybe<Array<NotesSelectColumn>>;
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
order_by?: InputMaybe<Array<NotesOrderBy>>;
where?: InputMaybe<NotesBoolExp>;
};
export type SubscriptionRootNotesStreamArgs = {
batch_size: Scalars['Int'];
cursor: Array<InputMaybe<NotesStreamCursorInput>>;
where?: InputMaybe<NotesBoolExp>;
};
export type SubscriptionRootTodoArgs = {
id: Scalars['uuid'];
};
@@ -687,6 +1219,13 @@ export type SubscriptionRootTodosAggregateArgs = {
};
export type SubscriptionRootTodosStreamArgs = {
batch_size: Scalars['Int'];
cursor: Array<InputMaybe<TodosStreamCursorInput>>;
where?: InputMaybe<TodosBoolExp>;
};
export type SubscriptionRootUserArgs = {
id: Scalars['uuid'];
};
@@ -700,6 +1239,13 @@ export type SubscriptionRootUsersArgs = {
where?: InputMaybe<UsersBoolExp>;
};
export type SubscriptionRootUsersStreamArgs = {
batch_size: Scalars['Int'];
cursor: Array<InputMaybe<UsersStreamCursorInput>>;
where?: InputMaybe<UsersBoolExp>;
};
/** Boolean expression to compare columns of type "timestamptz". All fields are combined with logical 'AND'. */
export type TimestamptzComparisonExp = {
_eq?: InputMaybe<Scalars['timestamptz']>;
@@ -842,6 +1388,23 @@ export type TodosSetInput = {
contents?: InputMaybe<Scalars['String']>;
};
/** Streaming cursor of the table "todos" */
export type TodosStreamCursorInput = {
/** Stream column input with initial value */
initial_value: TodosStreamCursorValueInput;
/** cursor ordering */
ordering?: InputMaybe<CursorOrdering>;
};
/** Initial value of the column from where the streaming should start */
export type TodosStreamCursorValueInput = {
contents?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
id?: InputMaybe<Scalars['uuid']>;
updatedAt?: InputMaybe<Scalars['timestamptz']>;
userId?: InputMaybe<Scalars['uuid']>;
};
/** update columns of table "todos" */
export enum TodosUpdateColumn {
/** column name */
@@ -851,6 +1414,7 @@ export enum TodosUpdateColumn {
export type TodosUpdates = {
/** sets the columns of the filtered rows to the given values */
_set?: InputMaybe<TodosSetInput>;
/** filter the rows which have to be updated */
where: TodosBoolExp;
};
@@ -876,6 +1440,8 @@ export type Users = {
phoneNumber?: Maybe<Scalars['String']>;
phoneNumberVerified: Scalars['Boolean'];
/** An array relationship */
refreshTokens: Array<AuthRefreshTokens>;
/** An array relationship */
securityKeys: Array<AuthUserSecurityKeys>;
updatedAt: Scalars['timestamptz'];
};
@@ -887,6 +1453,16 @@ export type UsersMetadataArgs = {
};
/** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
export type UsersRefreshTokensArgs = {
distinct_on?: InputMaybe<Array<AuthRefreshTokensSelectColumn>>;
limit?: InputMaybe<Scalars['Int']>;
offset?: InputMaybe<Scalars['Int']>;
order_by?: InputMaybe<Array<AuthRefreshTokensOrderBy>>;
where?: InputMaybe<AuthRefreshTokensBoolExp>;
};
/** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
export type UsersSecurityKeysArgs = {
distinct_on?: InputMaybe<Array<AuthUserSecurityKeysSelectColumn>>;
@@ -919,6 +1495,7 @@ export type UsersBoolExp = {
otpMethodLastUsed?: InputMaybe<StringComparisonExp>;
phoneNumber?: InputMaybe<StringComparisonExp>;
phoneNumberVerified?: InputMaybe<BooleanComparisonExp>;
refreshTokens?: InputMaybe<AuthRefreshTokensBoolExp>;
securityKeys?: InputMaybe<AuthUserSecurityKeysBoolExp>;
updatedAt?: InputMaybe<TimestamptzComparisonExp>;
};
@@ -943,6 +1520,7 @@ export type UsersOrderBy = {
otpMethodLastUsed?: InputMaybe<OrderBy>;
phoneNumber?: InputMaybe<OrderBy>;
phoneNumberVerified?: InputMaybe<OrderBy>;
refreshTokens_aggregate?: InputMaybe<AuthRefreshTokensAggregateOrderBy>;
securityKeys_aggregate?: InputMaybe<AuthUserSecurityKeysAggregateOrderBy>;
updatedAt?: InputMaybe<OrderBy>;
};
@@ -989,6 +1567,37 @@ export enum UsersSelectColumn {
UpdatedAt = 'updatedAt'
}
/** Streaming cursor of the table "users" */
export type UsersStreamCursorInput = {
/** Stream column input with initial value */
initial_value: UsersStreamCursorValueInput;
/** cursor ordering */
ordering?: InputMaybe<CursorOrdering>;
};
/** Initial value of the column from where the streaming should start */
export type UsersStreamCursorValueInput = {
activeMfaType?: InputMaybe<Scalars['String']>;
avatarUrl?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
currentChallenge?: InputMaybe<Scalars['String']>;
defaultRole?: InputMaybe<Scalars['String']>;
disabled?: InputMaybe<Scalars['Boolean']>;
displayName?: InputMaybe<Scalars['String']>;
email?: InputMaybe<Scalars['citext']>;
emailVerified?: InputMaybe<Scalars['Boolean']>;
id?: InputMaybe<Scalars['uuid']>;
isAnonymous?: InputMaybe<Scalars['Boolean']>;
lastSeen?: InputMaybe<Scalars['timestamptz']>;
locale?: InputMaybe<Scalars['String']>;
metadata?: InputMaybe<Scalars['jsonb']>;
otpHash?: InputMaybe<Scalars['String']>;
otpMethodLastUsed?: InputMaybe<Scalars['String']>;
phoneNumber?: InputMaybe<Scalars['String']>;
phoneNumberVerified?: InputMaybe<Scalars['Boolean']>;
updatedAt?: InputMaybe<Scalars['timestamptz']>;
};
/** Boolean expression to compare columns of type "uuid". All fields are combined with logical 'AND'. */
export type UuidComparisonExp = {
_eq?: InputMaybe<Scalars['uuid']>;
@@ -1016,6 +1625,27 @@ export type AddItemMutation = { __typename?: 'mutation_root', insertTodo?: { __t
export type NewTodoFragment = { __typename?: 'todos', id: string, contents: string };
export type NotesListQueryVariables = Exact<{ [key: string]: never; }>;
export type NotesListQuery = { __typename?: 'query_root', notes: Array<{ __typename?: 'notes', id: string, content: string }> };
export type InsertNoteMutationVariables = Exact<{
content: Scalars['String'];
}>;
export type InsertNoteMutation = { __typename?: 'mutation_root', insertNote?: { __typename?: 'notes', id: string, content: string } | null };
export type DeleteNoteMutationVariables = Exact<{
noteId: Scalars['uuid'];
}>;
export type DeleteNoteMutation = { __typename?: 'mutation_root', deleteNote?: { __typename?: 'notes', id: string, content: string } | null };
export type NewNoteFragment = { __typename?: 'notes', id: string, content: string };
export type SecurityKeysQueryVariables = Exact<{
userId: Scalars['uuid'];
}>;

View File

@@ -2,11 +2,25 @@ import { useState } from 'react'
import { Button, Card, Grid, TextInput, Title } from '@mantine/core'
import { showNotification } from '@mantine/notifications'
import { useChangeEmail, useUserEmail } from '@nhost/react'
import { useChangeEmail, useElevateSecurityKeyEmail, useUserEmail, useUserId } from '@nhost/react'
import { useAuthQuery } from '@nhost/react-apollo'
import { SecurityKeysQuery } from 'src/generated'
import { SECURITY_KEYS_LIST } from 'src/utils'
export const ChangeEmail: React.FC = () => {
const [newEmail, setNewEmail] = useState('')
const userId = useUserId()
const email = useUserEmail()
const [newEmail, setNewEmail] = useState('')
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
const [userHasSecurityKey, setUserHasSecurityKey] = useState(false)
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
variables: { userId },
onCompleted: ({ authUserSecurityKeys }) => {
setUserHasSecurityKey(authUserSecurityKeys?.length > 0)
}
})
const { changeEmail } = useChangeEmail({
redirectTo: '/profile'
})
@@ -19,7 +33,26 @@ export const ChangeEmail: React.FC = () => {
})
return
}
if (!elevated && userHasSecurityKey) {
try {
const { elevated } = await elevateEmailSecurityKey(email as string)
if (!elevated) {
throw new Error('Permissions were not elevated')
}
} catch (error) {
showNotification({
title: 'Error',
message: 'Could not elevate permissions'
})
return
}
}
const result = await changeEmail(newEmail)
if (result.needsEmailVerification) {
showNotification({
message: `An email has been sent to ${newEmail}. Please check your inbox and follow the link to confirm the email change.`
@@ -33,6 +66,7 @@ export const ChangeEmail: React.FC = () => {
})
}
}
return (
<Card shadow="sm" p="lg" m="sm">
<Title>Change email</Title>

View File

@@ -1,14 +1,53 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { Button, Card, Grid, PasswordInput, Title } from '@mantine/core'
import { showNotification } from '@mantine/notifications'
import { useChangePassword } from '@nhost/react'
import {
useChangePassword,
useElevateSecurityKeyEmail,
useUserEmail,
useUserId
} from '@nhost/react'
import { SecurityKeysQuery } from 'src/generated'
import { SECURITY_KEYS_LIST } from 'src/utils'
import { useAuthQuery } from '@nhost/react-apollo'
export const ChangePassword: React.FC = () => {
const userEmail = useUserEmail()
const userId = useUserId()
const [password, setPassword] = useState('')
const { changePassword } = useChangePassword()
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
const [userHasSecurityKey, setUserHasSecurityKey] = useState(false)
const { data } = useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, { variables: { userId } })
useEffect(() => {
const authUserSecurityKeys = data?.authUserSecurityKeys
if (authUserSecurityKeys) {
setUserHasSecurityKey(authUserSecurityKeys.length > 0)
}
}, [data])
const change = async () => {
if (!elevated && userHasSecurityKey) {
try {
const { elevated } = await elevateEmailSecurityKey(userEmail as string)
if (!elevated) {
throw new Error('Permissions were not elevated')
}
} catch (error) {
showNotification({
title: 'Error',
message: 'Could not elevate permissions'
})
return
}
}
const result = await changePassword(password)
if (result.isSuccess) {
showNotification({

View File

@@ -2,11 +2,56 @@ import { useState } from 'react'
import { Button, Card, TextInput, Title } from '@mantine/core'
import { showNotification } from '@mantine/notifications'
import { useConfigMfa } from '@nhost/react'
import { useConfigMfa, useElevateSecurityKeyEmail, useUserEmail, useUserId } from '@nhost/react'
import { SECURITY_KEYS_LIST } from 'src/utils'
import { SecurityKeysQuery } from 'src/generated'
import { useAuthQuery } from '@nhost/react-apollo'
export const Mfa: React.FC = () => {
const userId = useUserId()
const userEmail = useUserEmail()
const [code, setCode] = useState('')
const { generateQrCode, activateMfa, isActivated, isGenerated, qrCodeDataUrl } = useConfigMfa()
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
const [userHasSecurityKey, setUserHasSecurityKey] = useState(false)
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
variables: { userId },
onCompleted: ({ authUserSecurityKeys }) => {
setUserHasSecurityKey(authUserSecurityKeys?.length > 0)
}
})
const activate = async () => {
if (!elevated && userHasSecurityKey) {
try {
const { elevated } = await elevateEmailSecurityKey(userEmail as string)
if (!elevated) {
throw new Error('Permissions were not elevated')
}
} catch (error) {
showNotification({
title: 'Error',
message: 'Could not elevate permissions'
})
return
}
}
const { error, isError } = await activateMfa(code)
if (isError) {
showNotification({
color: 'red',
title: 'Error',
message: error?.message
})
}
}
const generate = async () => {
const result = await generateQrCode()
if (result.error) {
@@ -33,7 +78,7 @@ export const Mfa: React.FC = () => {
onChange={(e) => setCode(e.target.value)}
placeholder="Enter activation code"
/>
<Button fullWidth onClick={() => activateMfa(code)}>
<Button fullWidth onClick={activate}>
Activate
</Button>
</div>

View File

@@ -2,36 +2,22 @@ import { useState } from 'react'
import { FaMinus } from 'react-icons/fa'
import { RemoveSecurityKeyMutation, SecurityKeysQuery } from 'src/generated'
import { gql, useMutation } from '@apollo/client'
import { ApolloError, useApolloClient, useMutation } from '@apollo/client'
import { ActionIcon, Button, Card, SimpleGrid, Table, TextInput, Title } from '@mantine/core'
import { useInputState } from '@mantine/hooks'
import { showNotification } from '@mantine/notifications'
import { useAddSecurityKey, useUserId } from '@nhost/react'
import { useAuthQuery } from '@nhost/react-apollo'
const SECURITY_KEYS_LIST = gql`
query securityKeys($userId: uuid!) {
authUserSecurityKeys(where: { userId: { _eq: $userId } }) {
id
nickname
}
}
`
const REMOVE_SECURITY_KEY = gql`
mutation removeSecurityKey($id: uuid!) {
deleteAuthUserSecurityKey(id: $id) {
id
}
}
`
import { REMOVE_SECURITY_KEY, SECURITY_KEYS_LIST } from 'src/utils'
export const SecurityKeys: React.FC = () => {
const { add } = useAddSecurityKey()
const userId = useUserId()
const client = useApolloClient()
const { add } = useAddSecurityKey()
// Nickname of the security key
const [nickname, setNickname] = useInputState('')
const [list, setList] = useState<{ id: string; nickname?: string | null }[]>([])
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
variables: { userId },
onCompleted: ({ authUserSecurityKeys }) => {
@@ -43,9 +29,10 @@ export const SecurityKeys: React.FC = () => {
const addKey = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const { key, isError, error } = await add(nickname)
if (isError) {
console.log(error)
showNotification({
color: 'red',
title: 'Error',
@@ -53,11 +40,17 @@ export const SecurityKeys: React.FC = () => {
})
} else {
setNickname('')
// refetch securityKeys so that we know if need to elevate in other components
await client.refetchQueries({
include: [SECURITY_KEYS_LIST]
})
}
if (key) {
setList([...list, key])
}
}
const [removeKey] = useMutation<RemoveSecurityKeyMutation>(REMOVE_SECURITY_KEY, {
onCompleted: ({ deleteAuthUserSecurityKey }) => {
if (deleteAuthUserSecurityKey?.id) {
@@ -66,6 +59,25 @@ export const SecurityKeys: React.FC = () => {
}
})
const handleRemoveKey = async (id: string) => {
try {
await removeKey({ variables: { id } })
// refetch securityKeys so that we know if need to elevate in other components
await client.refetchQueries({
include: [SECURITY_KEYS_LIST]
})
} catch (error) {
const e = error as ApolloError
showNotification({
color: 'red',
title: 'Error',
message: e?.message
})
}
}
return (
<Card shadow="sm" p="lg" m="sm">
<Title>Security keys</Title>
@@ -79,7 +91,7 @@ export const SecurityKeys: React.FC = () => {
<tr key={id}>
<td>{nickname || id}</td>
<td>
<ActionIcon onClick={() => removeKey({ variables: { id } })} color="red">
<ActionIcon onClick={() => handleRemoveKey(id)} color="red">
<FaMinus />
</ActionIcon>
</td>

View File

@@ -0,0 +1 @@
export * from './security-keys'

View File

@@ -0,0 +1,18 @@
import { gql } from '@apollo/client'
export const SECURITY_KEYS_LIST = gql`
query securityKeys($userId: uuid!) {
authUserSecurityKeys(where: { userId: { _eq: $userId } }) {
id
nickname
}
}
`
export const REMOVE_SECURITY_KEY = gql`
mutation removeSecurityKey($id: uuid!) {
deleteAuthUserSecurityKey(id: $id) {
id
}
}
`

View File

@@ -1,5 +1,5 @@
import { defineConfig } from 'vite'
import path from 'path'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
@@ -8,5 +8,10 @@ export default defineConfig({
include: ['react/jsx-runtime'],
exclude: ['@nhost/react']
},
resolve: {
alias: {
src: path.resolve(__dirname, './src')
}
},
plugins: [react()]
})

View File

@@ -1,5 +1,27 @@
# @nhost-examples/react-gqty
## 1.0.4
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
## 1.0.3
### Patch Changes
- @nhost/react@3.1.1
## 1.0.2
### Patch Changes
- e5bab6a: chore: update dependencies
- Updated dependencies [1a61c65]
- Updated dependencies [e5bab6a]
- @nhost/react@3.1.0
## 1.0.1
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/react-gqty",
"private": true,
"version": "1.0.1",
"version": "1.0.4",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,7 +11,7 @@
},
"dependencies": {
"@gqty/react": "2.1.0",
"@nhost/react": "^3.0.2",
"@nhost/react": "workspace:^",
"gqty": "^2.3.0",
"graphql": "16.8.1",
"react": "18.2.0",
@@ -20,10 +20,10 @@
"devDependencies": {
"@gqty/cli": "3.3.0-alpha-d8cdbf6.0",
"@tailwindcss/forms": "^0.5.7",
"@types/react": "^18.2.47",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^3.1.0",
"autoprefixer": "^10.4.16",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"typescript": "^4.9.5",

View File

@@ -1,5 +1,13 @@
# @nhost-examples/serverless-functions
## 0.0.11
### Patch Changes
- 8c34c69: chore: update nodemailer version to `6.9.9`
- Updated dependencies [e5bab6a]
- @nhost/stripe-graphql-js@1.0.7
## 0.0.10
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/serverless-functions",
"private": true,
"version": "0.0.10",
"version": "0.0.11",
"devDependencies": {
"@types/express": "^4.17.21"
},
@@ -11,7 +11,7 @@
"@pothos/core": "^3.41.0",
"cross-fetch": "^3.1.8",
"graphql": "16.8.1",
"nodemailer": "^6.9.8",
"nodemailer": "^6.9.9",
"slugify": "^1.6.6",
"stripe": "^11.18.0"
}

View File

@@ -1,5 +1,41 @@
# @nhost-examples/vue-apollo
## 0.2.0
### Minor Changes
- 017f1a6: feat: add elevated permission examples
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/vue@2.2.0
- @nhost/nhost-js@3.0.5
- @nhost/apollo@6.0.5
## 0.1.1
### Patch Changes
- @nhost/nhost-js@3.0.4
- @nhost/apollo@6.0.4
- @nhost/vue@2.1.1
## 0.1.0
### Minor Changes
- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with integration into react-apollo and vue-apollo examples
### Patch Changes
- e5bab6a: chore: update dependencies
- Updated dependencies [1a61c65]
- Updated dependencies [e5bab6a]
- @nhost/vue@2.1.0
- @nhost/apollo@6.0.3
- @nhost/nhost-js@3.0.3
## 0.0.10
### Patch Changes

View File

@@ -0,0 +1 @@
{}

View File

@@ -2,8 +2,14 @@ table:
name: provider_requests
schema: auth
configuration:
column_config: {}
custom_column_names: {}
column_config:
id:
custom_name: id
options:
custom_name: options
custom_column_names:
id: id
options: options
custom_name: authProviderRequests
custom_root_fields:
delete: deleteAuthProviderRequests

View File

@@ -2,8 +2,11 @@ table:
name: providers
schema: auth
configuration:
column_config: {}
custom_column_names: {}
column_config:
id:
custom_name: id
custom_column_names:
id: id
custom_name: authProviders
custom_root_fields:
delete: deleteAuthProviders

View File

@@ -0,0 +1,26 @@
table:
name: refresh_token_types
schema: auth
is_enum: true
configuration:
column_config: {}
custom_column_names: {}
custom_name: authRefreshTokenTypes
custom_root_fields:
delete: deleteAuthRefreshTokenTypes
delete_by_pk: deleteAuthRefreshTokenType
insert: insertAuthRefreshTokenTypes
insert_one: insertAuthRefreshTokenType
select: authRefreshTokenTypes
select_aggregate: authRefreshTokenTypesAggregate
select_by_pk: authRefreshTokenType
update: updateAuthRefreshTokenTypes
update_by_pk: updateAuthRefreshTokenType
array_relationships:
- name: refreshTokens
using:
foreign_key_constraint_on:
column: type
table:
name: refresh_tokens
schema: auth

View File

@@ -7,14 +7,14 @@ configuration:
custom_name: createdAt
expires_at:
custom_name: expiresAt
refresh_token:
custom_name: refreshToken
refresh_token_hash:
custom_name: refreshTokenHash
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
expires_at: expiresAt
refresh_token: refreshToken
refresh_token_hash: refreshTokenHash
user_id: userId
custom_name: authRefreshTokens
custom_root_fields:
@@ -31,3 +31,25 @@ object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id
select_permissions:
- role: user
permission:
columns:
- id
- created_at
- expires_at
- metadata
- type
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
delete_permissions:
- role: user
permission:
filter:
_and:
- user_id:
_eq: X-Hasura-User-Id
- type:
_eq: pat

View File

@@ -2,8 +2,11 @@ table:
name: roles
schema: auth
configuration:
column_config: {}
custom_column_names: {}
column_config:
role:
custom_name: role
custom_column_names:
role: role
custom_name: authRoles
custom_root_fields:
delete: deleteAuthRoles

Some files were not shown because too many files have changed in this diff Show More