Compare commits

..

8 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
77 changed files with 1871 additions and 1960 deletions

View File

@@ -1,5 +1,25 @@
# @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 ## 1.6.6
### Patch Changes ### Patch Changes

View File

@@ -4,7 +4,6 @@ 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;
@@ -19,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,
@@ -41,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',
@@ -49,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.6", "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.9.1", "@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",
@@ -106,7 +106,7 @@
"@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",

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,11 @@
# @nhost/docs # @nhost/docs
## 2.4.0
### Minor Changes
- 791b729: fix: remove auth method
## 2.3.0 ## 2.3.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/docs", "name": "@nhost/docs",
"version": "2.3.0", "version": "2.4.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "mintlify dev" "start": "mintlify dev"

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,17 @@
# @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 ## 0.1.4
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost-examples/cli", "name": "@nhost-examples/cli",
"version": "0.1.4", "version": "0.1.6",
"main": "src/index.mjs", "main": "src/index.mjs",
"private": true, "private": true,
"scripts": { "scripts": {

View File

@@ -1,5 +1,20 @@
# @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 ## 0.1.12
### 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.12", "version": "0.1.14",
"private": true, "private": true,
"scripts": { "scripts": {
"codegen": "graphql-codegen", "codegen": "graphql-codegen",
@@ -15,7 +15,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.9.1", "@apollo/client": "^3.9.4",
"@nhost/react": "workspace:^", "@nhost/react": "workspace:^",
"@nhost/react-apollo": "workspace:^", "@nhost/react-apollo": "workspace:^",
"clsx": "^1.2.1", "clsx": "^1.2.1",

View File

@@ -1,5 +1,18 @@
# @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 ## 0.1.13
### 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.13", "version": "0.1.15",
"private": true, "private": true,
"scripts": { "scripts": {
"codegen": "graphql-codegen", "codegen": "graphql-codegen",

View File

@@ -1,5 +1,20 @@
# @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 ## 0.0.9
### 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.9", "version": "0.0.11",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",

View File

@@ -1,5 +1,17 @@
# @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 ## 2.0.2
### 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.2", "version": "2.0.4",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": {}, "scripts": {},

View File

@@ -1,5 +1,22 @@
# @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 ## 0.1.14
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost-examples/nextjs", "name": "@nhost-examples/nextjs",
"version": "0.1.14", "version": "0.1.16",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -15,7 +15,7 @@
"verify:fix": "run-p prettier:fix lint:fix" "verify:fix": "run-p prettier:fix lint:fix"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.9.1", "@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",

View File

@@ -1,5 +1,17 @@
# @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 ## 0.0.6
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost-examples/node-storage", "name": "@nhost-examples/node-storage",
"version": "0.0.6", "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",

View File

@@ -1,5 +1,17 @@
# @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 ## 0.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost-examples/nextjs-server-components", "name": "@nhost-examples/nextjs-server-components",
"version": "0.2.0", "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.9.1", "@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",

View File

@@ -1,5 +1,24 @@
# @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 ## 0.2.0
### Minor Changes ### Minor Changes

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,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

@@ -11,3 +11,4 @@
- "!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

@@ -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,7 +28,10 @@ httpPoolSize = 100
version = 18 version = 18
[auth] [auth]
version = '0.25.0' 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/'
@@ -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,9 +1,9 @@
{ {
"name": "@nhost-examples/react-apollo", "name": "@nhost-examples/react-apollo",
"version": "0.2.0", "version": "0.3.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.9.1", "@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",

View File

@@ -2,9 +2,19 @@ import { FaFile, FaHouseUser, FaQuestion, FaSignOutAlt, FaLock } from 'react-ico
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
@@ -59,10 +69,45 @@ const data: MenuItemProps[] = [
] ]
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">
@@ -78,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

@@ -1,4 +1,9 @@
import { DeleteNoteMutation, InsertNoteMutation, NotesListQuery } from 'src/generated' import {
DeleteNoteMutation,
InsertNoteMutation,
NotesListQuery,
SecurityKeysQuery
} from 'src/generated'
import { gql, useMutation } from '@apollo/client' import { gql, useMutation } from '@apollo/client'
import { import {
@@ -17,6 +22,8 @@ import { showNotification } from '@mantine/notifications'
import { useAuthQuery } from '@nhost/react-apollo' import { useAuthQuery } from '@nhost/react-apollo'
import { useElevateSecurityKeyEmail, useUserData } from '@nhost/react' import { useElevateSecurityKeyEmail, useUserData } from '@nhost/react'
import { FaTrash } from 'react-icons/fa' import { FaTrash } from 'react-icons/fa'
import { SECURITY_KEYS_LIST } from 'src/utils'
import { useState } from 'react'
const NOTES_LIST = gql` const NOTES_LIST = gql`
query notesList { query notesList {
@@ -54,15 +61,44 @@ export const NotesPage: React.FC = () => {
}) })
const [content, setContent] = useInputState('') const [content, setContent] = useInputState('')
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
const { elevateEmailSecurityKey, elevated } = 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 [addNoteMutation] = useMutation<InsertNoteMutation>(INSERT_NOTE)
const [deleteNoteMutation] = useMutation<DeleteNoteMutation>(DELETE_NOTE) const [deleteNoteMutation] = useMutation<DeleteNoteMutation>(DELETE_NOTE)
const add = () => { 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 if (!content) return
try {
await checkElevatedPermission()
} catch (error) {
showNotification({
title: 'Error',
message: 'Could not elevate permissions'
})
return
}
addNoteMutation({ addNoteMutation({
variables: { content }, variables: { content },
onCompleted: () => setContent(''), onCompleted: () => setContent(''),
@@ -94,9 +130,20 @@ export const NotesPage: React.FC = () => {
}) })
} }
const deleteNote = (noteId: string) => { const deleteNote = async (noteId: string) => {
if (!noteId) return if (!noteId) return
try {
await checkElevatedPermission()
} catch (error) {
showNotification({
title: 'Error',
message: 'Could not elevate permissions'
})
return
}
deleteNoteMutation({ deleteNoteMutation({
variables: { noteId }, variables: { noteId },
onCompleted: () => setContent(''), onCompleted: () => setContent(''),
@@ -126,20 +173,6 @@ export const NotesPage: React.FC = () => {
return ( return (
<Container> <Container>
{loading && <Loader />} {loading && <Loader />}
<Card shadow="sm" p="lg" m="sm">
<Group position="apart">
<span>Elevated permissions: {String(elevated)}</span>
<Button
onClick={async (e: React.MouseEvent) => {
if (userData?.email) {
await elevateEmailSecurityKey(userData.email)
}
}}
>
Elevate
</Button>
</Group>
</Card>
<Card shadow="sm" p="lg" m="sm"> <Card shadow="sm" p="lg" m="sm">
<Title>Secret Notes</Title> <Title>Secret Notes</Title>
<Grid> <Grid>

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,18 @@
# @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 ## 1.0.2
### 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.2", "version": "1.0.4",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1,5 +1,26 @@
# @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 ## 0.1.0
### Minor Changes ### Minor Changes

View File

@@ -31,3 +31,19 @@ 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
- nickname
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
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

@@ -11,3 +11,4 @@
- "!include public_notes.yaml" - "!include public_notes.yaml"
- "!include storage_buckets.yaml" - "!include storage_buckets.yaml"
- "!include storage_files.yaml" - "!include storage_files.yaml"
- "!include storage_virus.yaml"

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,7 +28,10 @@ httpPoolSize = 100
version = 18 version = 18
[auth] [auth]
version = '0.25.0' version = '0.26.0'
[auth.elevatedPrivileges]
mode = 'required'
[auth.redirections] [auth.redirections]
clientUrl = 'http://localhost:5173' clientUrl = 'http://localhost:5173'
@@ -66,11 +69,11 @@ expiresIn = 43200
enabled = false enabled = false
[auth.method.emailPasswordless] [auth.method.emailPasswordless]
enabled = false enabled = true
[auth.method.emailPassword] [auth.method.emailPassword]
hibpEnabled = false hibpEnabled = false
emailVerificationRequired = false emailVerificationRequired = true
passwordMinLength = 9 passwordMinLength = 9
[auth.method.smsPasswordless] [auth.method.smsPasswordless]
@@ -137,12 +140,12 @@ timeout = 60000
enabled = false enabled = false
[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,7 +1,7 @@
{ {
"name": "@nhost-examples/vue-apollo", "name": "@nhost-examples/vue-apollo",
"private": true, "private": true,
"version": "0.1.0", "version": "0.2.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
@@ -14,7 +14,7 @@
"verify:fix": "run-p prettier:fix lint:fix" "verify:fix": "run-p prettier:fix lint:fix"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.9.1", "@apollo/client": "^3.9.4",
"@mdi/font": "5.9.55", "@mdi/font": "5.9.55",
"@nhost/apollo": "workspace:^", "@nhost/apollo": "workspace:^",
"@nhost/nhost-js": "workspace:^", "@nhost/nhost-js": "workspace:^",

View File

@@ -17,19 +17,68 @@
prepend-icon="mdi-exit-to-app" prepend-icon="mdi-exit-to-app"
@click="signOutHandler" @click="signOutHandler"
/> />
<v-card-text class="d-flex flex-column align-center justify-space-between align-self-end">
<span>Elevated permissions: {{ elevated }}</span>
<v-btn variant="text" color="primary" @click="handleElevate(user?.email)"> Elevate </v-btn>
</v-card-text>
</v-list> </v-list>
<v-snackbar :modelValue="showElevateSuccess">
You now have an elevated permission
<template v-slot:actions>
<v-btn color="indigo" variant="text" @click="showElevateError = false"> Close </v-btn>
</template>
</v-snackbar>
<v-snackbar :modelValue="showElevateError">
Could not elevate permission
<template v-slot:actions>
<v-btn color="indigo" variant="text" @click="showElevateError = false"> Close </v-btn>
</template>
</v-snackbar>
<v-snackbar :modelValue="loggedOutWarning">
You are logged out. Please login first!
<template v-slot:actions>
<v-btn color="indigo" variant="text" @click="loggedOutWarning = false"> Close </v-btn>
</template>
</v-snackbar>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ref } from 'vue'
import { useAuthenticated, useSignOut, useElevateSecurityKeyEmail, useUserData } from '@nhost/vue'
import { useAuthenticated, useSignOut } from '@nhost/vue' const user = useUserData()
const router = useRouter() const router = useRouter()
const { signOut } = useSignOut() const { signOut } = useSignOut()
const showElevateError = ref(false)
const showElevateSuccess = ref(false)
const loggedOutWarning = ref(false)
const authenticated = useAuthenticated() const authenticated = useAuthenticated()
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
const signOutHandler = async () => { const signOutHandler = async () => {
await signOut() await signOut()
router.push('/') router.push('/')
} }
const handleElevate = async (email: string | undefined) => {
if (!authenticated.value) {
loggedOutWarning.value = true
return
}
if (email) {
const { elevated, isError } = await elevateEmailSecurityKey(email)
if (elevated) {
showElevateSuccess.value = true
}
if (isError) {
showElevateError.value = true
}
}
}
</script> </script>

View File

@@ -2,7 +2,228 @@
<div className="d-flex align-center flex-column"> <div className="d-flex align-center flex-column">
<v-card width="400"> <v-card width="400">
<v-card-title>Profile page</v-card-title> <v-card-title>Profile page</v-card-title>
<v-card-text> Here is the profile page </v-card-text> <v-card-text> {{ userEmail }} </v-card-text>
</v-card>
<v-card width="400" class="mt-2 pa-4">
<v-card-title>Add Security Key</v-card-title>
<form @submit="handleAddSecurityKey">
<v-text-field v-model="nickname" label="NickName" />
<v-btn
block
color="primary"
class="my-1"
type="submit"
:disabled="isChangeEmailLoading"
:loading="isChangeEmailLoading"
>
Add
</v-btn>
</form>
<v-list density="compact">
<v-list-subheader>Security Keys</v-list-subheader>
<v-list-item v-for="(key, i) in securityKeysList" :key="i" :value="key.id">
<div className="d-flex align-center justify-space-between">
<v-list-item-title>{{ key.id }}</v-list-item-title>
<v-btn
variant="flat"
prepend-icon="mdi-delete"
@click="handleRemoveSecurityKey(key.id)"
/>
</div>
</v-list-item>
</v-list>
</v-card>
<v-card width="400" class="mt-2 pa-4">
<v-card-title>Change Email</v-card-title>
<form @submit="handleChangeEmail">
<v-text-field v-model="email" label="Email" />
<v-btn
block
color="primary"
class="my-1"
type="submit"
:disabled="isChangeEmailLoading"
:loading="isChangeEmailLoading"
>
Change email
</v-btn>
</form>
</v-card>
<v-card width="400" class="mt-2 pa-4">
<v-card-title>Change Password</v-card-title>
<form @submit="handleChangePassword">
<v-text-field v-model="password" label="Password" type="password" />
<v-btn
block
color="primary"
class="my-1"
type="submit"
:disabled="isChangePasswordLoading"
:loading="isChangePasswordLoading"
>
Change password
</v-btn>
</form>
</v-card> </v-card>
</div> </div>
<error-snack-bar :error="elevateError" />
<error-snack-bar :error="changeEmailError" />
<v-snackbar :modelValue="successSnackBar">OK</v-snackbar>
<error-snack-bar v-model="showElevatePermissionError"
>Could not elevate permission</error-snack-bar
>
<error-snack-bar v-model="showRemoveKeyError"></error-snack-bar>
<v-snackbar v-model="showRemoveKeyError">
Could not remove key
<template #actions>
<v-btn color="blue" variant="text" @click="showRemoveKeyError = false"> Close </v-btn>
</template>
</v-snackbar>
<verification-email-dialog v-model="emailVerificationDialog" :email="email" />
</template> </template>
<script lang="ts" setup>
import { gql } from '@apollo/client/core'
import {
useChangeEmail,
useChangePassword,
useElevateSecurityKeyEmail,
useAddSecurityKey,
useUserEmail,
useUserId
} from '@nhost/vue'
import { useMutation, useQuery } from '@vue/apollo-composable'
import { computed } from 'vue'
import { ref, unref } from 'vue'
const email = ref('')
const password = ref('')
const nickname = ref('')
const successSnackBar = ref(false)
const userId = useUserId()
const userEmail = useUserEmail()
const emailVerificationDialog = ref(false)
const showElevatePermissionError = ref(false)
const showRemoveKeyError = ref(false)
const addSecurityKeyError = ref(false)
const elevateError = ref(null)
const changeEmailError = ref(null)
const { changeEmail, isLoading: isChangeEmailLoading } = useChangeEmail()
const { changePassword, isLoading: isChangePasswordLoading } = useChangePassword()
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
const { add } = useAddSecurityKey()
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
}
}
`
const { result: securityKeys, refetch } = useQuery(SECURITY_KEYS_LIST, { userId }, {})
const { mutate: removeKey } = useMutation(REMOVE_SECURITY_KEY)
const securityKeysList = computed(() => securityKeys.value?.authUserSecurityKeys || [])
const checkElevatedPermission = async () => {
let elevatedValue = unref(elevated)
if (!elevatedValue && securityKeys.value.authUserSecurityKeys.length > 0) {
const { elevated } = await elevateEmailSecurityKey(userEmail.value as string)
if (!elevated) {
throw new Error('Permissions were not elevated')
}
}
}
const handleChangeEmail = async (e: Event) => {
e.preventDefault()
try {
await checkElevatedPermission()
} catch (error) {
showElevatePermissionError.value = true
}
const { needsEmailVerification } = await changeEmail(email)
if (needsEmailVerification) {
emailVerificationDialog.value = true
} else {
successSnackBar.value = true
}
}
const handleChangePassword = async (e: Event) => {
e.preventDefault()
try {
await checkElevatedPermission()
} catch (error) {
showElevatePermissionError.value = true
}
const { error: changePasswordError } = await changePassword(password)
if (!changePasswordError) {
successSnackBar.value = true
}
}
const handleAddSecurityKey = async (e: Event) => {
e.preventDefault()
try {
await checkElevatedPermission()
} catch (error) {
showElevatePermissionError.value = true
}
const { isError } = await add(nickname.value)
if (isError) {
addSecurityKeyError.value = true
} else {
nickname.value = ''
refetch()
}
}
const handleRemoveSecurityKey = async (id: string) => {
try {
await checkElevatedPermission()
} catch (error) {
showElevatePermissionError.value = true
}
try {
await removeKey({ id })
await refetch()
} catch (error) {
showRemoveKeyError.value = true
}
}
</script>

View File

@@ -2,29 +2,17 @@
<div className="d-flex align-center flex-column"> <div className="d-flex align-center flex-column">
<v-card width="400" tile> <v-card width="400" tile>
<v-card-title>Secret Notes</v-card-title> <v-card-title>Secret Notes</v-card-title>
<v-card-text class="d-flex align-center justify-space-between">
<span>Elevated permissions: {{ elevated }}</span>
<v-btn variant="text" color="primary" @click="elevatePermission(user?.email)">
Elevate
</v-btn>
</v-card-text>
<v-col> <v-col>
<v-row class="px-4 mb-2 align-center"> <v-row class="px-4 mb-2 align-center">
<v-text-field v-model="content" label="Note" class="mt-4 mr-2" /> <v-text-field v-model="content" label="Note" class="mt-4 mr-2" />
<v-btn size="large" @click="insertNote({ content }, { refetchQueries: ['notesList'] })" <v-btn size="large" @click="addNote">Add</v-btn>
>Add</v-btn
>
</v-row> </v-row>
<v-list density="compact" v-if="result"> <v-list density="compact" v-if="result">
<v-list-subheader>Notes</v-list-subheader> <v-list-subheader>Notes</v-list-subheader>
<v-list-item v-for="(note, i) in result.notes" :key="i" :value="note.id"> <v-list-item v-for="(note, i) in result.notes" :key="i" :value="note.id">
<div className="d-flex align-center justify-space-between"> <div className="d-flex align-center justify-space-between">
<v-list-item-title v-text="note.content"></v-list-item-title> <v-list-item-title v-text="note.content"></v-list-item-title>
<v-btn <v-btn variant="flat" prepend-icon="mdi-delete" @click="deleteNote(note.id)" />
variant="flat"
prepend-icon="mdi-delete"
@click="deleteNote({ noteId: note.id }, { refetchQueries: ['notesList'] })"
/>
</div> </div>
</v-list-item> </v-list-item>
</v-list> </v-list>
@@ -33,26 +21,24 @@
</div> </div>
<error-snack-bar :error="insertNoteError" /> <error-snack-bar :error="insertNoteError" />
<error-snack-bar :error="deleteNoteError" /> <error-snack-bar :error="deleteNoteError" />
<error-snack-bar v-model="showElevatePermissionError"
>Could not elevate permission</error-snack-bar
>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue' import { computed, ref, unref } from 'vue'
import { gql } from '@apollo/client/core' import { gql } from '@apollo/client/core'
import { useAuthenticated, useElevateSecurityKeyEmail, useUserData } from '@nhost/vue' import { useAuthenticated, useElevateSecurityKeyEmail, useUserEmail, useUserId } from '@nhost/vue'
import { useQuery, useMutation } from '@vue/apollo-composable' import { useQuery, useMutation } from '@vue/apollo-composable'
const content = ref('') const content = ref('')
const userId = useUserId()
const user = useUserData() const userEmail = useUserEmail()
const isAuthenticated = useAuthenticated()
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail() const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
const showElevatePermissionError = ref(false)
const elevatePermission = async (email: string | undefined) => {
if (email) {
await elevateEmailSecurityKey(email)
}
}
const GET_NOTES = gql` const GET_NOTES = gql`
query notesList { query notesList {
@@ -81,8 +67,15 @@ const DELETE_NOTE = gql`
} }
` `
const isAuthenticated = useAuthenticated() const SECURITY_KEYS_LIST = gql`
// TODO check if the query always runs with the headers query securityKeys($userId: uuid!) {
authUserSecurityKeys(where: { userId: { _eq: $userId } }) {
id
nickname
}
}
`
const { result } = useQuery( const { result } = useQuery(
GET_NOTES, GET_NOTES,
null, null,
@@ -91,6 +84,44 @@ const { result } = useQuery(
})) }))
) )
const { mutate: insertNote, error: insertNoteError } = useMutation(INSERT_NOTE) const { result: securityKeys } = useQuery(SECURITY_KEYS_LIST, { userId })
const { mutate: deleteNote, error: deleteNoteError } = useMutation(DELETE_NOTE)
const { mutate: insertNoteMutation, error: insertNoteError } = useMutation(INSERT_NOTE)
const { mutate: deleteNoteMutation, error: deleteNoteError } = useMutation(DELETE_NOTE)
const checkElevatedPermission = async () => {
let elevatedValue = unref(elevated)
if (!elevatedValue && securityKeys.value.authUserSecurityKeys.length > 0) {
const { elevated } = await elevateEmailSecurityKey(userEmail.value as string)
if (!elevated) {
throw new Error('Permissions were not elevated')
}
}
}
const addNote = async () => {
try {
await checkElevatedPermission()
} catch (error) {
showElevatePermissionError.value = true
}
await insertNoteMutation({ content: content.value }, { refetchQueries: ['notesList'] })
content.value = ''
}
const deleteNote = async (noteId: string) => {
try {
await checkElevatedPermission()
} catch (error) {
showElevatePermissionError.value = true
}
await deleteNoteMutation({ noteId: noteId }, { refetchQueries: ['notesList'] })
content.value = ''
}
</script> </script>

View File

@@ -1,5 +1,20 @@
# @nhost-examples/vue-quickstart # @nhost-examples/vue-quickstart
## 0.0.13
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/vue@2.2.0
- @nhost/apollo@6.0.5
## 0.0.12
### Patch Changes
- @nhost/apollo@6.0.4
- @nhost/vue@2.1.1
## 0.0.11 ## 0.0.11
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost-examples/vue-quickstart", "name": "@nhost-examples/vue-quickstart",
"version": "0.0.11", "version": "0.0.13",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "vite build", "build": "vite build",
@@ -11,7 +11,7 @@
"test": "vitest" "test": "vitest"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.9.1", "@apollo/client": "^3.9.4",
"@nhost/apollo": "workspace:^", "@nhost/apollo": "workspace:^",
"@nhost/vue": "workspace:^", "@nhost/vue": "workspace:^",
"@vue/apollo-composable": "4.0.0-alpha.18", "@vue/apollo-composable": "4.0.0-alpha.18",

View File

@@ -1,5 +1,17 @@
# @nhost/apollo # @nhost/apollo
## 6.0.5
### Patch Changes
- @nhost/nhost-js@3.0.5
## 6.0.4
### Patch Changes
- @nhost/nhost-js@3.0.4
## 6.0.3 ## 6.0.3
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/apollo", "name": "@nhost/apollo",
"version": "6.0.3", "version": "6.0.5",
"description": "Nhost Apollo Client library", "description": "Nhost Apollo Client library",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@@ -68,7 +68,7 @@
"graphql-ws": "^5.14.3" "graphql-ws": "^5.14.3"
}, },
"devDependencies": { "devDependencies": {
"@apollo/client": "^3.9.1", "@apollo/client": "^3.9.4",
"@nhost/nhost-js": "workspace:*" "@nhost/nhost-js": "workspace:*"
} }
} }

View File

@@ -1,5 +1,20 @@
# @nhost/react-apollo # @nhost/react-apollo
## 9.0.0
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
- @nhost/apollo@6.0.5
## 8.0.1
### Patch Changes
- @nhost/apollo@6.0.4
- @nhost/react@3.1.1
## 8.0.0 ## 8.0.0
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/react-apollo", "name": "@nhost/react-apollo",
"version": "8.0.0", "version": "9.0.0",
"description": "Nhost React Apollo client", "description": "Nhost React Apollo client",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@@ -71,7 +71,7 @@
"react-dom": "^17.0.0 || ^18.0.0" "react-dom": "^17.0.0 || ^18.0.0"
}, },
"devDependencies": { "devDependencies": {
"@apollo/client": "^3.9.1", "@apollo/client": "^3.9.4",
"@nhost/react": "workspace:*", "@nhost/react": "workspace:*",
"@types/react": "^18.2.50", "@types/react": "^18.2.50",
"graphql": "16.8.1", "graphql": "16.8.1",

View File

@@ -1,5 +1,18 @@
# @nhost/react-urql # @nhost/react-urql
## 6.0.0
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
## 5.0.1
### Patch Changes
- @nhost/react@3.1.1
## 5.0.0 ## 5.0.0
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/react-urql", "name": "@nhost/react-urql",
"version": "5.0.0", "version": "6.0.0",
"description": "Nhost React URQL client", "description": "Nhost React URQL client",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/hasura-auth-js # @nhost/hasura-auth-js
## 2.3.0
### Minor Changes
- 017f1a6: feat: add elevated permission examples
## 2.2.0 ## 2.2.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/hasura-auth-js", "name": "@nhost/hasura-auth-js",
"version": "2.2.0", "version": "2.3.0",
"description": "Hasura-auth client", "description": "Hasura-auth client",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -445,16 +445,14 @@ export class HasuraAuthClient {
* *
* @docs https://docs.nhost.io/reference/javascript/auth/elevate-security-key * @docs https://docs.nhost.io/reference/javascript/auth/elevate-security-key
*/ */
async elevateWebAuthn( async elevateEmailSecurityKey(email: string) {
email: string
): Promise<SignInResponse & { providerUrl?: string; provider?: string }> {
if (!email) { if (!email) {
throw Error('A user email is required') throw Error('A user email is required')
} }
const res = await elevateEmailSecurityKeyPromise(this._client, email) const res = await elevateEmailSecurityKeyPromise(this._client, email)
return { ...getAuthenticationResult(res), mfa: null } return { ...res, mfa: null }
} }
/** /**

View File

@@ -3,15 +3,19 @@ import {
PublicKeyCredentialRequestOptionsJSON PublicKeyCredentialRequestOptionsJSON
} from '@simplewebauthn/typescript-types' } from '@simplewebauthn/typescript-types'
import { import {
AuthActionErrorState,
AuthActionSuccessState,
AuthClient, AuthClient,
AuthErrorPayload,
CodifiedError, CodifiedError,
postFetch, postFetch,
SessionActionHandlerResult,
SignInResponse SignInResponse
} from '..' } from '..'
import { startAuthentication } from '@simplewebauthn/browser' import { startAuthentication } from '@simplewebauthn/browser'
export interface ElevateWithSecurityKeyHandlerResult extends SessionActionHandlerResult { export interface ElevateWithSecurityKeyHandlerResult
extends AuthActionSuccessState,
AuthActionErrorState {
elevated: boolean elevated: boolean
} }
@@ -36,48 +40,42 @@ export const elevateEmailSecurityKeyPromise = (authClient: AuthClient, email: st
throw new CodifiedError(e as Error) throw new CodifiedError(e as Error)
} }
const { try {
data: { session }, const {
error: signInError data: { session },
} = await postFetch<SignInResponse>( error: signInError
`${authClient.backendUrl}/elevate/webauthn/verify`, } = await postFetch<SignInResponse>(
{ `${authClient.backendUrl}/elevate/webauthn/verify`,
email, {
credential email,
}, credential
accessToken },
) accessToken
)
if (session && !signInError) { if (session && !signInError) {
authClient.interpreter?.send({ authClient.interpreter?.send({
type: 'SESSION_UPDATE', type: 'SESSION_UPDATE',
data: { data: {
session session
} }
}) })
}
authClient.interpreter?.onTransition((state) => {
if (state.matches({ authentication: 'signedIn' })) {
resolve({ resolve({
accessToken: state.context.accessToken.value,
refreshToken: state.context.refreshToken.value,
error: null, error: null,
isError: false, isError: false,
isSuccess: true, isSuccess: true,
user: state.context.user,
elevated: true elevated: true
}) })
} else {
resolve({
accessToken: state.context.accessToken.value,
refreshToken: state.context.refreshToken.value,
error: null, // TODO pass error
isError: true,
isSuccess: false,
user: state.context.user,
elevated: false
})
} }
}) } catch (e) {
const { error } = e as { error: AuthErrorPayload }
resolve({
error,
isError: true,
isSuccess: false,
elevated: false
})
}
}) })

View File

@@ -1,5 +1,11 @@
# @nhost/hasura-storage-js # @nhost/hasura-storage-js
## 2.4.0
### Minor Changes
- 2505b2e: fix: fix headers sent with getPresignedUrl
## 2.3.0 ## 2.3.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/hasura-storage-js", "name": "@nhost/hasura-storage-js",
"version": "2.3.0", "version": "2.4.0",
"description": "Hasura-storage client", "description": "Hasura-storage client",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -99,12 +99,6 @@ export class HasuraStorageApi {
async downloadFile(params: StorageDownloadFileParams): Promise<StorageDownloadFileResponse> { async downloadFile(params: StorageDownloadFileParams): Promise<StorageDownloadFileResponse> {
try { try {
const { fileId, headers: customHeaders = {}, ...imageTransformationParams } = params const { fileId, headers: customHeaders = {}, ...imageTransformationParams } = params
const authHeaders = this.generateAuthHeaders()
const headers = new Headers(customHeaders)
authHeaders.forEach((value, key) => {
headers?.append(key, value)
})
const urlWithParams = appendImageTransformationParameters( const urlWithParams = appendImageTransformationParameters(
`${this.url}/files/${fileId}`, `${this.url}/files/${fileId}`,
@@ -113,7 +107,7 @@ export class HasuraStorageApi {
const response = await fetch(urlWithParams, { const response = await fetch(urlWithParams, {
method: 'GET', method: 'GET',
headers headers: {...this.generateAuthHeaders(), ...customHeaders}
}) })
if (!response.ok) { if (!response.ok) {
@@ -131,6 +125,7 @@ export class HasuraStorageApi {
async getPresignedUrl(params: ApiGetPresignedUrlParams): Promise<ApiGetPresignedUrlResponse> { async getPresignedUrl(params: ApiGetPresignedUrlParams): Promise<ApiGetPresignedUrlResponse> {
try { try {
const { fileId } = params const { fileId } = params
const response = await fetch(`${this.url}/files/${fileId}/presignedurl`, { const response = await fetch(`${this.url}/files/${fileId}/presignedurl`, {
method: 'GET', method: 'GET',
headers: this.generateAuthHeaders() headers: this.generateAuthHeaders()
@@ -185,16 +180,19 @@ export class HasuraStorageApi {
return this return this
} }
private generateAuthHeaders(): Headers { private generateAuthHeaders(): HeadersInit | undefined {
const headers = new Headers() if (!this.adminSecret && !this.accessToken) {
return undefined
}
if (this.adminSecret) { if (this.adminSecret) {
headers.append('x-hasura-admin-secret', this.adminSecret) return {
} 'x-hasura-admin-secret': this.adminSecret
if (this.accessToken) { }
headers.append('Authorization', `Bearer ${this.accessToken}`)
} }
return headers return {
Authorization: `Bearer ${this.accessToken}`
}
} }
} }

View File

@@ -1,5 +1,18 @@
# @nhost/nextjs # @nhost/nextjs
## 2.1.2
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/react@3.2.0
## 2.1.1
### Patch Changes
- @nhost/react@3.1.1
## 2.1.0 ## 2.1.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/nextjs", "name": "@nhost/nextjs",
"version": "2.1.0", "version": "2.1.2",
"description": "Nhost NextJS library", "description": "Nhost NextJS library",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -1,5 +1,19 @@
# @nhost/nhost-js # @nhost/nhost-js
## 3.0.5
### Patch Changes
- Updated dependencies [017f1a6]
- @nhost/hasura-auth-js@2.3.0
## 3.0.4
### Patch Changes
- Updated dependencies [2505b2e]
- @nhost/hasura-storage-js@2.4.0
## 3.0.3 ## 3.0.3
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/nhost-js", "name": "@nhost/nhost-js",
"version": "3.0.3", "version": "3.0.5",
"description": "Nhost JavaScript SDK", "description": "Nhost JavaScript SDK",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -1,5 +1,21 @@
# @nhost/react # @nhost/react
## 3.2.0
### Minor Changes
- 017f1a6: feat: add elevated permission examples
### Patch Changes
- @nhost/nhost-js@3.0.5
## 3.1.1
### Patch Changes
- @nhost/nhost-js@3.0.4
## 3.1.0 ## 3.1.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/react", "name": "@nhost/react",
"version": "3.1.0", "version": "3.2.0",
"description": "Nhost React library", "description": "Nhost React library",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -35,14 +35,16 @@ export const useElevateSecurityKeyEmail = (): ElevateWithSecurityKeyHook => {
const nhost = useNhostClient() const nhost = useNhostClient()
const claims = useHasuraClaims() const claims = useHasuraClaims()
const [elevated, setElevated] = useState(claims?.['x-hasura-auth-elevated'] === user?.id) const hasElevatedClaim = user ? claims?.['x-hasura-auth-elevated'] === user?.id : false
const [elevated, setElevated] = useState(!!hasElevatedClaim)
const elevateEmailSecurityKey: ElevateWithSecurityKeyHandler = (email: string) => const elevateEmailSecurityKey: ElevateWithSecurityKeyHandler = (email: string) =>
elevateEmailSecurityKeyPromise(nhost.auth.client, email) elevateEmailSecurityKeyPromise(nhost.auth.client, email)
useEffect(() => { useEffect(() => {
setElevated(claims?.['x-hasura-auth-elevated'] === user?.id) setElevated(!!hasElevatedClaim)
}, [claims, user]) }, [hasElevatedClaim])
return { return {
elevated, elevated,

View File

@@ -1,5 +1,21 @@
# @nhost/vue # @nhost/vue
## 2.2.0
### Minor Changes
- 017f1a6: feat: add elevated permission examples
### Patch Changes
- @nhost/nhost-js@3.0.5
## 2.1.1
### Patch Changes
- @nhost/nhost-js@3.0.4
## 2.1.0 ## 2.1.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/vue", "name": "@nhost/vue",
"version": "2.1.0", "version": "2.2.0",
"description": "Nhost Vue library", "description": "Nhost Vue library",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -34,3 +34,4 @@ export * from './useUserRoles'
export * from './useElevateSecurityKeyEmail' export * from './useElevateSecurityKeyEmail'
export * from './useSignUpEmailSecurityKey' export * from './useSignUpEmailSecurityKey'
export * from './useSignInEmailSecurityKey' export * from './useSignInEmailSecurityKey'
export * from './useAddSecurityKey'

View File

@@ -0,0 +1,67 @@
import {
ActionErrorState,
ActionLoadingState,
ActionSuccessState,
AddSecurityKeyHandlerResult,
addSecurityKeyPromise,
ErrorPayload
} from '@nhost/nhost-js'
import { ToRefs, ref, computed } from 'vue'
import { useNhostClient } from './useNhostClient'
interface AddSecurityKeyHandler {
(
/** Optional human-readable name of the security key */
nickname?: string
): Promise<AddSecurityKeyHandlerResult>
}
export interface AddSecuritKeyComposableResult
extends ToRefs<ActionErrorState>,
ToRefs<ActionSuccessState>,
ToRefs<ActionLoadingState> {
/** Add a security key to the current user with the WebAuthn API */
add: AddSecurityKeyHandler
}
/**
* Use the composable `useAddSecurityKey` to add a WebAuthn security key.
*
* @example
* ```tsx
* const { add, isLoading, isSuccess, isError, error } = useAddSecurityKey()
*
* const handleFormSubmit = async (e) => {
* e.preventDefault();
*
* await add('key nickname')
* }
* ```
*
* @docs https://docs.nhost.io/reference/vue/use-add-security-key
*/
export const useAddSecurityKey = (): AddSecuritKeyComposableResult => {
const { nhost } = useNhostClient()
const error = ref<ErrorPayload | null>(null)
const isSuccess = computed(() => !error)
const isError = computed(() => !!error)
const isLoading = ref<boolean>(false)
const add: AddSecurityKeyHandler = async (nickname) => {
isLoading.value = true
const result = await addSecurityKeyPromise(nhost.auth.client, nickname)
const { error: addSecurityKeyError } = result
if (error) {
error.value = addSecurityKeyError
}
isLoading.value = false
return result
}
return { add, isLoading, isSuccess, isError, error }
}

View File

@@ -2,7 +2,7 @@ import {
elevateEmailSecurityKeyPromise, elevateEmailSecurityKeyPromise,
ElevateWithSecurityKeyHandlerResult ElevateWithSecurityKeyHandlerResult
} from '@nhost/nhost-js' } from '@nhost/nhost-js'
import { computed, ref, unref } from 'vue' import { computed, unref } from 'vue'
import { RefOrValue } from './helpers' import { RefOrValue } from './helpers'
import { useHasuraClaims } from './useHasuraClaims' import { useHasuraClaims } from './useHasuraClaims'
import { useNhostClient } from './useNhostClient' import { useNhostClient } from './useNhostClient'
@@ -39,7 +39,11 @@ export const useElevateSecurityKeyEmail = (): ElevateWithSecurityKeyResult => {
const claims = useHasuraClaims() const claims = useHasuraClaims()
const { nhost } = useNhostClient() const { nhost } = useNhostClient()
const elevated = computed(() => claims.value?.['x-hasura-auth-elevated'] === user.value?.id) const hasElevatedClaim = computed(() =>
user.value ? claims.value?.['x-hasura-auth-elevated'] === user.value?.id : false
)
const elevated = computed(() => !!hasElevatedClaim.value)
const elevateEmailSecurityKey: ElevateWithSecurityKeyHandler = (email: RefOrValue<string>) => const elevateEmailSecurityKey: ElevateWithSecurityKeyHandler = (email: RefOrValue<string>) =>
elevateEmailSecurityKeyPromise(nhost.auth.client, unref(email)) elevateEmailSecurityKeyPromise(nhost.auth.client, unref(email))

2399
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff