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 # @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 ## 1.6.4
### Patch Changes ### Patch Changes

View File

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

View File

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

View File

@@ -46,7 +46,16 @@ export default function ErrorToast({
error, 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 ( return (
<AnimatePresence> <AnimatePresence>

View File

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

View File

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

View File

@@ -1,5 +1,22 @@
# @nhost/docs # @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 ## 2.2.0
### Minor Changes ### 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"> <Step title="Start nhost">
Run `nhost up`: 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. After starting the service the first thing you will notice is that there is a new `ai` service running.
</Step> </Step>
<Step title="Commit metadata changes"> <Step title="Commit metadata changes">
As you start the AI service metadata changes may be proposed: 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. We strongly recommmend you to commit them to your git repository so they can be deployed alongside your application.
</Step> </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: 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: 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. Pushing them to your deployment branch will also deploy them to your cloud project.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
--- ---
title: Resources title: Resources
description: description description: Learn about compute resources
icon: gauge-max 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", "group": "AI",
"pages": [ "pages": [
"guides/ai/enabling-service", "guides/ai/enabling-service",
"guides/ai/local_development", "guides/ai/local-development",
"guides/ai/auto-embeddings", "guides/ai/auto-embeddings",
"guides/ai/assistants", "guides/ai/assistants",
"guides/ai/dev-assistant" "guides/ai/dev-assistant"
@@ -319,12 +319,13 @@
"group": "Storage", "group": "Storage",
"pages": [ "pages": [
"reference/javascript/storage/hasura-storage-client", "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-presigned-url",
"reference/javascript/storage/get-public-url", "reference/javascript/storage/get-public-url",
"reference/javascript/storage/delete",
"reference/javascript/storage/set-access-token", "reference/javascript/storage/set-access-token",
"reference/javascript/storage/set-admin-secret", "reference/javascript/storage/set-admin-secret"
"reference/javascript/storage/upload"
] ]
}, },
{ {
@@ -335,6 +336,14 @@
"reference/javascript/graphql/set-access-token", "reference/javascript/graphql/set-access-token",
"reference/javascript/graphql/request" "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", "name": "@nhost/docs",
"version": "2.2.0", "version": "2.4.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "mintlify dev" "start": "mintlify dev"
}, },
"devDependencies": { "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"> <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. 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 ```toml
[[hasura.resources.networking.ingresses]] [[hasura.resources.networking.ingresses]]
@@ -34,14 +34,15 @@ fqdn = ['hasura.custom-domain.com']
[[auth.resources.networking.ingresses]] [[auth.resources.networking.ingresses]]
fqdn = ['auth.custom-domain.com'] fqdn = ['auth.custom-domain.com']
[[postgres.resources.networking.ingresses]]
fqdn = ['postgres.custom-domain.com']
[[functions.resources.networking.ingresses]] [[functions.resources.networking.ingresses]]
fqdn = ['functions.custom-domain.com'] 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 ```toml
name = 'my-service' name = 'my-service'

View File

@@ -1,12 +1,14 @@
--- ---
title: Functions title: Functions
description: Server-side code that works as API endpoints description: Server-side code that works as API endpoints
icon: code 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. 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 ## Hello World
@@ -21,7 +23,5 @@ Deploying functions is as easy as pushing your code!
## Additional Resources ## Additional Resources
<CardGroup cols={2}> <CardGroup cols={2}>
<Card title="Learn how to use Functions" href="/guides/functions/getting-started"> <Card title="Learn how to use Functions" href="/guides/functions/overview"></Card>
</Card>
</CardGroup> </CardGroup>

View File

@@ -9,10 +9,10 @@ Note: The Nhost client automatically refreshes the session when the user is auth
```ts ```ts
// Refresh the session with the the current internal refresh token. // Refresh the session with the the current internal refresh token.
nhost.auth.refreshToken() nhost.auth.refreshSession()
// Refresh the session with an external refresh token. // Refresh the session with an external refresh token.
nhost.auth.refreshToken(refreshToken) nhost.auth.refreshSession(refreshToken)
``` ```
## Parameters ## 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: servers:
- url: https://local.auth.nhost.run/v1 - url: https://local.auth.nhost.run/v1
description: API Server description: API Server
security:
- AdminSecret: []
- BearerAuth: []
components: components:
securitySchemes: securitySchemes:
AdminSecret:
type: apiKey
in: header
name: X-Hasura-Admin-Secret
description: Hasura Admin Secret
BearerAuth: BearerAuth:
scheme: bearer scheme: bearer
type: http type: http
@@ -1524,7 +1515,6 @@ paths:
schema: schema:
$ref: '#/components/schemas/DisabledEndpointError' $ref: '#/components/schemas/DisabledEndpointError'
description: The feature is not activated description: The feature is not activated
security: []
summary: Sign In TOTP summary: Sign In TOTP
tags: tags:
- Authentication - Authentication

View File

@@ -1,5 +1,24 @@
# @nhost-examples/cli # @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 ## 0.1.3
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,30 @@
# @nhost-examples/codegen-react-apollo # @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 ## 0.1.11
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,27 @@
# @nhost-examples/codegen-react-query # @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 ## 0.1.12
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,30 @@
# @nhost-examples/react-urql # @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 ## 0.0.8
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,24 @@
# @nhost-examples/multi-tenant-one-to-many # @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 ## 2.0.1
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,34 @@
# @nhost-examples/nextjs # @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 ## 0.1.13
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,24 @@
# @nhost-examples/node-storage # @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 ## 0.0.5
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# @nhost-examples/nextjs-server-components # @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 ## 0.1.5
### Patch Changes ### Patch Changes

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,20 @@ const TodoItem = ({ todo }: { todo: Todo }) => {
await deleteTodo(todo.id) 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 ( return (
<div <div
className={twMerge( className={twMerge(
@@ -74,6 +88,12 @@ const TodoItem = ({ todo }: { todo: Todo }) => {
</Link> </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"> <button onClick={handleDeleteTodo} className="w-6 h-6">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,38 @@
# @nhost-examples/react-apollo # @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 ## 0.1.18
### Patch Changes ### Patch Changes

View File

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

View File

@@ -46,4 +46,4 @@ delete_permissions:
permission: permission:
filter: filter:
user_id: 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_roles.yaml"
- "!include auth_user_security_keys.yaml" - "!include auth_user_security_keys.yaml"
- "!include auth_users.yaml" - "!include auth_users.yaml"
- "!include public_notes.yaml"
- "!include public_todos.yaml" - "!include public_todos.yaml"
- "!include storage_buckets.yaml" - "!include storage_buckets.yaml"
- "!include storage_files.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] [global]
[hasura] [hasura]
version = 'v2.25.1-ce' version = 'v2.33.4-ce'
adminSecret = '{{ secrets.HASURA_GRAPHQL_ADMIN_SECRET }}' adminSecret = '{{ secrets.HASURA_GRAPHQL_ADMIN_SECRET }}'
webhookSecret = '{{ secrets.NHOST_WEBHOOK_SECRET }}' webhookSecret = '{{ secrets.NHOST_WEBHOOK_SECRET }}'
@@ -28,11 +28,14 @@ httpPoolSize = 100
version = 18 version = 18
[auth] [auth]
version = '0.22.1' version = '0.26.0'
[auth.elevatedPrivileges]
mode = 'required'
[auth.redirections] [auth.redirections]
clientUrl = 'https://react-apollo.example.nhost.io/' 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] [auth.signUp]
enabled = true enabled = true
@@ -149,12 +152,12 @@ enabled = true
issuer = 'nhost' issuer = 'nhost'
[postgres] [postgres]
version = '14.6-20230406-2' version = '14.6-20240129-1'
[provider] [provider]
[storage] [storage]
version = '0.3.4' version = '0.6.0'
[observability] [observability]
[observability.grafana] [observability.grafana]

View File

@@ -1,22 +1,22 @@
{ {
"name": "@nhost-examples/react-apollo", "name": "@nhost-examples/react-apollo",
"version": "0.1.18", "version": "0.3.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.8.9", "@apollo/client": "^3.9.4",
"@mantine/core": "^4.2.12", "@mantine/core": "^4.2.12",
"@mantine/dropzone": "^4.2.12", "@mantine/dropzone": "^4.2.12",
"@mantine/hooks": "^4.2.12", "@mantine/hooks": "^4.2.12",
"@mantine/notifications": "^4.2.12", "@mantine/notifications": "^4.2.12",
"@mantine/prism": "^4.2.12", "@mantine/prism": "^4.2.12",
"@nhost/react": "^3.0.2", "@nhost/react": "workspace:^",
"@nhost/react-apollo": "^7.0.2", "@nhost/react-apollo": "workspace:^",
"graphql": "16.8.1", "graphql": "16.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-router": "^6.21.2", "react-router": "^6.21.3",
"react-router-dom": "^6.21.2", "react-router-dom": "^6.21.3",
"tabler-icons-react": "^1.56.0" "tabler-icons-react": "^1.56.0"
}, },
"scripts": { "scripts": {
@@ -54,12 +54,12 @@
"@nuintun/qrcode": "^3.4.0", "@nuintun/qrcode": "^3.4.0",
"@playwright/test": "1.31.0", "@playwright/test": "1.31.0",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react": "^18.2.47", "@types/react": "^18.2.50",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.18",
"@types/totp-generator": "^0.0.4", "@types/totp-generator": "^0.0.4",
"@vitejs/plugin-react": "^3.1.0", "@vitejs/plugin-react": "^3.1.0",
"@xstate/inspect": "^0.6.5", "@xstate/inspect": "^0.6.5",
"dotenv": "^16.3.1", "dotenv": "^16.4.1",
"jsqr": "^1.4.0", "jsqr": "^1.4.0",
"pngjs": "^7.0.0", "pngjs": "^7.0.0",
"totp-generator": "^0.0.13", "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 { StoragePage } from './Storage'
import './App.css?inline' import './App.css?inline'
import { NotesPage } from './components/notes'
const title = 'Nhost with React and Apollo' const title = 'Nhost with React and Apollo'
function App() { function App() {
@@ -94,6 +95,16 @@ function App() {
</AuthGate> </AuthGate>
} }
/> />
<Route
path="/secret-notes"
element={
<AuthGate>
<NotesPage />
</AuthGate>
}
/>
<Route <Route
path="/storage" path="/storage"
element={ 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 { SiApollographql } from 'react-icons/si'
import { useLocation, useNavigate } from 'react-router' import { useLocation, useNavigate } from 'react-router'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { showNotification } from '@mantine/notifications'
import { Group, MantineColor, Navbar, Text, ThemeIcon, UnstyledButton } from '@mantine/core' import {
import { useAuthenticated, useSignOut } from '@nhost/react' Button,
Card,
Group,
MantineColor,
Navbar,
Text,
ThemeIcon,
UnstyledButton
} from '@mantine/core'
import { useAuthenticated, useElevateSecurityKeyEmail, useSignOut, useUserData } from '@nhost/react'
interface MenuItemProps { interface MenuItemProps {
icon: React.ReactNode icon: React.ReactNode
color?: MantineColor color?: MantineColor
@@ -52,16 +62,52 @@ const MenuItem: React.FC<MenuItemProps> = ({ icon, color, label, link, action })
const data: MenuItemProps[] = [ const data: MenuItemProps[] = [
{ icon: <FaHouseUser size={16} />, label: 'Home', link: '/' }, { icon: <FaHouseUser size={16} />, label: 'Home', link: '/' },
{ icon: <FaHouseUser size={16} />, label: 'Profile', link: '/profile' }, { 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: <FaFile size={16} />, label: 'Storage', link: '/storage' },
{ icon: <SiApollographql size={16} />, label: 'Apollo', link: '/apollo' }, { icon: <SiApollographql size={16} />, label: 'Apollo', link: '/apollo' },
{ icon: <FaQuestion size={16} />, label: 'About', link: '/about' } { icon: <FaQuestion size={16} />, label: 'About', link: '/about' }
] ]
export default function NavBar() { export default function NavBar() {
const authenticated = useAuthenticated() const userData = useUserData()
const { signOut } = useSignOut()
const navigate = useNavigate() 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} />) const links = data.map((link) => <MenuItem {...link} key={link.label} />)
return ( return (
<Navbar width={{ sm: 300, lg: 400, base: 100 }} aria-label="main navigation"> <Navbar width={{ sm: 300, lg: 400, base: 100 }} aria-label="main navigation">
<Navbar.Section grow mt="md"> <Navbar.Section grow mt="md">
@@ -77,6 +123,13 @@ export default function NavBar() {
/> />
)} )}
</Navbar.Section> </Navbar.Section>
<Card p="lg" m="sm">
<Group position="apart">
<span>Elevated permissions: {String(elevated)}</span>
<Button onClick={handleElevate}>Elevate</Button>
</Group>
</Card>
</Navbar> </Navbar>
) )
} }

View File

@@ -18,7 +18,9 @@ const LoadingComponent: React.FC<React.PropsWithChildren<{ connectionAttempts: n
export const AuthGate: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => { export const AuthGate: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const { isLoading, isAuthenticated, connectionAttempts } = useAuthenticationStatus() const { isLoading, isAuthenticated, connectionAttempts } = useAuthenticationStatus()
const location = useLocation() const location = useLocation()
if (isLoading) { if (isLoading) {
return <LoadingComponent connectionAttempts={connectionAttempts} /> 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']>; _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. */ /** User webauthn security keys. Don't modify its structure as Hasura Auth relies on it to function properly. */
export type AuthUserSecurityKeys = { export type AuthUserSecurityKeys = {
__typename?: 'authUserSecurityKeys'; __typename?: 'authUserSecurityKeys';
@@ -144,6 +270,21 @@ export enum AuthUserSecurityKeysSelectColumn {
UserId = 'userId' 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'. */ /** Boolean expression to compare columns of type "citext". All fields are combined with logical 'AND'. */
export type CitextComparisonExp = { export type CitextComparisonExp = {
_eq?: InputMaybe<Scalars['citext']>; _eq?: InputMaybe<Scalars['citext']>;
@@ -177,6 +318,14 @@ export type CitextComparisonExp = {
_similar?: InputMaybe<Scalars['citext']>; _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" */ /** columns and relationships of "storage.files" */
export type Files = { export type Files = {
__typename?: 'files'; __typename?: 'files';
@@ -264,7 +413,7 @@ export type FilesOrderBy = {
uploadedByUserId?: InputMaybe<OrderBy>; uploadedByUserId?: InputMaybe<OrderBy>;
}; };
/** primary key columns input for table: files */ /** primary key columns input for table: storage.files */
export type FilesPkColumnsInput = { export type FilesPkColumnsInput = {
id: Scalars['uuid']; id: Scalars['uuid'];
}; };
@@ -307,6 +456,28 @@ export type FilesSetInput = {
uploadedByUserId?: InputMaybe<Scalars['uuid']>; 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" */ /** update columns of table "storage.files" */
export enum FilesUpdateColumn { export enum FilesUpdateColumn {
/** column name */ /** column name */
@@ -336,6 +507,7 @@ export type FilesUpdates = {
_inc?: InputMaybe<FilesIncInput>; _inc?: InputMaybe<FilesIncInput>;
/** sets the columns of the filtered rows to the given values */ /** sets the columns of the filtered rows to the given values */
_set?: InputMaybe<FilesSetInput>; _set?: InputMaybe<FilesSetInput>;
/** filter the rows which have to be updated */
where: FilesBoolExp; where: FilesBoolExp;
}; };
@@ -370,6 +542,10 @@ export type JsonbComparisonExp = {
/** mutation root */ /** mutation root */
export type MutationRoot = { export type MutationRoot = {
__typename?: 'mutation_root'; __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" */ /** delete single row from the table: "auth.user_security_keys" */
deleteAuthUserSecurityKey?: Maybe<AuthUserSecurityKeys>; deleteAuthUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
/** delete data from the table: "auth.user_security_keys" */ /** delete data from the table: "auth.user_security_keys" */
@@ -378,14 +554,22 @@ export type MutationRoot = {
deleteFile?: Maybe<Files>; deleteFile?: Maybe<Files>;
/** delete data from the table: "storage.files" */ /** delete data from the table: "storage.files" */
deleteFiles?: Maybe<FilesMutationResponse>; 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" */ /** delete single row from the table: "todos" */
deleteTodo?: Maybe<Todos>; deleteTodo?: Maybe<Todos>;
/** delete data from the table: "todos" */ /** delete data from the table: "todos" */
deleteTodos?: Maybe<TodosMutationResponse>; deleteTodos?: Maybe<TodosMutationResponse>;
/** insert data into the table: "notes" */
inserNotes?: Maybe<NotesMutationResponse>;
/** insert a single row into the table: "storage.files" */ /** insert a single row into the table: "storage.files" */
insertFile?: Maybe<Files>; insertFile?: Maybe<Files>;
/** insert data into the table: "storage.files" */ /** insert data into the table: "storage.files" */
insertFiles?: Maybe<FilesMutationResponse>; insertFiles?: Maybe<FilesMutationResponse>;
/** insert a single row into the table: "notes" */
insertNote?: Maybe<Notes>;
/** insert a single row into the table: "todos" */ /** insert a single row into the table: "todos" */
insertTodo?: Maybe<Todos>; insertTodo?: Maybe<Todos>;
/** insert data into the table: "todos" */ /** insert data into the table: "todos" */
@@ -394,17 +578,35 @@ export type MutationRoot = {
updateFile?: Maybe<Files>; updateFile?: Maybe<Files>;
/** update data of the table: "storage.files" */ /** update data of the table: "storage.files" */
updateFiles?: Maybe<FilesMutationResponse>; 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" */ /** update single row of the table: "todos" */
updateTodo?: Maybe<Todos>; updateTodo?: Maybe<Todos>;
/** update data of the table: "todos" */ /** update data of the table: "todos" */
updateTodos?: Maybe<TodosMutationResponse>; updateTodos?: Maybe<TodosMutationResponse>;
/** update multiples rows of table: "storage.files" */ /** update multiples rows of table: "storage.files" */
update_files_many?: Maybe<Array<Maybe<FilesMutationResponse>>>; 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 multiples rows of table: "todos" */
update_todos_many?: Maybe<Array<Maybe<TodosMutationResponse>>>; update_todos_many?: Maybe<Array<Maybe<TodosMutationResponse>>>;
}; };
/** mutation root */
export type MutationRootDeleteAuthRefreshTokenArgs = {
id: Scalars['uuid'];
};
/** mutation root */
export type MutationRootDeleteAuthRefreshTokensArgs = {
where: AuthRefreshTokensBoolExp;
};
/** mutation root */ /** mutation root */
export type MutationRootDeleteAuthUserSecurityKeyArgs = { export type MutationRootDeleteAuthUserSecurityKeyArgs = {
id: Scalars['uuid']; 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 */ /** mutation root */
export type MutationRootDeleteTodoArgs = { export type MutationRootDeleteTodoArgs = {
id: Scalars['uuid']; id: Scalars['uuid'];
@@ -441,6 +655,13 @@ export type MutationRootDeleteTodosArgs = {
}; };
/** mutation root */
export type MutationRootInserNotesArgs = {
objects: Array<NotesInsertInput>;
on_conflict?: InputMaybe<NotesOnConflict>;
};
/** mutation root */ /** mutation root */
export type MutationRootInsertFileArgs = { export type MutationRootInsertFileArgs = {
object: FilesInsertInput; object: FilesInsertInput;
@@ -455,6 +676,13 @@ export type MutationRootInsertFilesArgs = {
}; };
/** mutation root */
export type MutationRootInsertNoteArgs = {
object: NotesInsertInput;
on_conflict?: InputMaybe<NotesOnConflict>;
};
/** mutation root */ /** mutation root */
export type MutationRootInsertTodoArgs = { export type MutationRootInsertTodoArgs = {
object: TodosInsertInput; 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 */ /** mutation root */
export type MutationRootUpdateTodoArgs = { export type MutationRootUpdateTodoArgs = {
_set?: InputMaybe<TodosSetInput>; _set?: InputMaybe<TodosSetInput>;
@@ -505,11 +747,167 @@ export type MutationRootUpdateFilesManyArgs = {
}; };
/** mutation root */
export type MutationRootUpdateNotesManyArgs = {
updates: Array<NotesUpdates>;
};
/** mutation root */ /** mutation root */
export type MutationRootUpdateTodosManyArgs = { export type MutationRootUpdateTodosManyArgs = {
updates: Array<TodosUpdates>; 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 */ /** column ordering options */
export enum OrderBy { export enum OrderBy {
/** in ascending order, nulls last */ /** in ascending order, nulls last */
@@ -528,6 +926,10 @@ export enum OrderBy {
export type QueryRoot = { export type QueryRoot = {
__typename?: 'query_root'; __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 */ /** fetch data from the table: "auth.user_security_keys" using primary key columns */
authUserSecurityKey?: Maybe<AuthUserSecurityKeys>; authUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
/** fetch data from the table: "auth.user_security_keys" */ /** fetch data from the table: "auth.user_security_keys" */
@@ -536,6 +938,12 @@ export type QueryRoot = {
file?: Maybe<Files>; file?: Maybe<Files>;
/** fetch data from the table: "storage.files" */ /** fetch data from the table: "storage.files" */
files: Array<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 */ /** fetch data from the table: "todos" using primary key columns */
todo?: Maybe<Todos>; todo?: Maybe<Todos>;
/** fetch data from the table: "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 = { export type QueryRootAuthUserSecurityKeyArgs = {
id: Scalars['uuid']; 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 = { export type QueryRootTodoArgs = {
id: Scalars['uuid']; id: Scalars['uuid'];
}; };
@@ -615,24 +1060,67 @@ export type QueryRootUsersArgs = {
export type SubscriptionRoot = { export type SubscriptionRoot = {
__typename?: 'subscription_root'; __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 */ /** fetch data from the table: "auth.user_security_keys" using primary key columns */
authUserSecurityKey?: Maybe<AuthUserSecurityKeys>; authUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
/** fetch data from the table: "auth.user_security_keys" */ /** fetch data from the table: "auth.user_security_keys" */
authUserSecurityKeys: Array<AuthUserSecurityKeys>; 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 */ /** fetch data from the table: "storage.files" using primary key columns */
file?: Maybe<Files>; file?: Maybe<Files>;
/** fetch data from the table: "storage.files" */ /** fetch data from the table: "storage.files" */
files: Array<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 */ /** fetch data from the table: "todos" using primary key columns */
todo?: Maybe<Todos>; todo?: Maybe<Todos>;
/** fetch data from the table: "todos" */ /** fetch data from the table: "todos" */
todos: Array<Todos>; todos: Array<Todos>;
/** fetch aggregated fields from the table: "todos" */ /** fetch aggregated fields from the table: "todos" */
todosAggregate: TodosAggregate; 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 */ /** fetch data from the table: "auth.users" using primary key columns */
user?: Maybe<Users>; user?: Maybe<Users>;
/** fetch data from the table: "auth.users" */ /** fetch data from the table: "auth.users" */
users: Array<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 = { export type SubscriptionRootFileArgs = {
id: Scalars['uuid']; 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 = { export type SubscriptionRootTodoArgs = {
id: Scalars['uuid']; 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 = { export type SubscriptionRootUserArgs = {
id: Scalars['uuid']; id: Scalars['uuid'];
}; };
@@ -700,6 +1239,13 @@ export type SubscriptionRootUsersArgs = {
where?: InputMaybe<UsersBoolExp>; 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'. */ /** Boolean expression to compare columns of type "timestamptz". All fields are combined with logical 'AND'. */
export type TimestamptzComparisonExp = { export type TimestamptzComparisonExp = {
_eq?: InputMaybe<Scalars['timestamptz']>; _eq?: InputMaybe<Scalars['timestamptz']>;
@@ -842,6 +1388,23 @@ export type TodosSetInput = {
contents?: InputMaybe<Scalars['String']>; 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" */ /** update columns of table "todos" */
export enum TodosUpdateColumn { export enum TodosUpdateColumn {
/** column name */ /** column name */
@@ -851,6 +1414,7 @@ export enum TodosUpdateColumn {
export type TodosUpdates = { export type TodosUpdates = {
/** sets the columns of the filtered rows to the given values */ /** sets the columns of the filtered rows to the given values */
_set?: InputMaybe<TodosSetInput>; _set?: InputMaybe<TodosSetInput>;
/** filter the rows which have to be updated */
where: TodosBoolExp; where: TodosBoolExp;
}; };
@@ -876,6 +1440,8 @@ export type Users = {
phoneNumber?: Maybe<Scalars['String']>; phoneNumber?: Maybe<Scalars['String']>;
phoneNumberVerified: Scalars['Boolean']; phoneNumberVerified: Scalars['Boolean'];
/** An array relationship */ /** An array relationship */
refreshTokens: Array<AuthRefreshTokens>;
/** An array relationship */
securityKeys: Array<AuthUserSecurityKeys>; securityKeys: Array<AuthUserSecurityKeys>;
updatedAt: Scalars['timestamptz']; 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. */ /** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
export type UsersSecurityKeysArgs = { export type UsersSecurityKeysArgs = {
distinct_on?: InputMaybe<Array<AuthUserSecurityKeysSelectColumn>>; distinct_on?: InputMaybe<Array<AuthUserSecurityKeysSelectColumn>>;
@@ -919,6 +1495,7 @@ export type UsersBoolExp = {
otpMethodLastUsed?: InputMaybe<StringComparisonExp>; otpMethodLastUsed?: InputMaybe<StringComparisonExp>;
phoneNumber?: InputMaybe<StringComparisonExp>; phoneNumber?: InputMaybe<StringComparisonExp>;
phoneNumberVerified?: InputMaybe<BooleanComparisonExp>; phoneNumberVerified?: InputMaybe<BooleanComparisonExp>;
refreshTokens?: InputMaybe<AuthRefreshTokensBoolExp>;
securityKeys?: InputMaybe<AuthUserSecurityKeysBoolExp>; securityKeys?: InputMaybe<AuthUserSecurityKeysBoolExp>;
updatedAt?: InputMaybe<TimestamptzComparisonExp>; updatedAt?: InputMaybe<TimestamptzComparisonExp>;
}; };
@@ -943,6 +1520,7 @@ export type UsersOrderBy = {
otpMethodLastUsed?: InputMaybe<OrderBy>; otpMethodLastUsed?: InputMaybe<OrderBy>;
phoneNumber?: InputMaybe<OrderBy>; phoneNumber?: InputMaybe<OrderBy>;
phoneNumberVerified?: InputMaybe<OrderBy>; phoneNumberVerified?: InputMaybe<OrderBy>;
refreshTokens_aggregate?: InputMaybe<AuthRefreshTokensAggregateOrderBy>;
securityKeys_aggregate?: InputMaybe<AuthUserSecurityKeysAggregateOrderBy>; securityKeys_aggregate?: InputMaybe<AuthUserSecurityKeysAggregateOrderBy>;
updatedAt?: InputMaybe<OrderBy>; updatedAt?: InputMaybe<OrderBy>;
}; };
@@ -989,6 +1567,37 @@ export enum UsersSelectColumn {
UpdatedAt = 'updatedAt' 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'. */ /** Boolean expression to compare columns of type "uuid". All fields are combined with logical 'AND'. */
export type UuidComparisonExp = { export type UuidComparisonExp = {
_eq?: InputMaybe<Scalars['uuid']>; _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 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<{ export type SecurityKeysQueryVariables = Exact<{
userId: Scalars['uuid']; userId: Scalars['uuid'];
}>; }>;

View File

@@ -2,11 +2,25 @@ import { useState } from 'react'
import { Button, Card, Grid, TextInput, Title } from '@mantine/core' import { Button, Card, Grid, TextInput, Title } from '@mantine/core'
import { showNotification } from '@mantine/notifications' 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 = () => { export const ChangeEmail: React.FC = () => {
const [newEmail, setNewEmail] = useState('') const userId = useUserId()
const email = useUserEmail() 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({ const { changeEmail } = useChangeEmail({
redirectTo: '/profile' redirectTo: '/profile'
}) })
@@ -19,7 +33,26 @@ export const ChangeEmail: React.FC = () => {
}) })
return 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) const result = await changeEmail(newEmail)
if (result.needsEmailVerification) { if (result.needsEmailVerification) {
showNotification({ showNotification({
message: `An email has been sent to ${newEmail}. Please check your inbox and follow the link to confirm the email change.` 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 ( return (
<Card shadow="sm" p="lg" m="sm"> <Card shadow="sm" p="lg" m="sm">
<Title>Change email</Title> <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 { Button, Card, Grid, PasswordInput, Title } from '@mantine/core'
import { showNotification } from '@mantine/notifications' 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 = () => { export const ChangePassword: React.FC = () => {
const userEmail = useUserEmail()
const userId = useUserId()
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const { changePassword } = useChangePassword() 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 () => { 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) const result = await changePassword(password)
if (result.isSuccess) { if (result.isSuccess) {
showNotification({ showNotification({

View File

@@ -2,11 +2,56 @@ import { useState } from 'react'
import { Button, Card, TextInput, Title } from '@mantine/core' import { Button, Card, TextInput, Title } from '@mantine/core'
import { showNotification } from '@mantine/notifications' 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 = () => { export const Mfa: React.FC = () => {
const userId = useUserId()
const userEmail = useUserEmail()
const [code, setCode] = useState('') const [code, setCode] = useState('')
const { generateQrCode, activateMfa, isActivated, isGenerated, qrCodeDataUrl } = useConfigMfa() 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 generate = async () => {
const result = await generateQrCode() const result = await generateQrCode()
if (result.error) { if (result.error) {
@@ -33,7 +78,7 @@ export const Mfa: React.FC = () => {
onChange={(e) => setCode(e.target.value)} onChange={(e) => setCode(e.target.value)}
placeholder="Enter activation code" placeholder="Enter activation code"
/> />
<Button fullWidth onClick={() => activateMfa(code)}> <Button fullWidth onClick={activate}>
Activate Activate
</Button> </Button>
</div> </div>

View File

@@ -2,36 +2,22 @@ import { useState } from 'react'
import { FaMinus } from 'react-icons/fa' import { FaMinus } from 'react-icons/fa'
import { RemoveSecurityKeyMutation, SecurityKeysQuery } from 'src/generated' 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 { ActionIcon, Button, Card, SimpleGrid, Table, TextInput, Title } from '@mantine/core'
import { useInputState } from '@mantine/hooks' import { useInputState } from '@mantine/hooks'
import { showNotification } from '@mantine/notifications' import { showNotification } from '@mantine/notifications'
import { useAddSecurityKey, useUserId } from '@nhost/react' import { useAddSecurityKey, useUserId } from '@nhost/react'
import { useAuthQuery } from '@nhost/react-apollo' import { useAuthQuery } from '@nhost/react-apollo'
import { REMOVE_SECURITY_KEY, SECURITY_KEYS_LIST } from 'src/utils'
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
}
}
`
export const SecurityKeys: React.FC = () => { export const SecurityKeys: React.FC = () => {
const { add } = useAddSecurityKey()
const userId = useUserId() const userId = useUserId()
const client = useApolloClient()
const { add } = useAddSecurityKey()
// Nickname of the security key // Nickname of the security key
const [nickname, setNickname] = useInputState('') const [nickname, setNickname] = useInputState('')
const [list, setList] = useState<{ id: string; nickname?: string | null }[]>([]) const [list, setList] = useState<{ id: string; nickname?: string | null }[]>([])
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, { useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
variables: { userId }, variables: { userId },
onCompleted: ({ authUserSecurityKeys }) => { onCompleted: ({ authUserSecurityKeys }) => {
@@ -43,9 +29,10 @@ export const SecurityKeys: React.FC = () => {
const addKey = async (e: React.FormEvent<HTMLFormElement>) => { const addKey = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
const { key, isError, error } = await add(nickname) const { key, isError, error } = await add(nickname)
if (isError) { if (isError) {
console.log(error)
showNotification({ showNotification({
color: 'red', color: 'red',
title: 'Error', title: 'Error',
@@ -53,11 +40,17 @@ export const SecurityKeys: React.FC = () => {
}) })
} else { } else {
setNickname('') setNickname('')
// refetch securityKeys so that we know if need to elevate in other components
await client.refetchQueries({
include: [SECURITY_KEYS_LIST]
})
} }
if (key) { if (key) {
setList([...list, key]) setList([...list, key])
} }
} }
const [removeKey] = useMutation<RemoveSecurityKeyMutation>(REMOVE_SECURITY_KEY, { const [removeKey] = useMutation<RemoveSecurityKeyMutation>(REMOVE_SECURITY_KEY, {
onCompleted: ({ deleteAuthUserSecurityKey }) => { onCompleted: ({ deleteAuthUserSecurityKey }) => {
if (deleteAuthUserSecurityKey?.id) { 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 ( return (
<Card shadow="sm" p="lg" m="sm"> <Card shadow="sm" p="lg" m="sm">
<Title>Security keys</Title> <Title>Security keys</Title>
@@ -79,7 +91,7 @@ export const SecurityKeys: React.FC = () => {
<tr key={id}> <tr key={id}>
<td>{nickname || id}</td> <td>{nickname || id}</td>
<td> <td>
<ActionIcon onClick={() => removeKey({ variables: { id } })} color="red"> <ActionIcon onClick={() => handleRemoveKey(id)} color="red">
<FaMinus /> <FaMinus />
</ActionIcon> </ActionIcon>
</td> </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 { defineConfig } from 'vite'
import path from 'path'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
@@ -8,5 +8,10 @@ export default defineConfig({
include: ['react/jsx-runtime'], include: ['react/jsx-runtime'],
exclude: ['@nhost/react'] exclude: ['@nhost/react']
}, },
resolve: {
alias: {
src: path.resolve(__dirname, './src')
}
},
plugins: [react()] plugins: [react()]
}) })

View File

@@ -1,5 +1,27 @@
# @nhost-examples/react-gqty # @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 ## 1.0.1
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,13 @@
# @nhost-examples/serverless-functions # @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 ## 0.0.10
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,41 @@
# @nhost-examples/vue-apollo # @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 ## 0.0.10
### Patch Changes ### Patch Changes

View File

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

View File

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

View File

@@ -2,8 +2,11 @@ table:
name: providers name: providers
schema: auth schema: auth
configuration: configuration:
column_config: {} column_config:
custom_column_names: {} id:
custom_name: id
custom_column_names:
id: id
custom_name: authProviders custom_name: authProviders
custom_root_fields: custom_root_fields:
delete: deleteAuthProviders 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 custom_name: createdAt
expires_at: expires_at:
custom_name: expiresAt custom_name: expiresAt
refresh_token: refresh_token_hash:
custom_name: refreshToken custom_name: refreshTokenHash
user_id: user_id:
custom_name: userId custom_name: userId
custom_column_names: custom_column_names:
created_at: createdAt created_at: createdAt
expires_at: expiresAt expires_at: expiresAt
refresh_token: refreshToken refresh_token_hash: refreshTokenHash
user_id: userId user_id: userId
custom_name: authRefreshTokens custom_name: authRefreshTokens
custom_root_fields: custom_root_fields:
@@ -31,3 +31,25 @@ object_relationships:
- name: user - name: user
using: using:
foreign_key_constraint_on: user_id 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 name: roles
schema: auth schema: auth
configuration: configuration:
column_config: {} column_config:
custom_column_names: {} role:
custom_name: role
custom_column_names:
role: role
custom_name: authRoles custom_name: authRoles
custom_root_fields: custom_root_fields:
delete: deleteAuthRoles delete: deleteAuthRoles

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