Compare commits
79 Commits
@nhost/rea
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fec74e501 | ||
|
|
e94b28b3bc | ||
|
|
f591f76256 | ||
|
|
58fb955dc6 | ||
|
|
f472d42ae9 | ||
|
|
9993bea7ab | ||
|
|
6f33fc6ce6 | ||
|
|
b6858c5638 | ||
|
|
9b01c3ba93 | ||
|
|
fcfd6a9c13 | ||
|
|
e4daefe637 | ||
|
|
a0bcbb6269 | ||
|
|
1ec1004507 | ||
|
|
756d996096 | ||
|
|
c0f1d03c3c | ||
|
|
af55789d07 | ||
|
|
cb28676895 | ||
|
|
939a3d1090 | ||
|
|
0163d0588b | ||
|
|
c4124f22b0 | ||
|
|
7188b0971c | ||
|
|
6ac969320c | ||
|
|
414bc2e75b | ||
|
|
6862e1e24d | ||
|
|
4b9deaa2f7 | ||
|
|
cf366cef35 | ||
|
|
00d041f6b4 | ||
|
|
da06fef64e | ||
|
|
09be9582f8 | ||
|
|
6b26fed8ae | ||
|
|
5a62c66fc4 | ||
|
|
17e0e6d116 | ||
|
|
938000e61b | ||
|
|
d700107222 | ||
|
|
69d9e40187 | ||
|
|
fc5b18fdf0 | ||
|
|
fa3eb980a0 | ||
|
|
efad3a2b08 | ||
|
|
563fa4fe9b | ||
|
|
6f0a30059a | ||
|
|
47cda5d716 | ||
|
|
3f625ce9e1 | ||
|
|
38d2609249 | ||
|
|
030243cd45 | ||
|
|
c1905243d0 | ||
|
|
37627cc50e | ||
|
|
009f68d500 | ||
|
|
b752cc2be8 | ||
|
|
72fc7d4e44 | ||
|
|
80ef14e50a | ||
|
|
543ea2a0e7 | ||
|
|
6764d476fd | ||
|
|
7bed0eadc9 | ||
|
|
c7644ace34 | ||
|
|
49cdb2843e | ||
|
|
6f45856c46 | ||
|
|
61e719eea0 | ||
|
|
208bdbba2d | ||
|
|
cd62e1e833 | ||
|
|
1dfb11d7e8 | ||
|
|
8b5c4ed443 | ||
|
|
6bd5c96ed5 | ||
|
|
6b8762a62e | ||
|
|
ddeff7cbd6 | ||
|
|
ed952c1251 | ||
|
|
34e73f18bd | ||
|
|
84262a24f1 | ||
|
|
ec2a88d69c | ||
|
|
fe1049df6b | ||
|
|
a924d21815 | ||
|
|
4405535d4a | ||
|
|
af15771517 | ||
|
|
c066ea5b75 | ||
|
|
80e42b939b | ||
|
|
3ea6f685e2 | ||
|
|
ac77f427c3 | ||
|
|
0f95ee5bb4 | ||
|
|
47406d3617 | ||
|
|
125bc9a749 |
8
.github/workflows/tests.yaml
vendored
8
.github/workflows/tests.yaml
vendored
@@ -67,12 +67,18 @@ jobs:
|
|||||||
# * Run the `ci` script of the current package of the matrix. Dependencies build is cached by Turborepo
|
# * Run the `ci` script of the current package of the matrix. Dependencies build is cached by Turborepo
|
||||||
- name: Run e2e test
|
- name: Run e2e test
|
||||||
run: pnpm run e2e -- --filter="${{ matrix.package.name }}"
|
run: pnpm run e2e -- --filter="${{ matrix.package.name }}"
|
||||||
|
- id: file-name
|
||||||
|
if: ${{ failure() }}
|
||||||
|
name: Tranform package name into a valid file name
|
||||||
|
run: |
|
||||||
|
PACKAGE_FILE_NAME=$(echo "${{ matrix.package.name }}" | sed 's/@//g; s/\//-/g')
|
||||||
|
echo "::set-output name=fileName::$PACKAGE_FILE_NAME"
|
||||||
# * Run this step only if the previous step failed, and some Cypress screenshots/videos exist
|
# * Run this step only if the previous step failed, and some Cypress screenshots/videos exist
|
||||||
- name: Upload Cypress videos and screenshots
|
- name: Upload Cypress videos and screenshots
|
||||||
if: ${{ failure() && hashFiles(format('{0}/cypress/screenshots/**', matrix.package.path), format('{0}/cypress/videos/**', matrix.package.path)) != ''}}
|
if: ${{ failure() && hashFiles(format('{0}/cypress/screenshots/**', matrix.package.path), format('{0}/cypress/videos/**', matrix.package.path)) != ''}}
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: cypress-${{ matrix.package.name }}
|
name: cypress-${{ steps.file-name.outputs.fileName }}
|
||||||
path: |
|
path: |
|
||||||
${{format('{0}/cypress/screenshots/**', matrix.package.path)}}
|
${{format('{0}/cypress/screenshots/**', matrix.package.path)}}
|
||||||
${{format('{0}/cypress/videos/**', matrix.package.path)}}
|
${{format('{0}/cypress/videos/**', matrix.package.path)}}
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -285,21 +285,28 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<sub><b>Savin Vadim</b></sub>
|
<sub><b>Savin Vadim</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/muttenzer">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/49474412?v=4" width="100;" alt="muttenzer"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Muttenzer</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/ahmic">
|
<a href="https://github.com/ahmic">
|
||||||
<img src="https://avatars.githubusercontent.com/u/13452362?v=4" width="100;" alt="ahmic"/>
|
<img src="https://avatars.githubusercontent.com/u/13452362?v=4" width="100;" alt="ahmic"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Amir Ahmic</b></sub>
|
<sub><b>Amir Ahmic</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/akd-io">
|
<a href="https://github.com/akd-io">
|
||||||
<img src="https://avatars.githubusercontent.com/u/30059155?v=4" width="100;" alt="akd-io"/>
|
<img src="https://avatars.githubusercontent.com/u/30059155?v=4" width="100;" alt="akd-io"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Anders Kjær Damgaard</b></sub>
|
<sub><b>Anders Kjær Damgaard</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/Sonichigo">
|
<a href="https://github.com/Sonichigo">
|
||||||
<img src="https://avatars.githubusercontent.com/u/53110238?v=4" width="100;" alt="Sonichigo"/>
|
<img src="https://avatars.githubusercontent.com/u/53110238?v=4" width="100;" alt="Sonichigo"/>
|
||||||
@@ -334,15 +341,15 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<br />
|
<br />
|
||||||
<sub><b>Helio Alves</b></sub>
|
<sub><b>Helio Alves</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/nkhdo">
|
<a href="https://github.com/nkhdo">
|
||||||
<img src="https://avatars.githubusercontent.com/u/26102306?v=4" width="100;" alt="nkhdo"/>
|
<img src="https://avatars.githubusercontent.com/u/26102306?v=4" width="100;" alt="nkhdo"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Hoang Do</b></sub>
|
<sub><b>Hoang Do</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/MelodicCrypter">
|
<a href="https://github.com/MelodicCrypter">
|
||||||
<img src="https://avatars.githubusercontent.com/u/18341500?v=4" width="100;" alt="MelodicCrypter"/>
|
<img src="https://avatars.githubusercontent.com/u/18341500?v=4" width="100;" alt="MelodicCrypter"/>
|
||||||
@@ -357,6 +364,13 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<sub><b>Jacob Duval</b></sub>
|
<sub><b>Jacob Duval</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/kylehayes">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/509932?v=4" width="100;" alt="kylehayes"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Kyle Hayes</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/leothorp">
|
<a href="https://github.com/leothorp">
|
||||||
<img src="https://avatars.githubusercontent.com/u/12928449?v=4" width="100;" alt="leothorp"/>
|
<img src="https://avatars.githubusercontent.com/u/12928449?v=4" width="100;" alt="leothorp"/>
|
||||||
@@ -370,7 +384,8 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<br />
|
<br />
|
||||||
<sub><b>Max Reynolds</b></sub>
|
<sub><b>Max Reynolds</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/ghoshnirmalya">
|
<a href="https://github.com/ghoshnirmalya">
|
||||||
<img src="https://avatars.githubusercontent.com/u/6391763?v=4" width="100;" alt="ghoshnirmalya"/>
|
<img src="https://avatars.githubusercontent.com/u/6391763?v=4" width="100;" alt="ghoshnirmalya"/>
|
||||||
@@ -384,8 +399,7 @@ Here are some ways of contributing to making Nhost better:
|
|||||||
<br />
|
<br />
|
||||||
<sub><b>Quentin Decré</b></sub>
|
<sub><b>Quentin Decré</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/atapas">
|
<a href="https://github.com/atapas">
|
||||||
<img src="https://avatars.githubusercontent.com/u/3633137?v=4" width="100;" alt="atapas"/>
|
<img src="https://avatars.githubusercontent.com/u/3633137?v=4" width="100;" alt="atapas"/>
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ module.exports = {
|
|||||||
'tests/**/*.ts',
|
'tests/**/*.ts',
|
||||||
'tests/**/*.d.ts'
|
'tests/**/*.d.ts'
|
||||||
],
|
],
|
||||||
plugins: ['@typescript-eslint', 'simple-import-sort'],
|
plugins: ['@typescript-eslint', 'simple-import-sort', 'cypress'],
|
||||||
|
extends: ['plugin:cypress/recommended'],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
sourceType: 'module'
|
sourceType: 'module'
|
||||||
@@ -60,13 +61,5 @@ module.exports = {
|
|||||||
allowAnonymousFunction: true
|
allowAnonymousFunction: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['*.test.js', '*.spec.js', '*.test.ts', '*.spec.ts', '*.cy.js', '*.cy.ts'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unused-expressions': 'off'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
const base = require('./.eslint.base')
|
const base = require('./.eslint.base')
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...base,
|
...base,
|
||||||
extends: ['react-app', 'plugin:react/recommended', 'plugin:react-hooks/recommended'],
|
extends: [
|
||||||
|
...base.extends,
|
||||||
|
'react-app',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:react/jsx-runtime'
|
||||||
|
],
|
||||||
plugins: [...base.plugins, 'react', 'react-hooks']
|
plugins: [...base.plugins, 'react', 'react-hooks']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const base = require('./.eslint.base')
|
const base = require('./.eslint.base')
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...base,
|
...base,
|
||||||
extends: ['plugin:import/recommended', 'plugin:import/typescript'],
|
extends: [...base.extends, 'plugin:import/recommended', 'plugin:import/typescript'],
|
||||||
parser: 'vue-eslint-parser',
|
parser: 'vue-eslint-parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
...base.parserOptions,
|
...base.parserOptions,
|
||||||
|
|||||||
@@ -60,11 +60,35 @@ The default role is used when no role is specified in the GraphQL request. By de
|
|||||||
|
|
||||||
### Allowed Roles
|
### Allowed Roles
|
||||||
|
|
||||||
|
Allowed roles are roles the user is allowed to use when making a GraphQL request. Usually you would change the role from `user` (the default role) to some other role because you want Hasura to use a different role to resolve permissions for a particular GraphQL request.
|
||||||
|
|
||||||
By default, users have two allowed roles:
|
By default, users have two allowed roles:
|
||||||
|
|
||||||
- `user`
|
- `user`
|
||||||
- `me`
|
- `me`
|
||||||
|
|
||||||
|
You can manage what allowed roles users should get when they sign up under **Users** -> **Roles & Permissions**.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
You must also add the roles manually to the `auth.roles` table.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
It's also possible to give users a subset of allowed roles during signup.
|
||||||
|
|
||||||
|
**Example:** Only give the `user` role (without the `me` role) for the user's allowed roles:
|
||||||
|
|
||||||
|
```js
|
||||||
|
await nhost.auth.signUp({
|
||||||
|
email: 'joe@example.com',
|
||||||
|
password: 'secret-password'
|
||||||
|
options: {
|
||||||
|
allowedRoles: ['user']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
### Public Role
|
### Public Role
|
||||||
|
|
||||||
The `public` role is used to resolve GraphQL permissions for unauthenticated users.
|
The `public` role is used to resolve GraphQL permissions for unauthenticated users.
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ The database is managed via the Hasura Console where you can manage the database
|
|||||||
|
|
||||||
Hasura Console is where you manage your database. This is where you create and manage tables, schemas, and data.
|
Hasura Console is where you manage your database. This is where you create and manage tables, schemas, and data.
|
||||||
|
|
||||||
Open the Hasura Console by clicking on **Data** in the top menu in the Nhost Dashboard, copy the **admin secret**, and click **Open Hasura**. Use the **admin secret** to sign in.
|
1) Open the Hasura Console by clicking on **GraphQL** in the top menu in the Nhost Dashboard.
|
||||||
|
2) Click **Open Hasura Console** at the top right of the page.
|
||||||
|
3) Copy the **admin secret**, and click **Open Hasura**.
|
||||||
|
4) Use the **admin secret** to sign in.
|
||||||
|
|
||||||
<video width="99%" autoPlay muted loop controls="true">
|
<video width="99%" autoPlay muted loop controls="true">
|
||||||
<source src="/videos/open-hasura-console.mp4" type="video/mp4" />
|
<source src="/videos/open-hasura-console.mp4" type="video/mp4" />
|
||||||
|
|||||||
@@ -54,6 +54,18 @@ query {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Local Custom Permission Variables
|
||||||
|
|
||||||
|
To use custom permission variables locally, add your claims to the `config.yml` as following:
|
||||||
|
|
||||||
|
```
|
||||||
|
auth:
|
||||||
|
jwt:
|
||||||
|
custom_claims: '{"organisation-id":"profile.organisation.id"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Your custom claim will be automatically prefixed with `x-hasura-`, therefore, the example above results in a custom permission variable named `x-hasura-organisation-id`.
|
||||||
|
|
||||||
## Roles
|
## Roles
|
||||||
|
|
||||||
Every GraphQL request is resolved based on a **single role**. Roles are added in the Hasura Console when selecting a table and clicking **Permisisons**.
|
Every GraphQL request is resolved based on a **single role**. Roles are added in the Hasura Console when selecting a table and clicking **Permisisons**.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: signUp()
|
|||||||
sidebar_label: signUp()
|
sidebar_label: signUp()
|
||||||
slug: /reference/javascript/auth/sign-up
|
slug: /reference/javascript/auth/sign-up
|
||||||
description: Use `nhost.auth.signUp` to sign up a user using email and password. If you want to sign up a user using passwordless email (Magic Link), SMS, or an OAuth provider, use the `signIn` function instead.
|
description: Use `nhost.auth.signUp` to sign up a user using email and password. If you want to sign up a user using passwordless email (Magic Link), SMS, or an OAuth provider, use the `signIn` function instead.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L101
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L102
|
||||||
---
|
---
|
||||||
|
|
||||||
# `signUp()`
|
# `signUp()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: signIn()
|
|||||||
sidebar_label: signIn()
|
sidebar_label: signIn()
|
||||||
slug: /reference/javascript/auth/sign-in
|
slug: /reference/javascript/auth/sign-in
|
||||||
description: Use `nhost.auth.signIn` to sign in a user using email and password, passwordless (email or sms) or an external provider. `signIn` can be used to sign in a user in various ways depending on the parameters.
|
description: Use `nhost.auth.signIn` to sign in a user using email and password, passwordless (email or sms) or an external provider. `signIn` can be used to sign in a user in various ways depending on the parameters.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L144
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L145
|
||||||
---
|
---
|
||||||
|
|
||||||
# `signIn()`
|
# `signIn()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: signOut()
|
|||||||
sidebar_label: signOut()
|
sidebar_label: signOut()
|
||||||
slug: /reference/javascript/auth/sign-out
|
slug: /reference/javascript/auth/sign-out
|
||||||
description: Use `nhost.auth.signOut` to sign out the user.
|
description: Use `nhost.auth.signOut` to sign out the user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L222
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L230
|
||||||
---
|
---
|
||||||
|
|
||||||
# `signOut()`
|
# `signOut()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: resetPassword()
|
|||||||
sidebar_label: resetPassword()
|
sidebar_label: resetPassword()
|
||||||
slug: /reference/javascript/auth/reset-password
|
slug: /reference/javascript/auth/reset-password
|
||||||
description: Use `nhost.auth.resetPassword` to reset the password for a user. This will send a reset-password link in an email to the user. When the user clicks the reset-password link the user is automatically signed-in. Once signed-in, the user can change their password using `nhost.auth.changePassword()`.
|
description: Use `nhost.auth.resetPassword` to reset the password for a user. This will send a reset-password link in an email to the user. When the user clicks the reset-password link the user is automatically signed-in. Once signed-in, the user can change their password using `nhost.auth.changePassword()`.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L238
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L246
|
||||||
---
|
---
|
||||||
|
|
||||||
# `resetPassword()`
|
# `resetPassword()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: changePassword()
|
|||||||
sidebar_label: changePassword()
|
sidebar_label: changePassword()
|
||||||
slug: /reference/javascript/auth/change-password
|
slug: /reference/javascript/auth/change-password
|
||||||
description: Use `nhost.auth.changePassword` to change the password for the user. The old password is not needed.
|
description: Use `nhost.auth.changePassword` to change the password for the user. The old password is not needed.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L254
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L262
|
||||||
---
|
---
|
||||||
|
|
||||||
# `changePassword()`
|
# `changePassword()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: sendVerificationEmail()
|
|||||||
sidebar_label: sendVerificationEmail()
|
sidebar_label: sendVerificationEmail()
|
||||||
slug: /reference/javascript/auth/send-verification-email
|
slug: /reference/javascript/auth/send-verification-email
|
||||||
description: Use `nhost.auth.sendVerificationEmail` to send a verification email to the specified email. The email contains a verification-email link. When the user clicks the verification-email link their email is verified.
|
description: Use `nhost.auth.sendVerificationEmail` to send a verification email to the specified email. The email contains a verification-email link. When the user clicks the verification-email link their email is verified.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L270
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L278
|
||||||
---
|
---
|
||||||
|
|
||||||
# `sendVerificationEmail()`
|
# `sendVerificationEmail()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: changeEmail()
|
|||||||
sidebar_label: changeEmail()
|
sidebar_label: changeEmail()
|
||||||
slug: /reference/javascript/auth/change-email
|
slug: /reference/javascript/auth/change-email
|
||||||
description: Use `nhost.auth.changeEmail` to change a user's email. This will send a confirm-email-change link in an email to the new email. Once the user clicks on the confirm-email-change link the email will be change to the new email.
|
description: Use `nhost.auth.changeEmail` to change a user's email. This will send a confirm-email-change link in an email to the new email. Once the user clicks on the confirm-email-change link the email will be change to the new email.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L289
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L297
|
||||||
---
|
---
|
||||||
|
|
||||||
# `changeEmail()`
|
# `changeEmail()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: deanonymize()
|
|||||||
sidebar_label: deanonymize()
|
sidebar_label: deanonymize()
|
||||||
slug: /reference/javascript/auth/deanonymize
|
slug: /reference/javascript/auth/deanonymize
|
||||||
description: Use `nhost.auth.deanonymize` to deanonymize a user.
|
description: Use `nhost.auth.deanonymize` to deanonymize a user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L305
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L313
|
||||||
---
|
---
|
||||||
|
|
||||||
# `deanonymize()`
|
# `deanonymize()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: onTokenChanged()
|
|||||||
sidebar_label: onTokenChanged()
|
sidebar_label: onTokenChanged()
|
||||||
slug: /reference/javascript/auth/on-token-changed
|
slug: /reference/javascript/auth/on-token-changed
|
||||||
description: Use `nhost.auth.onTokenChanged` to add a custom function that runs every time the access or refresh token is changed.
|
description: Use `nhost.auth.onTokenChanged` to add a custom function that runs every time the access or refresh token is changed.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L348
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L356
|
||||||
---
|
---
|
||||||
|
|
||||||
# `onTokenChanged()`
|
# `onTokenChanged()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: onAuthStateChanged()
|
|||||||
sidebar_label: onAuthStateChanged()
|
sidebar_label: onAuthStateChanged()
|
||||||
slug: /reference/javascript/auth/on-auth-state-changed
|
slug: /reference/javascript/auth/on-auth-state-changed
|
||||||
description: Use `nhost.auth.onAuthStateChanged` to add a custom function that runs every time the authentication status of the user changes. E.g. add a custom function that runs every time the authentication status changes from signed-in to signed-out.
|
description: Use `nhost.auth.onAuthStateChanged` to add a custom function that runs every time the authentication status of the user changes. E.g. add a custom function that runs every time the authentication status changes from signed-in to signed-out.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L383
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L391
|
||||||
---
|
---
|
||||||
|
|
||||||
# `onAuthStateChanged()`
|
# `onAuthStateChanged()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: isAuthenticated()
|
|||||||
sidebar_label: isAuthenticated()
|
sidebar_label: isAuthenticated()
|
||||||
slug: /reference/javascript/auth/is-authenticated
|
slug: /reference/javascript/auth/is-authenticated
|
||||||
description: Use `nhost.auth.isAuthenticated` to check if the user is authenticated or not.
|
description: Use `nhost.auth.isAuthenticated` to check if the user is authenticated or not.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L425
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L433
|
||||||
---
|
---
|
||||||
|
|
||||||
# `isAuthenticated()`
|
# `isAuthenticated()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: isAuthenticatedAsync()
|
|||||||
sidebar_label: isAuthenticatedAsync()
|
sidebar_label: isAuthenticatedAsync()
|
||||||
slug: /reference/javascript/auth/is-authenticated-async
|
slug: /reference/javascript/auth/is-authenticated-async
|
||||||
description: Use `nhost.auth.isAuthenticatedAsync` to wait (await) for any internal authentication network requests to finish and then return the authentication status.
|
description: Use `nhost.auth.isAuthenticatedAsync` to wait (await) for any internal authentication network requests to finish and then return the authentication status.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L443
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L451
|
||||||
---
|
---
|
||||||
|
|
||||||
# `isAuthenticatedAsync()`
|
# `isAuthenticatedAsync()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: getAuthenticationStatus()
|
|||||||
sidebar_label: getAuthenticationStatus()
|
sidebar_label: getAuthenticationStatus()
|
||||||
slug: /reference/javascript/auth/get-authentication-status
|
slug: /reference/javascript/auth/get-authentication-status
|
||||||
description: Use `nhost.auth.getAuthenticationStatus` to get the authentication status of the user.
|
description: Use `nhost.auth.getAuthenticationStatus` to get the authentication status of the user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L469
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L477
|
||||||
---
|
---
|
||||||
|
|
||||||
# `getAuthenticationStatus()`
|
# `getAuthenticationStatus()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: getAccessToken()
|
|||||||
sidebar_label: getAccessToken()
|
sidebar_label: getAccessToken()
|
||||||
slug: /reference/javascript/auth/get-access-token
|
slug: /reference/javascript/auth/get-access-token
|
||||||
description: Use `nhost.auth.getAccessToken` to get the access token of the user.
|
description: Use `nhost.auth.getAccessToken` to get the access token of the user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L499
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L507
|
||||||
---
|
---
|
||||||
|
|
||||||
# `getAccessToken()`
|
# `getAccessToken()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: getDecodedAccessToken()
|
|||||||
sidebar_label: getDecodedAccessToken()
|
sidebar_label: getDecodedAccessToken()
|
||||||
slug: /reference/javascript/auth/get-decoded-access-token
|
slug: /reference/javascript/auth/get-decoded-access-token
|
||||||
description: Use `nhost.auth.getDecodedAccessToken` to get the decoded access token of the user.
|
description: Use `nhost.auth.getDecodedAccessToken` to get the decoded access token of the user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L514
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L522
|
||||||
---
|
---
|
||||||
|
|
||||||
# `getDecodedAccessToken()`
|
# `getDecodedAccessToken()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: getHasuraClaims()
|
|||||||
sidebar_label: getHasuraClaims()
|
sidebar_label: getHasuraClaims()
|
||||||
slug: /reference/javascript/auth/get-hasura-claims
|
slug: /reference/javascript/auth/get-hasura-claims
|
||||||
description: Use `nhost.auth.getHasuraClaims` to get the Hasura claims of the user.
|
description: Use `nhost.auth.getHasuraClaims` to get the Hasura claims of the user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L531
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L539
|
||||||
---
|
---
|
||||||
|
|
||||||
# `getHasuraClaims()`
|
# `getHasuraClaims()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: getHasuraClaim()
|
|||||||
sidebar_label: getHasuraClaim()
|
sidebar_label: getHasuraClaim()
|
||||||
slug: /reference/javascript/auth/get-hasura-claim
|
slug: /reference/javascript/auth/get-hasura-claim
|
||||||
description: Use `nhost.auth.getHasuraClaim` to get the value of a specific Hasura claim of the user.
|
description: Use `nhost.auth.getHasuraClaim` to get the value of a specific Hasura claim of the user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L549
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L557
|
||||||
---
|
---
|
||||||
|
|
||||||
# `getHasuraClaim()`
|
# `getHasuraClaim()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: refreshSession()
|
|||||||
sidebar_label: refreshSession()
|
sidebar_label: refreshSession()
|
||||||
slug: /reference/javascript/auth/refresh-session
|
slug: /reference/javascript/auth/refresh-session
|
||||||
description: Use `nhost.auth.refreshSession` to refresh the session with either the current internal refresh token or an external refresh token.
|
description: Use `nhost.auth.refreshSession` to refresh the session with either the current internal refresh token or an external refresh token.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L572
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L580
|
||||||
---
|
---
|
||||||
|
|
||||||
# `refreshSession()`
|
# `refreshSession()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: getSession()
|
|||||||
sidebar_label: getSession()
|
sidebar_label: getSession()
|
||||||
slug: /reference/javascript/auth/get-session
|
slug: /reference/javascript/auth/get-session
|
||||||
description: Use `nhost.auth.getSession()` to get the session of the user.
|
description: Use `nhost.auth.getSession()` to get the session of the user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L616
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L624
|
||||||
---
|
---
|
||||||
|
|
||||||
# `getSession()`
|
# `getSession()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: getUser()
|
|||||||
sidebar_label: getUser()
|
sidebar_label: getUser()
|
||||||
slug: /reference/javascript/auth/get-user
|
slug: /reference/javascript/auth/get-user
|
||||||
description: Use `nhost.auth.getUser()` to get the signed-in user.
|
description: Use `nhost.auth.getUser()` to get the signed-in user.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L631
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L639
|
||||||
---
|
---
|
||||||
|
|
||||||
# `getUser()`
|
# `getUser()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: HasuraAuthClient
|
|||||||
sidebar_label: Auth
|
sidebar_label: Auth
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
slug: /reference/javascript/auth
|
slug: /reference/javascript/auth
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L59
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/hasura-auth-client.ts#L60
|
||||||
---
|
---
|
||||||
|
|
||||||
# `HasuraAuthClient`
|
# `HasuraAuthClient`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: AuthChangeEvent
|
|||||||
sidebar_label: AuthChangeEvent
|
sidebar_label: AuthChangeEvent
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L128
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L134
|
||||||
---
|
---
|
||||||
|
|
||||||
# `AuthChangeEvent`
|
# `AuthChangeEvent`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: AuthChangedFunction
|
|||||||
sidebar_label: AuthChangedFunction
|
sidebar_label: AuthChangedFunction
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L130
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L136
|
||||||
---
|
---
|
||||||
|
|
||||||
# `AuthChangedFunction`
|
# `AuthChangedFunction`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: ChangeEmailParams
|
|||||||
sidebar_label: ChangeEmailParams
|
sidebar_label: ChangeEmailParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L99
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L105
|
||||||
---
|
---
|
||||||
|
|
||||||
# `ChangeEmailParams`
|
# `ChangeEmailParams`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: ChangePasswordParams
|
|||||||
sidebar_label: ChangePasswordParams
|
sidebar_label: ChangePasswordParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L90
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L96
|
||||||
---
|
---
|
||||||
|
|
||||||
# `ChangePasswordParams`
|
# `ChangePasswordParams`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: DeanonymizeParams
|
|||||||
sidebar_label: DeanonymizeParams
|
sidebar_label: DeanonymizeParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L104
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L110
|
||||||
---
|
---
|
||||||
|
|
||||||
# `DeanonymizeParams`
|
# `DeanonymizeParams`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: OnTokenChangedFunction
|
|||||||
sidebar_label: OnTokenChangedFunction
|
sidebar_label: OnTokenChangedFunction
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L132
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L138
|
||||||
---
|
---
|
||||||
|
|
||||||
# `OnTokenChangedFunction`
|
# `OnTokenChangedFunction`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: ResetPasswordParams
|
|||||||
sidebar_label: ResetPasswordParams
|
sidebar_label: ResetPasswordParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L85
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L91
|
||||||
---
|
---
|
||||||
|
|
||||||
# `ResetPasswordParams`
|
# `ResetPasswordParams`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: SendVerificationEmailParams
|
|||||||
sidebar_label: SendVerificationEmailParams
|
sidebar_label: SendVerificationEmailParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L94
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L100
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SendVerificationEmailParams`
|
# `SendVerificationEmailParams`
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
---
|
---
|
||||||
# ⚠️ AUTO-GENERATED CONTENT. DO NOT EDIT THIS FILE DIRECTLY! ⚠️
|
# ⚠️ AUTO-GENERATED CONTENT. DO NOT EDIT THIS FILE DIRECTLY! ⚠️
|
||||||
title: LoginData
|
title: SignInEmailPasswordOtpParams
|
||||||
sidebar_label: LoginData
|
sidebar_label: SignInEmailPasswordOtpParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L134
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L59
|
||||||
---
|
---
|
||||||
|
|
||||||
# `LoginData`
|
# `SignInEmailPasswordOtpParams`
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**<span className="parameter-name">mfa</span>** <span className="optional-status">optional</span> `boolean`
|
**<span className="parameter-name">otp</span>** <span className="optional-status">required</span> `string`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ title: SignInParams
|
|||||||
sidebar_label: SignInParams
|
sidebar_label: SignInParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L78
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L83
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SignInParams`
|
# `SignInParams`
|
||||||
@@ -12,6 +12,7 @@ custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-j
|
|||||||
```ts
|
```ts
|
||||||
type SignInParams =
|
type SignInParams =
|
||||||
| SignInEmailPasswordParams
|
| SignInEmailPasswordParams
|
||||||
|
| SignInEmailPasswordOtpParams
|
||||||
| SignInPasswordlessEmailParams
|
| SignInPasswordlessEmailParams
|
||||||
| SignInPasswordlessSmsOtpParams
|
| SignInPasswordlessSmsOtpParams
|
||||||
| SignInPasswordlessSmsParams
|
| SignInPasswordlessSmsParams
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: SignInPasswordlessEmailParams
|
|||||||
sidebar_label: SignInPasswordlessEmailParams
|
sidebar_label: SignInPasswordlessEmailParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L59
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L64
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SignInPasswordlessEmailParams`
|
# `SignInPasswordlessEmailParams`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: SignInPasswordlessSmsOtpParams
|
|||||||
sidebar_label: SignInPasswordlessSmsOtpParams
|
sidebar_label: SignInPasswordlessSmsOtpParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L69
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L74
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SignInPasswordlessSmsOtpParams`
|
# `SignInPasswordlessSmsOtpParams`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: SignInPasswordlessSmsParams
|
|||||||
sidebar_label: SignInPasswordlessSmsParams
|
sidebar_label: SignInPasswordlessSmsParams
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L64
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L69
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SignInPasswordlessSmsParams`
|
# `SignInPasswordlessSmsParams`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: SignInReponse
|
|||||||
sidebar_label: SignInReponse
|
sidebar_label: SignInReponse
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L117
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L123
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SignInReponse`
|
# `SignInReponse`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: SignInWithProviderOptions
|
|||||||
sidebar_label: SignInWithProviderOptions
|
sidebar_label: SignInWithProviderOptions
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L73
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/hasura-auth-js/src/utils/types.ts#L78
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SignInWithProviderOptions`
|
# `SignInWithProviderOptions`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: useSignInEmailPassword()
|
|||||||
sidebar_label: useSignInEmailPassword()
|
sidebar_label: useSignInEmailPassword()
|
||||||
slug: /reference/nextjs/use-sign-in-email-password
|
slug: /reference/nextjs/use-sign-in-email-password
|
||||||
description: Use the hook `useSignInEmailPassword` to sign in a user using email and password.
|
description: Use the hook `useSignInEmailPassword` to sign in a user using email and password.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSignInEmailPassword.ts#L49
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSignInEmailPassword.ts#L54
|
||||||
---
|
---
|
||||||
|
|
||||||
# `useSignInEmailPassword()`
|
# `useSignInEmailPassword()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: SignInEmailPasswordHookResult
|
|||||||
sidebar_label: SignInEmailPasswordHookResult
|
sidebar_label: SignInEmailPasswordHookResult
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSignInEmailPassword.ts#L19
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSignInEmailPassword.ts#L24
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SignInEmailPasswordHookResult`
|
# `SignInEmailPasswordHookResult`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: useSignInEmailPassword()
|
|||||||
sidebar_label: useSignInEmailPassword()
|
sidebar_label: useSignInEmailPassword()
|
||||||
slug: /reference/react/use-sign-in-email-password
|
slug: /reference/react/use-sign-in-email-password
|
||||||
description: Use the hook `useSignInEmailPassword` to sign in a user using email and password.
|
description: Use the hook `useSignInEmailPassword` to sign in a user using email and password.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSignInEmailPassword.ts#L49
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSignInEmailPassword.ts#L54
|
||||||
---
|
---
|
||||||
|
|
||||||
# `useSignInEmailPassword()`
|
# `useSignInEmailPassword()`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: SignInEmailPasswordHookResult
|
|||||||
sidebar_label: SignInEmailPasswordHookResult
|
sidebar_label: SignInEmailPasswordHookResult
|
||||||
description: No description provided.
|
description: No description provided.
|
||||||
displayed_sidebar: referenceSidebar
|
displayed_sidebar: referenceSidebar
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSignInEmailPassword.ts#L19
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/react/src/useSignInEmailPassword.ts#L24
|
||||||
---
|
---
|
||||||
|
|
||||||
# `SignInEmailPasswordHookResult`
|
# `SignInEmailPasswordHookResult`
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/vue/src/useRe
|
|||||||
Use the composable `useResetPassword` to reset the password for a user. This will send a reset password link in an email to the user. When the user clicks on the reset-password link the user is automatically signed in and can change their password using the composable `useChangePassword`.
|
Use the composable `useResetPassword` to reset the password for a user. This will send a reset password link in an email to the user. When the user clicks on the reset-password link the user is automatically signed in and can change their password using the composable `useChangePassword`.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
const { resetPassword, isLoading, isSent, isError, error } = useResetPassword()
|
const { resetPassword, isLoading, isSent, isError, error } = useResetPassword({
|
||||||
|
redirectTo: 'http://localhost:3000/settings/change-password'
|
||||||
|
})
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
console.log(isLoading.value, isSent.value, isError.value, error.value)
|
console.log(isLoading.value, isSent.value, isError.value, error.value)
|
||||||
@@ -21,9 +23,7 @@ watchEffect(() => {
|
|||||||
const handleFormSubmit = async (e) => {
|
const handleFormSubmit = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
await resetPassword('joe@example.com', {
|
await resetPassword('joe@example.com')
|
||||||
redirectTo: 'http://localhost:3000/settings/change-password'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: useSignInEmailPassword()
|
|||||||
sidebar_label: useSignInEmailPassword()
|
sidebar_label: useSignInEmailPassword()
|
||||||
slug: /reference/vue/use-sign-in-email-password
|
slug: /reference/vue/use-sign-in-email-password
|
||||||
description: Use the composable `useSignInEmailPassword` to sign in a user using email and password.
|
description: Use the composable `useSignInEmailPassword` to sign in a user using email and password.
|
||||||
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/vue/src/useSignInEmailPassword.ts#L44
|
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/vue/src/useSignInEmailPassword.ts#L46
|
||||||
---
|
---
|
||||||
|
|
||||||
# `useSignInEmailPassword()`
|
# `useSignInEmailPassword()`
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
"@mantine/hooks": "^4.2.2",
|
"@mantine/hooks": "^4.2.2",
|
||||||
"@mantine/next": "^4.2.2",
|
"@mantine/next": "^4.2.2",
|
||||||
"@mantine/notifications": "^4.2.2",
|
"@mantine/notifications": "^4.2.2",
|
||||||
"@nhost/nextjs": "workspace:*",
|
"@nhost/nextjs": "*",
|
||||||
"@nhost/react": "workspace:*",
|
"@nhost/react": "*",
|
||||||
"@nhost/react-apollo": "workspace:*",
|
"@nhost/react-apollo": "*",
|
||||||
"graphql": "^16.3.0",
|
"graphql": "^16.3.0",
|
||||||
"next": "12.1.6",
|
"next": "12.1.6",
|
||||||
"react": "18.1.0",
|
"react": "18.1.0",
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
"@apollo/client": "^3.5.10",
|
"@apollo/client": "^3.5.10",
|
||||||
"@headlessui/react": "^1.5.0",
|
"@headlessui/react": "^1.5.0",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@nhost/react": "workspace:*",
|
"@nhost/react": "*",
|
||||||
"@nhost/react-apollo": "workspace:*",
|
"@nhost/react-apollo": "*",
|
||||||
"@tailwindcss/forms": "^0.5.0",
|
"@tailwindcss/forms": "^0.5.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export default defineConfig({
|
|||||||
chromeWebSecurity: false,
|
chromeWebSecurity: false,
|
||||||
// * for some reason, the mailhog API is not systematically available
|
// * for some reason, the mailhog API is not systematically available
|
||||||
// * when using `localhost` instead of `127.0.0.1`
|
// * when using `localhost` instead of `127.0.0.1`
|
||||||
mailHogUrl: 'http://127.0.0.1:8025'
|
mailHogUrl: 'http://127.0.0.1:8025',
|
||||||
|
env: {
|
||||||
|
backendUrl: 'http://localhost:1337'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} as Cypress.ConfigOptions)
|
} as Cypress.ConfigOptions)
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { faker } from '@faker-js/faker'
|
|
||||||
|
|
||||||
context('Failed attempt to sign up with an email already present in the database', () => {
|
|
||||||
it('shoud raise an error when trying to sign up with an existing email', () => {
|
|
||||||
const email = faker.internet.email()
|
|
||||||
const password = faker.internet.password(10)
|
|
||||||
cy.signUpEmailPassword(email, password)
|
|
||||||
cy.contains('Verification email sent').should('be.visible')
|
|
||||||
cy.signUpEmailPassword(email, password)
|
|
||||||
cy.contains('Email already in use').should('be.visible')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { faker } from '@faker-js/faker'
|
|
||||||
|
|
||||||
context('Successful email+password sign-up', () => {
|
|
||||||
it('should redirect to /sign-in when not authenticated', () => {
|
|
||||||
cy.visit('/')
|
|
||||||
cy.location('pathname').should('equal', '/sign-in')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should sign-up with email and password', () => {
|
|
||||||
const email = faker.internet.email()
|
|
||||||
cy.signUpEmailPassword(email, faker.internet.password())
|
|
||||||
cy.contains('Verification email sent').should('be.visible')
|
|
||||||
cy.confirmEmail(email)
|
|
||||||
cy.contains('You are authenticated')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
context('Authentication guards', () => {
|
||||||
|
it('should redirect to /sign-in when not authenticated', () => {
|
||||||
|
cy.visit('/')
|
||||||
|
cy.location('pathname').should('equal', '/sign-in')
|
||||||
|
cy.visit('/apollo')
|
||||||
|
cy.location('pathname').should('equal', '/sign-in')
|
||||||
|
})
|
||||||
|
})
|
||||||
51
examples/react-apollo/cypress/e2e/2-sign-up/anonymous.cy.ts
Normal file
51
examples/react-apollo/cypress/e2e/2-sign-up/anonymous.cy.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { faker } from '@faker-js/faker'
|
||||||
|
|
||||||
|
context('Anonymous users', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.signInAnonymous()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should sign-up anonymously', () => {
|
||||||
|
cy.contains('You signed in anonymously')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should deanonymise with email+password', () => {
|
||||||
|
cy.fetchUserData()
|
||||||
|
.its('id')
|
||||||
|
.then((id) => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
const password = faker.internet.password()
|
||||||
|
cy.signUpEmailPassword(email, password)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
cy.contains('You signed in anonymously').should('not.exist')
|
||||||
|
|
||||||
|
cy.fetchUserData().then((user) => {
|
||||||
|
cy.wrap(user).its('id').should('equal', id)
|
||||||
|
cy.wrap(user).its('email').should('equal', email)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should deanonymise with passwordless email', () => {
|
||||||
|
cy.fetchUserData()
|
||||||
|
.its('id')
|
||||||
|
.then((id) => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
cy.signUpEmailPasswordless(email)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
cy.goToHomePage()
|
||||||
|
cy.contains('You signed in anonymously').should('not.exist')
|
||||||
|
|
||||||
|
cy.fetchUserData().then((user) => {
|
||||||
|
cy.wrap(user).its('id').should('equal', id)
|
||||||
|
cy.wrap(user).its('email').should('equal', email)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO implement deanonymisation with Oauth?
|
||||||
|
// TODO forbid email/password change, MFA activation, and password reset when the following PR is released
|
||||||
|
// * https://github.com/nhost/hasura-auth/pull/190
|
||||||
|
})
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { faker } from '@faker-js/faker'
|
||||||
|
|
||||||
|
context('Sign up with email+password', () => {
|
||||||
|
it('should sign-up with email and password', () => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
const password = faker.internet.password()
|
||||||
|
cy.signUpEmailPassword(email, password)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
cy.contains('You are authenticated')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shoud raise an error when trying to sign up with an existing email', () => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
const password = faker.internet.password(10)
|
||||||
|
cy.signUpEmailPassword(email, password)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
cy.signUpEmailPassword(email, password)
|
||||||
|
cy.contains('Email already in use').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO implement in the UI
|
||||||
|
it.skip('should fail when network is not available', () => {
|
||||||
|
cy.disconnectBackend()
|
||||||
|
cy.signUpEmailPassword(faker.internet.email(), faker.internet.password())
|
||||||
|
cy.contains('Error').should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { faker } from '@faker-js/faker'
|
||||||
|
|
||||||
|
context('Sign up with passwordless email', () => {
|
||||||
|
it('should sign-up with passwordless email', () => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
cy.signUpEmailPasswordless(email)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
cy.contains('Profile page')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail when network is not available', () => {
|
||||||
|
cy.disconnectBackend()
|
||||||
|
cy.signUpEmailPasswordless(faker.internet.email())
|
||||||
|
cy.contains('Error').should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import totp from 'totp-generator'
|
||||||
|
|
||||||
|
import faker from '@faker-js/faker'
|
||||||
|
import { Decoder } from '@nuintun/qrcode'
|
||||||
|
|
||||||
|
context('Sign in with email+password', () => {
|
||||||
|
it('should sign-in with email and password', () => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
const password = faker.internet.password()
|
||||||
|
cy.signUpEmailPassword(email, password)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
cy.signOut()
|
||||||
|
cy.contains('Log in to the Application').should('be.visible')
|
||||||
|
cy.signInEmailPassword(email, password)
|
||||||
|
|
||||||
|
cy.contains('You are authenticated')
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO implement in the UI
|
||||||
|
it.skip('should fail when network is not available', () => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
const password = faker.internet.password()
|
||||||
|
cy.disconnectBackend()
|
||||||
|
cy.signInEmailPassword(email, password)
|
||||||
|
cy.contains('Error').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should activate and sign-in with MFA', () => {
|
||||||
|
// * Sign-up with email+password
|
||||||
|
const email = faker.internet.email()
|
||||||
|
const password = faker.internet.email()
|
||||||
|
cy.signUpEmailPassword(email, password)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
|
||||||
|
cy.getNavBar()
|
||||||
|
.findByRole('button', { name: /Profile/i })
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.findByText(/Activate 2-step verification/i)
|
||||||
|
.parent()
|
||||||
|
.findByRole('button')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
cy.findByText(/Activate 2-step verification/i)
|
||||||
|
.get('img')
|
||||||
|
.then(async (img) => {
|
||||||
|
// * Activate MFA
|
||||||
|
const result = await new Decoder().scan(img.prop('src'))
|
||||||
|
const [, params] = result.data.split('?')
|
||||||
|
const { secret, algorithm, digits, period } = Object.fromEntries(
|
||||||
|
new URLSearchParams(params)
|
||||||
|
)
|
||||||
|
const code = totp(secret, {
|
||||||
|
algorithm: algorithm.replace('SHA1', 'SHA-1'),
|
||||||
|
digits: parseInt(digits),
|
||||||
|
period: parseInt(period)
|
||||||
|
})
|
||||||
|
cy.findByPlaceholderText('Enter activation code').type(code)
|
||||||
|
cy.findByRole('button', { name: /Activate/i }).click()
|
||||||
|
cy.contains('MFA has been activated!!!')
|
||||||
|
cy.signOut()
|
||||||
|
|
||||||
|
// * Sign-in with MFA
|
||||||
|
cy.visit('/sign-in')
|
||||||
|
cy.findByRole('button', { name: /Continue with email \+ password/i }).click()
|
||||||
|
cy.findByPlaceholderText('Email Address').type(email)
|
||||||
|
cy.findByPlaceholderText('Password').type(password)
|
||||||
|
cy.findByRole('button', { name: /Sign in/i }).click()
|
||||||
|
cy.contains('Send 2-step verification code')
|
||||||
|
const newCode = totp(secret, { timestamp: Date.now() })
|
||||||
|
cy.findByPlaceholderText('One-time password').type(newCode)
|
||||||
|
cy.findByRole('button', { name: /Send 2-step verification code/i }).click()
|
||||||
|
cy.contains('You are authenticated')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
22
examples/react-apollo/cypress/e2e/3-sign-in/token.cy.ts
Normal file
22
examples/react-apollo/cypress/e2e/3-sign-in/token.cy.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
context('Sign in with a refresh token', () => {
|
||||||
|
it('should sign-in with a refresh token', () => {
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
cy.contains('Profile page')
|
||||||
|
cy.clearLocalStorage()
|
||||||
|
cy.reload()
|
||||||
|
cy.contains('Log in to the Application')
|
||||||
|
cy.visitPathWithRefreshToken('/profile')
|
||||||
|
cy.contains('Profile page')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail authentication when network is not available', () => {
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
cy.contains('Profile page')
|
||||||
|
cy.disconnectBackend()
|
||||||
|
cy.clearLocalStorage()
|
||||||
|
cy.reload()
|
||||||
|
cy.contains('Log in to the Application')
|
||||||
|
cy.visitPathWithRefreshToken('/profile')
|
||||||
|
cy.location('pathname').should('equal', '/sign-in')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import faker from '@faker-js/faker'
|
||||||
|
|
||||||
|
context('Apollo', () => {
|
||||||
|
const addItemTest = (sentence: string) => {
|
||||||
|
cy.getNavBar()
|
||||||
|
.findByRole('button', { name: /Apollo/i })
|
||||||
|
.click()
|
||||||
|
cy.contains('Todo list')
|
||||||
|
cy.focused().type(sentence)
|
||||||
|
cy.findByRole('button', { name: /Add/i }).click()
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should add an item to the todo list when normally authenticated', () => {
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
const sentence = faker.lorem.sentence()
|
||||||
|
addItemTest(sentence)
|
||||||
|
cy.get('li').contains(sentence)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add an item to the todo list when anonymous', () => {
|
||||||
|
cy.signInAnonymous()
|
||||||
|
const sentence = faker.lorem.sentence()
|
||||||
|
addItemTest(sentence)
|
||||||
|
cy.get('li').contains(sentence)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add an item to the todo list after a token refresh', () => {
|
||||||
|
// * This test has a limitation: Hasura's clock is not changing, so the previous JWT will still be valid.
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
const now = Date.now()
|
||||||
|
cy.clock(now)
|
||||||
|
cy.tick(4 * 7 * 24 * 60 * 60 * 1000)
|
||||||
|
const sentence = faker.lorem.sentence()
|
||||||
|
addItemTest(sentence)
|
||||||
|
cy.get('li').contains(sentence)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not add an item when backend is disconnected', () => {
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
cy.disconnectBackend()
|
||||||
|
addItemTest(faker.lorem.sentence())
|
||||||
|
cy.contains('Network error')
|
||||||
|
cy.get('ul').should('be.empty')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import faker from '@faker-js/faker'
|
||||||
|
|
||||||
|
context('Change email', () => {
|
||||||
|
it('should change email', () => {
|
||||||
|
const newEmail = faker.internet.email()
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
cy.findByPlaceholderText('New email').type(newEmail)
|
||||||
|
cy.findByText(/Change Email/i)
|
||||||
|
.parent()
|
||||||
|
.findByRole('button')
|
||||||
|
.click()
|
||||||
|
cy.contains('Please check your inbox and follow the link to confirm the email change').should(
|
||||||
|
'be.visible'
|
||||||
|
)
|
||||||
|
cy.signOut()
|
||||||
|
cy.confirmEmail(newEmail)
|
||||||
|
cy.contains('Profile page')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not accept an invalid email', () => {
|
||||||
|
const newEmail = faker.random.alphaNumeric()
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
cy.findByPlaceholderText('New email').type(newEmail)
|
||||||
|
cy.findByText(/Change Email/i)
|
||||||
|
.parent()
|
||||||
|
.findByRole('button')
|
||||||
|
.click()
|
||||||
|
cy.contains('Email is incorrectly formatted').should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import faker from '@faker-js/faker'
|
||||||
|
|
||||||
|
context('Change password', () => {
|
||||||
|
it('should change password', () => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
const newPassword = faker.internet.password()
|
||||||
|
cy.signUpAndConfirmEmail(email)
|
||||||
|
cy.findByPlaceholderText('New password').type(newPassword)
|
||||||
|
cy.findByText(/Change Password/i)
|
||||||
|
.parent()
|
||||||
|
.findByRole('button')
|
||||||
|
.click()
|
||||||
|
cy.contains('Password changed successfully').should('be.visible')
|
||||||
|
cy.signOut()
|
||||||
|
cy.signInEmailPassword(email, newPassword)
|
||||||
|
cy.contains('You are authenticated')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not accept an invalid password', () => {
|
||||||
|
const newPassword = faker.random.alphaNumeric(2)
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
cy.findByPlaceholderText('New password').type(newPassword)
|
||||||
|
cy.findByText(/Change Password/i)
|
||||||
|
.parent()
|
||||||
|
.findByRole('button')
|
||||||
|
.click()
|
||||||
|
cy.contains('Password is incorrectly formatted').should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
context('Sign out', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.signUpAndConfirmEmail()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should sign out', () => {
|
||||||
|
cy.visitPathWithRefreshToken()
|
||||||
|
cy.goToProfilePage()
|
||||||
|
cy.contains('Profile page')
|
||||||
|
cy.signOut()
|
||||||
|
cy.contains('Log in to the Application')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import faker from '@faker-js/faker'
|
||||||
|
|
||||||
|
context('Token refresh', () => {
|
||||||
|
it('should refresh token one minute before it expires', () => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
cy.signUpEmailPasswordless(email)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
const now = Date.now()
|
||||||
|
cy.clock(now)
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
|
||||||
|
cy.intercept(Cypress.env('backendUrl') + '/v1/auth/token').as('tokenRequest')
|
||||||
|
cy.tick(14 * 60 * 1000)
|
||||||
|
cy.wait('@tokenRequest').its('response.statusCode').should('eq', 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should refresh session from localStorage after 4 weeks of inactivity', () => {
|
||||||
|
const email = faker.internet.email()
|
||||||
|
cy.signUpEmailPasswordless(email)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
const now = Date.now()
|
||||||
|
cy.clock(now)
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
cy.contains('Profile page')
|
||||||
|
|
||||||
|
cy.tick(4 * 7 * 24 * 60 * 60 * 1000)
|
||||||
|
cy.reload()
|
||||||
|
cy.contains('Profile page')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,25 +1,85 @@
|
|||||||
import { faker } from '@faker-js/faker'
|
import { faker } from '@faker-js/faker'
|
||||||
|
import { User } from '@nhost/core'
|
||||||
|
|
||||||
import '@testing-library/cypress/add-commands'
|
import '@testing-library/cypress/add-commands'
|
||||||
import 'cypress-mailhog'
|
import 'cypress-mailhog'
|
||||||
|
declare module 'mocha' {
|
||||||
|
export interface Context {
|
||||||
|
refreshToken?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
declare global {
|
declare global {
|
||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
interface Chainable {
|
interface Chainable {
|
||||||
signUpEmailPassword(email: string, password: string): Chainable<Element>
|
signUpEmailPassword(email: string, password: string): Chainable<Element>
|
||||||
|
signUpEmailPasswordless(email: string): Chainable<Element>
|
||||||
|
signInEmailPassword(email: string, password: string): Chainable<Element>
|
||||||
|
signInAnonymous(): Chainable<Element>
|
||||||
|
/** Sign in from the refresh token stored in the global state */
|
||||||
|
visitPathWithRefreshToken(path?: string): Chainable<Element>
|
||||||
|
/** Click on the 'Sign Out' item of the left side menu to sign out the current user */
|
||||||
|
signOut(): Chainable<Element>
|
||||||
|
/** Run a sign-up + authentication sequence with passwordless to use an authenticated user in other tests */
|
||||||
|
signUpAndConfirmEmail(email?: string): Chainable<Element>
|
||||||
|
/** Gets a confirmation email and click on the link */
|
||||||
confirmEmail(email: string): Chainable<Element>
|
confirmEmail(email: string): Chainable<Element>
|
||||||
|
/** Save the refresh token in the global state so it can be reused with `this.refreshToken` */
|
||||||
|
saveRefreshToken(): Chainable<Element>
|
||||||
|
/** Make the Nhost backend unavailable */
|
||||||
|
disconnectBackend(): Chainable<Element>
|
||||||
|
/** Get the left side navigation bar */
|
||||||
|
getNavBar(): Chainable<Element>
|
||||||
|
/** Go to the profile page */
|
||||||
|
goToProfilePage(): Chainable<Element>
|
||||||
|
/** Go to the home page */
|
||||||
|
goToHomePage(): Chainable<Element>
|
||||||
|
/** Go getch the user ID in the profile page*/
|
||||||
|
fetchUserData(): Chainable<User>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cypress.Commands.add('signUpEmailPassword', (email, password) => {
|
Cypress.Commands.add('signUpEmailPassword', (email, password) => {
|
||||||
cy.visit('/sign-up')
|
cy.visit('/sign-up')
|
||||||
cy.contains('Continue with email + password').click()
|
cy.findByRole('button', { name: /Continue with email \+ password/i }).click()
|
||||||
cy.findByPlaceholderText('First name').type(faker.name.firstName())
|
cy.findByPlaceholderText('First name').type(faker.name.firstName())
|
||||||
cy.findByPlaceholderText('Last name').type(faker.name.lastName())
|
cy.findByPlaceholderText('Last name').type(faker.name.lastName())
|
||||||
cy.findByPlaceholderText('Email Address').type(email)
|
cy.findByPlaceholderText('Email Address').type(email)
|
||||||
cy.findByPlaceholderText('Password').type(password)
|
cy.findByPlaceholderText('Password').type(password)
|
||||||
cy.findByPlaceholderText('Confirm Password').type(password)
|
cy.findByPlaceholderText('Confirm Password').type(password)
|
||||||
cy.contains('Continue with email + password').click()
|
cy.findByRole('button', { name: /Continue with email \+ password/i }).click()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('signUpEmailPasswordless', (email) => {
|
||||||
|
cy.visit('/sign-up')
|
||||||
|
cy.findByRole('button', { name: /Continue with passwordless email/i }).click()
|
||||||
|
cy.findByPlaceholderText('Email Address').type(email)
|
||||||
|
cy.findByRole('button', { name: /Continue with email/i }).click()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('signInEmailPassword', (email, password) => {
|
||||||
|
cy.visit('/sign-in')
|
||||||
|
cy.findByRole('button', { name: /Continue with email \+ password/i }).click()
|
||||||
|
cy.findByPlaceholderText('Email Address').type(email)
|
||||||
|
cy.findByPlaceholderText('Password').type(password)
|
||||||
|
cy.findByRole('button', { name: /Sign in/i }).click()
|
||||||
|
cy.saveRefreshToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('signInAnonymous', () => {
|
||||||
|
cy.visit('/sign-in')
|
||||||
|
cy.findByRole('link', { name: /sign in anonymously/i }).click()
|
||||||
|
cy.saveRefreshToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('visitPathWithRefreshToken', function (path = '/') {
|
||||||
|
cy.visit(path + '#refreshToken=' + this.refreshToken)
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('signOut', () => {
|
||||||
|
cy.getNavBar()
|
||||||
|
.findByRole('button', { name: /Sign Out/i })
|
||||||
|
.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add('confirmEmail', (email) => {
|
Cypress.Commands.add('confirmEmail', (email) => {
|
||||||
@@ -27,5 +87,53 @@ Cypress.Commands.add('confirmEmail', (email) => {
|
|||||||
.should('have.length', 1)
|
.should('have.length', 1)
|
||||||
.then(([message]) => {
|
.then(([message]) => {
|
||||||
cy.visit(message.Content.Headers['X-Link'][0])
|
cy.visit(message.Content.Headers['X-Link'][0])
|
||||||
|
cy.saveRefreshToken()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('signUpAndConfirmEmail', (givenEmail) => {
|
||||||
|
const email = givenEmail || faker.internet.email()
|
||||||
|
cy.signUpEmailPasswordless(email)
|
||||||
|
cy.contains('Verification email sent').should('be.visible')
|
||||||
|
cy.confirmEmail(email)
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('saveRefreshToken', () => {
|
||||||
|
cy.getNavBar()
|
||||||
|
.findByRole('button', { name: /Sign Out/i })
|
||||||
|
.then(() => localStorage.getItem('nhostRefreshToken'))
|
||||||
|
.as('refreshToken')
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('disconnectBackend', () => {
|
||||||
|
cy.intercept(Cypress.env('backendUrl') + '/**', {
|
||||||
|
forceNetworkError: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('getNavBar', () => {
|
||||||
|
cy.findByRole(`navigation`, { name: /main navigation/i })
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('goToProfilePage', () => {
|
||||||
|
cy.getNavBar()
|
||||||
|
.findByRole('button', { name: /Profile/i })
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('goToHomePage', () => {
|
||||||
|
cy.getNavBar().findByRole('button', { name: /Home/i }).click()
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add('fetchUserData', () => {
|
||||||
|
cy.goToProfilePage()
|
||||||
|
cy.findByText('User information')
|
||||||
|
.parent()
|
||||||
|
.within(() => {
|
||||||
|
cy.get('pre')
|
||||||
|
.invoke('text')
|
||||||
|
.then((text) => JSON.parse(text))
|
||||||
|
.as('user')
|
||||||
|
})
|
||||||
|
return cy.get<User>('@user')
|
||||||
|
})
|
||||||
|
|||||||
15
examples/react-apollo/graphql.config.yaml
Normal file
15
examples/react-apollo/graphql.config.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
schema:
|
||||||
|
- http://localhost:1337/v1/graphql:
|
||||||
|
headers:
|
||||||
|
x-hasura-admin-secret: nhost-admin-secret
|
||||||
|
x-hasura-role: user
|
||||||
|
documents: 'src/**/!(*.d).{ts,tsx}'
|
||||||
|
generates:
|
||||||
|
./src/generated.ts:
|
||||||
|
config:
|
||||||
|
namingConvention:
|
||||||
|
typeNames: change-case-all#pascalCase
|
||||||
|
transformUnderscore: true
|
||||||
|
plugins:
|
||||||
|
- typescript
|
||||||
|
- typescript-operations
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
table:
|
||||||
|
name: todos
|
||||||
|
schema: public
|
||||||
|
configuration:
|
||||||
|
column_config:
|
||||||
|
created_at:
|
||||||
|
custom_name: createdAt
|
||||||
|
updated_at:
|
||||||
|
custom_name: updatedAt
|
||||||
|
user_id:
|
||||||
|
custom_name: userId
|
||||||
|
custom_column_names:
|
||||||
|
created_at: createdAt
|
||||||
|
updated_at: updatedAt
|
||||||
|
user_id: userId
|
||||||
|
custom_root_fields:
|
||||||
|
delete: deleteTodos
|
||||||
|
delete_by_pk: deleteTodo
|
||||||
|
insert: insertTodos
|
||||||
|
insert_one: insertTodo
|
||||||
|
select_aggregate: todosAggregate
|
||||||
|
select_by_pk: todo
|
||||||
|
update: updateTodos
|
||||||
|
update_by_pk: updateTodo
|
||||||
|
object_relationships:
|
||||||
|
- name: user
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on: user_id
|
||||||
|
insert_permissions:
|
||||||
|
- permission:
|
||||||
|
backend_only: false
|
||||||
|
check: {}
|
||||||
|
columns:
|
||||||
|
- contents
|
||||||
|
- id
|
||||||
|
set:
|
||||||
|
user_id: x-hasura-user-id
|
||||||
|
role: anonymous
|
||||||
|
- permission:
|
||||||
|
backend_only: false
|
||||||
|
check: {}
|
||||||
|
columns:
|
||||||
|
- contents
|
||||||
|
- id
|
||||||
|
set:
|
||||||
|
user_id: x-hasura-user-id
|
||||||
|
role: user
|
||||||
|
select_permissions:
|
||||||
|
- permission:
|
||||||
|
allow_aggregations: true
|
||||||
|
columns:
|
||||||
|
- contents
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- id
|
||||||
|
- user_id
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
role: anonymous
|
||||||
|
- permission:
|
||||||
|
allow_aggregations: true
|
||||||
|
columns:
|
||||||
|
- contents
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- id
|
||||||
|
- user_id
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
role: user
|
||||||
|
update_permissions:
|
||||||
|
- permission:
|
||||||
|
check:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
columns:
|
||||||
|
- contents
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
role: anonymous
|
||||||
|
- permission:
|
||||||
|
check:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
columns:
|
||||||
|
- contents
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
role: user
|
||||||
|
delete_permissions:
|
||||||
|
- permission:
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
role: anonymous
|
||||||
|
- permission:
|
||||||
|
filter:
|
||||||
|
user_id:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
role: user
|
||||||
@@ -5,6 +5,6 @@
|
|||||||
- "!include auth_user_providers.yaml"
|
- "!include auth_user_providers.yaml"
|
||||||
- "!include auth_user_roles.yaml"
|
- "!include auth_user_roles.yaml"
|
||||||
- "!include auth_users.yaml"
|
- "!include auth_users.yaml"
|
||||||
- "!include public_books.yaml"
|
- "!include public_todos.yaml"
|
||||||
- "!include storage_buckets.yaml"
|
- "!include storage_buckets.yaml"
|
||||||
- "!include storage_files.yaml"
|
- "!include storage_files.yaml"
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
DROP INDEX IF EXISTS "public"."user_id";
|
||||||
|
DROP TABLE "public"."todos_user_id";
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE "public"."todos" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "user_id" uuid NOT NULL, "contents" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON UPDATE cascade ON DELETE cascade);
|
||||||
|
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
_new record;
|
||||||
|
BEGIN
|
||||||
|
_new := NEW;
|
||||||
|
_new."updated_at" = NOW();
|
||||||
|
RETURN _new;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
CREATE TRIGGER "set_public_todos_updated_at"
|
||||||
|
BEFORE UPDATE ON "public"."todos"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
|
||||||
|
COMMENT ON TRIGGER "set_public_todos_updated_at" ON "public"."todos"
|
||||||
|
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
|
CREATE INDEX "todos_user_id" on
|
||||||
|
"public"."todos" using btree ("user_id");
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE TABLE "public"."books" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "title" text NOT NULL, PRIMARY KEY ("id") );
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP table "public"."books";
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
"generate": "graphql-codegen --config graphql.config.yaml",
|
||||||
"cypress": "cypress open",
|
"cypress": "cypress open",
|
||||||
"test": "cypress run",
|
"test": "cypress run",
|
||||||
"e2e": "start-test e2e:backend :1337/v1/auth/healthz e2e:frontend 3000 test",
|
"e2e": "start-test e2e:backend :1337/v1/auth/healthz e2e:frontend 3000 test",
|
||||||
@@ -33,11 +34,6 @@
|
|||||||
"verify": "run-p prettier lint",
|
"verify": "run-p prettier lint",
|
||||||
"verify:fix": "run-p prettier:fix lint:fix"
|
"verify:fix": "run-p prettier:fix lint:fix"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
@@ -52,14 +48,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faker-js/faker": "^6.3.1",
|
"@faker-js/faker": "^6.3.1",
|
||||||
|
"@graphql-codegen/cli": "^2.6.2",
|
||||||
|
"@nuintun/qrcode": "^3.3.0",
|
||||||
"@testing-library/cypress": "^8.0.3",
|
"@testing-library/cypress": "^8.0.3",
|
||||||
"@types/react": "^18.0.8",
|
"@types/react": "^18.0.8",
|
||||||
"@types/react-dom": "^18.0.3",
|
"@types/react-dom": "^18.0.3",
|
||||||
|
"@types/totp-generator": "^0.0.4",
|
||||||
"@vitejs/plugin-react": "^1.3.2",
|
"@vitejs/plugin-react": "^1.3.2",
|
||||||
"@xstate/inspect": "^0.6.2",
|
"@xstate/inspect": "^0.6.2",
|
||||||
"cypress": "^10.0.1",
|
"cypress": "^10.0.1",
|
||||||
"cypress-mailhog": "^1.4.0",
|
"cypress-mailhog": "^1.4.0",
|
||||||
"start-server-and-test": "^1.14.0",
|
"start-server-and-test": "^1.14.0",
|
||||||
|
"totp-generator": "^0.0.13",
|
||||||
"typescript": "^4.6.3",
|
"typescript": "^4.6.3",
|
||||||
"vite": "^2.9.7",
|
"vite": "^2.9.7",
|
||||||
"ws": "^8.7.0",
|
"ws": "^8.7.0",
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
import { Container, Title } from '@mantine/core'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { Anchor, Container, Title } from '@mantine/core'
|
||||||
|
import { useUserIsAnonymous } from '@nhost/react'
|
||||||
|
|
||||||
const HomePage: React.FC = () => {
|
const HomePage: React.FC = () => {
|
||||||
|
const isAnonymous = useUserIsAnonymous()
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Title>Home page</Title>
|
<Title>Home page</Title>
|
||||||
You are authenticated. You have now access to the authorised part of the application.
|
You are authenticated. You have now access to the authorised part of the application.
|
||||||
|
{isAnonymous && (
|
||||||
|
<p>
|
||||||
|
You signed in anonymously.{' '}
|
||||||
|
<Anchor role="link" component={Link} to="/sign-up">
|
||||||
|
Sign up
|
||||||
|
</Anchor>{' '}
|
||||||
|
to complete your registration
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,106 @@
|
|||||||
import { gql } from '@apollo/client'
|
import { AddItemMutation, TodoListQuery } from 'src/generated'
|
||||||
import { Container, Loader, Title } from '@mantine/core'
|
|
||||||
|
import { gql, useMutation } from '@apollo/client'
|
||||||
|
import { Button, Card, Container, Grid, Loader, TextInput, Title } from '@mantine/core'
|
||||||
|
import { useInputState } from '@mantine/hooks'
|
||||||
|
import { showNotification } from '@mantine/notifications'
|
||||||
import { useAuthQuery } from '@nhost/react-apollo'
|
import { useAuthQuery } from '@nhost/react-apollo'
|
||||||
|
|
||||||
const GET_BOOKS = gql`
|
const TODO_LIST = gql`
|
||||||
query BooksQuery {
|
query TodoList {
|
||||||
books {
|
todos {
|
||||||
id
|
id
|
||||||
title
|
contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ADD_ITEM = gql`
|
||||||
|
mutation AddItem($contents: String!) {
|
||||||
|
insertTodo(object: { contents: $contents }) {
|
||||||
|
id
|
||||||
|
contents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const ApolloPage: React.FC = () => {
|
export const ApolloPage: React.FC = () => {
|
||||||
const { loading, data } = useAuthQuery(GET_BOOKS, {
|
const { loading, data } = useAuthQuery<TodoListQuery>(TODO_LIST, {
|
||||||
pollInterval: 5000,
|
pollInterval: 5000,
|
||||||
fetchPolicy: 'cache-and-network'
|
fetchPolicy: 'cache-and-network'
|
||||||
})
|
})
|
||||||
|
const [contents, setContents] = useInputState('')
|
||||||
|
|
||||||
|
const [mutate] = useMutation<AddItemMutation>(ADD_ITEM, {
|
||||||
|
variables: { contents },
|
||||||
|
onCompleted: () => {
|
||||||
|
setContents('')
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.log(error)
|
||||||
|
showNotification({
|
||||||
|
color: 'red',
|
||||||
|
title: error.networkError ? 'Network error' : 'Error',
|
||||||
|
message: error.message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
update: (cache, { data }) => {
|
||||||
|
cache.modify({
|
||||||
|
fields: {
|
||||||
|
todos(existingTodos = []) {
|
||||||
|
const newTodoRef = cache.writeFragment({
|
||||||
|
data: data?.insertTodo,
|
||||||
|
fragment: gql`
|
||||||
|
fragment NewTodo on todos {
|
||||||
|
id
|
||||||
|
contents
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
return [...existingTodos, newTodoRef]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const add = () => {
|
||||||
|
if (contents) {
|
||||||
|
mutate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Title>Apollo GraphQL</Title>
|
|
||||||
{loading && <Loader />}
|
{loading && <Loader />}
|
||||||
{data?.books && (
|
<Card shadow="sm" p="lg" m="sm">
|
||||||
|
<Title>Todo list</Title>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={9}>
|
||||||
|
<TextInput
|
||||||
|
value={contents}
|
||||||
|
onChange={setContents}
|
||||||
|
autoFocus
|
||||||
|
onKeyDown={(e) => e.code === 'Enter' && add()}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={3}>
|
||||||
|
<Button
|
||||||
|
onClick={(e: React.MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
add()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
<ul>
|
<ul>
|
||||||
{data.books.map((book) => (
|
{data?.todos.map((item) => (
|
||||||
<li key={book.id}>{book.title}</li>
|
<li key={item.id}>{item.contents}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const AuthLink: React.FC<{
|
|||||||
variant?: ButtonVariant
|
variant?: ButtonVariant
|
||||||
}> = ({ icon, color, link, variant, children }) => {
|
}> = ({ icon, color, link, variant, children }) => {
|
||||||
return (
|
return (
|
||||||
// <Link to={link}>
|
|
||||||
<Button
|
<Button
|
||||||
|
role="button"
|
||||||
component={Link}
|
component={Link}
|
||||||
fullWidth
|
fullWidth
|
||||||
radius="sm"
|
radius="sm"
|
||||||
@@ -33,7 +33,6 @@ const AuthLink: React.FC<{
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
// </Link>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FaHouseUser, FaQuestion, FaSignOutAlt } from 'react-icons/fa'
|
import { FaHouseUser, FaQuestion, FaSignOutAlt } 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 { Group, MantineColor, Navbar, Text, ThemeIcon, UnstyledButton } from '@mantine/core'
|
import { Group, MantineColor, Navbar, Text, ThemeIcon, UnstyledButton } from '@mantine/core'
|
||||||
@@ -62,7 +62,7 @@ export default function NavBar() {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
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 }}>
|
<Navbar width={{ sm: 300, lg: 400, base: 100 }} aria-label="main navigation">
|
||||||
<Navbar.Section grow mt="md">
|
<Navbar.Section grow mt="md">
|
||||||
{links}
|
{links}
|
||||||
{authenticated && (
|
{authenticated && (
|
||||||
|
|||||||
356
examples/react-apollo/src/generated.ts
Normal file
356
examples/react-apollo/src/generated.ts
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
export type Maybe<T> = T | null;
|
||||||
|
export type InputMaybe<T> = Maybe<T>;
|
||||||
|
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||||
|
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||||
|
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||||
|
/** All built-in and custom scalars, mapped to their actual values */
|
||||||
|
export type Scalars = {
|
||||||
|
ID: string;
|
||||||
|
String: string;
|
||||||
|
Boolean: boolean;
|
||||||
|
Int: number;
|
||||||
|
Float: number;
|
||||||
|
timestamptz: any;
|
||||||
|
uuid: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Boolean expression to compare columns of type "String". All fields are combined with logical 'AND'. */
|
||||||
|
export type StringComparisonExp = {
|
||||||
|
_eq?: InputMaybe<Scalars['String']>;
|
||||||
|
_gt?: InputMaybe<Scalars['String']>;
|
||||||
|
_gte?: InputMaybe<Scalars['String']>;
|
||||||
|
/** does the column match the given case-insensitive pattern */
|
||||||
|
_ilike?: InputMaybe<Scalars['String']>;
|
||||||
|
_in?: InputMaybe<Array<Scalars['String']>>;
|
||||||
|
/** does the column match the given POSIX regular expression, case insensitive */
|
||||||
|
_iregex?: InputMaybe<Scalars['String']>;
|
||||||
|
_is_null?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
/** does the column match the given pattern */
|
||||||
|
_like?: InputMaybe<Scalars['String']>;
|
||||||
|
_lt?: InputMaybe<Scalars['String']>;
|
||||||
|
_lte?: InputMaybe<Scalars['String']>;
|
||||||
|
_neq?: InputMaybe<Scalars['String']>;
|
||||||
|
/** does the column NOT match the given case-insensitive pattern */
|
||||||
|
_nilike?: InputMaybe<Scalars['String']>;
|
||||||
|
_nin?: InputMaybe<Array<Scalars['String']>>;
|
||||||
|
/** does the column NOT match the given POSIX regular expression, case insensitive */
|
||||||
|
_niregex?: InputMaybe<Scalars['String']>;
|
||||||
|
/** does the column NOT match the given pattern */
|
||||||
|
_nlike?: InputMaybe<Scalars['String']>;
|
||||||
|
/** does the column NOT match the given POSIX regular expression, case sensitive */
|
||||||
|
_nregex?: InputMaybe<Scalars['String']>;
|
||||||
|
/** does the column NOT match the given SQL regular expression */
|
||||||
|
_nsimilar?: InputMaybe<Scalars['String']>;
|
||||||
|
/** does the column match the given POSIX regular expression, case sensitive */
|
||||||
|
_regex?: InputMaybe<Scalars['String']>;
|
||||||
|
/** does the column match the given SQL regular expression */
|
||||||
|
_similar?: InputMaybe<Scalars['String']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** mutation root */
|
||||||
|
export type MutationRoot = {
|
||||||
|
__typename?: 'mutation_root';
|
||||||
|
/** delete single row from the table: "todos" */
|
||||||
|
deleteTodo?: Maybe<Todos>;
|
||||||
|
/** delete data from the table: "todos" */
|
||||||
|
deleteTodos?: Maybe<TodosMutationResponse>;
|
||||||
|
/** insert a single row into the table: "todos" */
|
||||||
|
insertTodo?: Maybe<Todos>;
|
||||||
|
/** insert data into the table: "todos" */
|
||||||
|
insertTodos?: Maybe<TodosMutationResponse>;
|
||||||
|
/** update single row of the table: "todos" */
|
||||||
|
updateTodo?: Maybe<Todos>;
|
||||||
|
/** update data of the table: "todos" */
|
||||||
|
updateTodos?: Maybe<TodosMutationResponse>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** mutation root */
|
||||||
|
export type MutationRootDeleteTodoArgs = {
|
||||||
|
id: Scalars['uuid'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** mutation root */
|
||||||
|
export type MutationRootDeleteTodosArgs = {
|
||||||
|
where: TodosBoolExp;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** mutation root */
|
||||||
|
export type MutationRootInsertTodoArgs = {
|
||||||
|
object: TodosInsertInput;
|
||||||
|
on_conflict?: InputMaybe<TodosOnConflict>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** mutation root */
|
||||||
|
export type MutationRootInsertTodosArgs = {
|
||||||
|
objects: Array<TodosInsertInput>;
|
||||||
|
on_conflict?: InputMaybe<TodosOnConflict>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** mutation root */
|
||||||
|
export type MutationRootUpdateTodoArgs = {
|
||||||
|
_set?: InputMaybe<TodosSetInput>;
|
||||||
|
pk_columns: TodosPkColumnsInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** mutation root */
|
||||||
|
export type MutationRootUpdateTodosArgs = {
|
||||||
|
_set?: InputMaybe<TodosSetInput>;
|
||||||
|
where: TodosBoolExp;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** column ordering options */
|
||||||
|
export enum OrderBy {
|
||||||
|
/** in ascending order, nulls last */
|
||||||
|
Asc = 'asc',
|
||||||
|
/** in ascending order, nulls first */
|
||||||
|
AscNullsFirst = 'asc_nulls_first',
|
||||||
|
/** in ascending order, nulls last */
|
||||||
|
AscNullsLast = 'asc_nulls_last',
|
||||||
|
/** in descending order, nulls first */
|
||||||
|
Desc = 'desc',
|
||||||
|
/** in descending order, nulls first */
|
||||||
|
DescNullsFirst = 'desc_nulls_first',
|
||||||
|
/** in descending order, nulls last */
|
||||||
|
DescNullsLast = 'desc_nulls_last'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueryRoot = {
|
||||||
|
__typename?: 'query_root';
|
||||||
|
/** fetch data from the table: "todos" using primary key columns */
|
||||||
|
todo?: Maybe<Todos>;
|
||||||
|
/** fetch data from the table: "todos" */
|
||||||
|
todos: Array<Todos>;
|
||||||
|
/** fetch aggregated fields from the table: "todos" */
|
||||||
|
todosAggregate: TodosAggregate;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryRootTodoArgs = {
|
||||||
|
id: Scalars['uuid'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryRootTodosArgs = {
|
||||||
|
distinct_on?: InputMaybe<Array<TodosSelectColumn>>;
|
||||||
|
limit?: InputMaybe<Scalars['Int']>;
|
||||||
|
offset?: InputMaybe<Scalars['Int']>;
|
||||||
|
order_by?: InputMaybe<Array<TodosOrderBy>>;
|
||||||
|
where?: InputMaybe<TodosBoolExp>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryRootTodosAggregateArgs = {
|
||||||
|
distinct_on?: InputMaybe<Array<TodosSelectColumn>>;
|
||||||
|
limit?: InputMaybe<Scalars['Int']>;
|
||||||
|
offset?: InputMaybe<Scalars['Int']>;
|
||||||
|
order_by?: InputMaybe<Array<TodosOrderBy>>;
|
||||||
|
where?: InputMaybe<TodosBoolExp>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SubscriptionRoot = {
|
||||||
|
__typename?: 'subscription_root';
|
||||||
|
/** fetch data from the table: "todos" using primary key columns */
|
||||||
|
todo?: Maybe<Todos>;
|
||||||
|
/** fetch data from the table: "todos" */
|
||||||
|
todos: Array<Todos>;
|
||||||
|
/** fetch aggregated fields from the table: "todos" */
|
||||||
|
todosAggregate: TodosAggregate;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type SubscriptionRootTodoArgs = {
|
||||||
|
id: Scalars['uuid'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type SubscriptionRootTodosArgs = {
|
||||||
|
distinct_on?: InputMaybe<Array<TodosSelectColumn>>;
|
||||||
|
limit?: InputMaybe<Scalars['Int']>;
|
||||||
|
offset?: InputMaybe<Scalars['Int']>;
|
||||||
|
order_by?: InputMaybe<Array<TodosOrderBy>>;
|
||||||
|
where?: InputMaybe<TodosBoolExp>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type SubscriptionRootTodosAggregateArgs = {
|
||||||
|
distinct_on?: InputMaybe<Array<TodosSelectColumn>>;
|
||||||
|
limit?: InputMaybe<Scalars['Int']>;
|
||||||
|
offset?: InputMaybe<Scalars['Int']>;
|
||||||
|
order_by?: InputMaybe<Array<TodosOrderBy>>;
|
||||||
|
where?: InputMaybe<TodosBoolExp>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Boolean expression to compare columns of type "timestamptz". All fields are combined with logical 'AND'. */
|
||||||
|
export type TimestamptzComparisonExp = {
|
||||||
|
_eq?: InputMaybe<Scalars['timestamptz']>;
|
||||||
|
_gt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
|
_gte?: InputMaybe<Scalars['timestamptz']>;
|
||||||
|
_in?: InputMaybe<Array<Scalars['timestamptz']>>;
|
||||||
|
_is_null?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
_lt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
|
_lte?: InputMaybe<Scalars['timestamptz']>;
|
||||||
|
_neq?: InputMaybe<Scalars['timestamptz']>;
|
||||||
|
_nin?: InputMaybe<Array<Scalars['timestamptz']>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** columns and relationships of "todos" */
|
||||||
|
export type Todos = {
|
||||||
|
__typename?: 'todos';
|
||||||
|
contents: Scalars['String'];
|
||||||
|
createdAt: Scalars['timestamptz'];
|
||||||
|
id: Scalars['uuid'];
|
||||||
|
updatedAt: Scalars['timestamptz'];
|
||||||
|
userId: Scalars['uuid'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** aggregated selection of "todos" */
|
||||||
|
export type TodosAggregate = {
|
||||||
|
__typename?: 'todos_aggregate';
|
||||||
|
aggregate?: Maybe<TodosAggregateFields>;
|
||||||
|
nodes: Array<Todos>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** aggregate fields of "todos" */
|
||||||
|
export type TodosAggregateFields = {
|
||||||
|
__typename?: 'todos_aggregate_fields';
|
||||||
|
count: Scalars['Int'];
|
||||||
|
max?: Maybe<TodosMaxFields>;
|
||||||
|
min?: Maybe<TodosMinFields>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** aggregate fields of "todos" */
|
||||||
|
export type TodosAggregateFieldsCountArgs = {
|
||||||
|
columns?: InputMaybe<Array<TodosSelectColumn>>;
|
||||||
|
distinct?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Boolean expression to filter rows from the table "todos". All fields are combined with a logical 'AND'. */
|
||||||
|
export type TodosBoolExp = {
|
||||||
|
_and?: InputMaybe<Array<TodosBoolExp>>;
|
||||||
|
_not?: InputMaybe<TodosBoolExp>;
|
||||||
|
_or?: InputMaybe<Array<TodosBoolExp>>;
|
||||||
|
contents?: InputMaybe<StringComparisonExp>;
|
||||||
|
createdAt?: InputMaybe<TimestamptzComparisonExp>;
|
||||||
|
id?: InputMaybe<UuidComparisonExp>;
|
||||||
|
updatedAt?: InputMaybe<TimestamptzComparisonExp>;
|
||||||
|
userId?: InputMaybe<UuidComparisonExp>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** unique or primary key constraints on table "todos" */
|
||||||
|
export enum TodosConstraint {
|
||||||
|
/** unique or primary key constraint */
|
||||||
|
TodosPkey = 'todos_pkey'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** input type for inserting data into table "todos" */
|
||||||
|
export type TodosInsertInput = {
|
||||||
|
contents?: InputMaybe<Scalars['String']>;
|
||||||
|
id?: InputMaybe<Scalars['uuid']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** aggregate max on columns */
|
||||||
|
export type TodosMaxFields = {
|
||||||
|
__typename?: 'todos_max_fields';
|
||||||
|
contents?: Maybe<Scalars['String']>;
|
||||||
|
createdAt?: Maybe<Scalars['timestamptz']>;
|
||||||
|
id?: Maybe<Scalars['uuid']>;
|
||||||
|
updatedAt?: Maybe<Scalars['timestamptz']>;
|
||||||
|
userId?: Maybe<Scalars['uuid']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** aggregate min on columns */
|
||||||
|
export type TodosMinFields = {
|
||||||
|
__typename?: 'todos_min_fields';
|
||||||
|
contents?: Maybe<Scalars['String']>;
|
||||||
|
createdAt?: Maybe<Scalars['timestamptz']>;
|
||||||
|
id?: Maybe<Scalars['uuid']>;
|
||||||
|
updatedAt?: Maybe<Scalars['timestamptz']>;
|
||||||
|
userId?: Maybe<Scalars['uuid']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** response of any mutation on the table "todos" */
|
||||||
|
export type TodosMutationResponse = {
|
||||||
|
__typename?: 'todos_mutation_response';
|
||||||
|
/** number of rows affected by the mutation */
|
||||||
|
affected_rows: Scalars['Int'];
|
||||||
|
/** data from the rows affected by the mutation */
|
||||||
|
returning: Array<Todos>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** on_conflict condition type for table "todos" */
|
||||||
|
export type TodosOnConflict = {
|
||||||
|
constraint: TodosConstraint;
|
||||||
|
update_columns?: Array<TodosUpdateColumn>;
|
||||||
|
where?: InputMaybe<TodosBoolExp>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Ordering options when selecting data from "todos". */
|
||||||
|
export type TodosOrderBy = {
|
||||||
|
contents?: InputMaybe<OrderBy>;
|
||||||
|
createdAt?: InputMaybe<OrderBy>;
|
||||||
|
id?: InputMaybe<OrderBy>;
|
||||||
|
updatedAt?: InputMaybe<OrderBy>;
|
||||||
|
userId?: InputMaybe<OrderBy>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** primary key columns input for table: todos */
|
||||||
|
export type TodosPkColumnsInput = {
|
||||||
|
id: Scalars['uuid'];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** select columns of table "todos" */
|
||||||
|
export enum TodosSelectColumn {
|
||||||
|
/** column name */
|
||||||
|
Contents = 'contents',
|
||||||
|
/** column name */
|
||||||
|
CreatedAt = 'createdAt',
|
||||||
|
/** column name */
|
||||||
|
Id = 'id',
|
||||||
|
/** column name */
|
||||||
|
UpdatedAt = 'updatedAt',
|
||||||
|
/** column name */
|
||||||
|
UserId = 'userId'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** input type for updating data in table "todos" */
|
||||||
|
export type TodosSetInput = {
|
||||||
|
contents?: InputMaybe<Scalars['String']>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** update columns of table "todos" */
|
||||||
|
export enum TodosUpdateColumn {
|
||||||
|
/** column name */
|
||||||
|
Contents = 'contents'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Boolean expression to compare columns of type "uuid". All fields are combined with logical 'AND'. */
|
||||||
|
export type UuidComparisonExp = {
|
||||||
|
_eq?: InputMaybe<Scalars['uuid']>;
|
||||||
|
_gt?: InputMaybe<Scalars['uuid']>;
|
||||||
|
_gte?: InputMaybe<Scalars['uuid']>;
|
||||||
|
_in?: InputMaybe<Array<Scalars['uuid']>>;
|
||||||
|
_is_null?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
_lt?: InputMaybe<Scalars['uuid']>;
|
||||||
|
_lte?: InputMaybe<Scalars['uuid']>;
|
||||||
|
_neq?: InputMaybe<Scalars['uuid']>;
|
||||||
|
_nin?: InputMaybe<Array<Scalars['uuid']>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TodoListQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type TodoListQuery = { __typename?: 'query_root', todos: Array<{ __typename?: 'todos', id: any, contents: string }> };
|
||||||
|
|
||||||
|
export type AddItemMutationVariables = Exact<{
|
||||||
|
contents: Scalars['String'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type AddItemMutation = { __typename?: 'mutation_root', insertTodo?: { __typename?: 'todos', id: any } | null };
|
||||||
@@ -13,7 +13,6 @@ export const EmailPassword: React.FC = () => {
|
|||||||
const [otp, setOtp] = useState('')
|
const [otp, setOtp] = useState('')
|
||||||
const { signInEmailPassword, needsMfaOtp, sendMfaOtp } = useSignInEmailPassword()
|
const { signInEmailPassword, needsMfaOtp, sendMfaOtp } = useSignInEmailPassword()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const [emailVerificationToggle, setEmailVerificationToggle] = useState(false)
|
const [emailVerificationToggle, setEmailVerificationToggle] = useState(false)
|
||||||
|
|
||||||
const signIn = async () => {
|
const signIn = async () => {
|
||||||
@@ -26,14 +25,22 @@ export const EmailPassword: React.FC = () => {
|
|||||||
})
|
})
|
||||||
} else if (result.needsEmailVerification) {
|
} else if (result.needsEmailVerification) {
|
||||||
setEmailVerificationToggle(true)
|
setEmailVerificationToggle(true)
|
||||||
} else if (!result.needsEmailVerification) {
|
} else if (result.isSuccess) {
|
||||||
navigate('/', { replace: true })
|
navigate('/', { replace: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendOtp = async () => {
|
const sendOtp = async () => {
|
||||||
sendMfaOtp(otp)
|
const result = await sendMfaOtp(otp)
|
||||||
console.log('TODO')
|
if (result.isError) {
|
||||||
|
showNotification({
|
||||||
|
color: 'red',
|
||||||
|
title: 'Error',
|
||||||
|
message: result.error?.message
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
navigate('/', { replace: true })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (needsMfaOtp)
|
if (needsMfaOtp)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { FaLock } from 'react-icons/fa'
|
|||||||
import { Link, Route, Routes, useNavigate } from 'react-router-dom'
|
import { Link, Route, Routes, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import { Anchor, Center, Divider, Text } from '@mantine/core'
|
import { Anchor, Center, Divider, Text } from '@mantine/core'
|
||||||
|
import { useSignInAnonymous } from '@nhost/react'
|
||||||
|
|
||||||
import AuthLayout from '../components/AuthLayout'
|
import AuthLayout from '../components/AuthLayout'
|
||||||
import AuthLink from '../components/AuthLink'
|
import AuthLink from '../components/AuthLink'
|
||||||
@@ -10,7 +11,6 @@ import OAuthLinks from '../components/OauthLinks'
|
|||||||
import { EmailPassword } from './email-password'
|
import { EmailPassword } from './email-password'
|
||||||
import { EmailPasswordless } from './email-passwordless'
|
import { EmailPasswordless } from './email-passwordless'
|
||||||
import { ForgotPassword } from './forgot-password'
|
import { ForgotPassword } from './forgot-password'
|
||||||
import { useSignInAnonymous } from '@nhost/react'
|
|
||||||
|
|
||||||
const Index: React.FC = () => (
|
const Index: React.FC = () => (
|
||||||
<>
|
<>
|
||||||
@@ -41,10 +41,13 @@ export const SignInPage: React.FC = () => {
|
|||||||
<Center>
|
<Center>
|
||||||
<Text>
|
<Text>
|
||||||
Don‘t have an account?{' '}
|
Don‘t have an account?{' '}
|
||||||
<Anchor component={Link} to="/sign-up">
|
<Anchor role="link" component={Link} to="/sign-up">
|
||||||
Sign up
|
Sign up
|
||||||
</Anchor>{' '}
|
</Anchor>{' '}
|
||||||
or <Anchor onClick={anonymousHandler}>sign in anonymously</Anchor>
|
or{' '}
|
||||||
|
<Anchor role="link" onClick={anonymousHandler}>
|
||||||
|
sign in anonymously
|
||||||
|
</Anchor>
|
||||||
</Text>
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { FaLock } from 'react-icons/fa'
|
|||||||
import { Link, Route, Routes } from 'react-router-dom'
|
import { Link, Route, Routes } from 'react-router-dom'
|
||||||
|
|
||||||
import { Anchor, Center, Divider, Text } from '@mantine/core'
|
import { Anchor, Center, Divider, Text } from '@mantine/core'
|
||||||
|
import { useUserIsAnonymous } from '@nhost/react'
|
||||||
|
|
||||||
import AuthLayout from '../components/AuthLayout'
|
import AuthLayout from '../components/AuthLayout'
|
||||||
import AuthLink from '../components/AuthLink'
|
import AuthLink from '../components/AuthLink'
|
||||||
@@ -10,18 +11,25 @@ import OAuthLinks from '../components/OauthLinks'
|
|||||||
import { EmailPassword } from './email-password'
|
import { EmailPassword } from './email-password'
|
||||||
import { EmailPasswordless } from './email-passwordless'
|
import { EmailPasswordless } from './email-passwordless'
|
||||||
|
|
||||||
const Index: React.FC = () => (
|
const Index: React.FC = () => {
|
||||||
<>
|
const isAnonymous = useUserIsAnonymous()
|
||||||
<OAuthLinks />
|
return (
|
||||||
<Divider my="sm" />
|
<>
|
||||||
<AuthLink icon={<FaLock />} variant="outline" link="/sign-up/email-passwordless">
|
{!isAnonymous && (
|
||||||
Continue with passwordless email
|
<>
|
||||||
</AuthLink>
|
<OAuthLinks />
|
||||||
<AuthLink variant="subtle" link="/sign-up/email-password">
|
<Divider my="sm" />
|
||||||
Continue with email + password
|
</>
|
||||||
</AuthLink>
|
)}
|
||||||
</>
|
<AuthLink icon={<FaLock />} variant="outline" link="/sign-up/email-passwordless">
|
||||||
)
|
Continue with passwordless email
|
||||||
|
</AuthLink>
|
||||||
|
<AuthLink variant="subtle" link="/sign-up/email-password">
|
||||||
|
Continue with email + password
|
||||||
|
</AuthLink>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
export const SignUpPage: React.FC = () => {
|
export const SignUpPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<AuthLayout
|
<AuthLayout
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import react from '@vitejs/plugin-react'
|
|||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['react/jsx-runtime']
|
include: ['react/jsx-runtime'],
|
||||||
|
exclude: ['@nhost/react']
|
||||||
},
|
},
|
||||||
plugins: [react()]
|
plugins: [react()]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: '../../config/.eslintrc.vue.js'
|
|
||||||
}
|
|
||||||
@@ -37,5 +37,9 @@
|
|||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"vite": "^2.9.0",
|
"vite": "^2.9.0",
|
||||||
"vue-tsc": "^0.29.8"
|
"vue-tsc": "^0.29.8"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"extends": "../../config/.eslintrc.vue.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
examples/vue-apollo/src/env.d.ts
vendored
1
examples/vue-apollo/src/env.d.ts
vendored
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
declare module '*.vue' {
|
declare module '*.vue' {
|
||||||
import type { DefineComponent } from 'vue'
|
import type { DefineComponent } from 'vue'
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
|
||||||
const component: DefineComponent<{}, {}, any>
|
const component: DefineComponent<{}, {}, any>
|
||||||
export default component
|
export default component
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['../../config/.eslintrc.vue']
|
root: true,
|
||||||
|
extends: ['../../config/.eslintrc.vue.js', '@antfu'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/comma-dangle': 'off',
|
||||||
|
curly: 'off',
|
||||||
|
'quote-props': 'off',
|
||||||
|
'vue/html-self-closing': 'off',
|
||||||
|
'vue/singleline-html-element-content-newline': 'off'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.6.2",
|
"@apollo/client": "^3.6.2",
|
||||||
"@nhost/apollo": "^0.5.4",
|
"@nhost/apollo": "*",
|
||||||
"@nhost/vue": "*",
|
"@nhost/vue": "*",
|
||||||
"@vue/apollo-composable": "^4.0.0-alpha.17",
|
"@vue/apollo-composable": "^4.0.0-alpha.17",
|
||||||
"@vueuse/core": "^8.4.2",
|
"@vueuse/core": "^8.4.2",
|
||||||
@@ -38,8 +38,5 @@
|
|||||||
"vite-plugin-pages": "^0.23.0",
|
"vite-plugin-pages": "^0.23.0",
|
||||||
"vitest": "^0.12.4",
|
"vitest": "^0.12.4",
|
||||||
"vue-tsc": "^0.34.12"
|
"vue-tsc": "^0.34.12"
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": "@antfu"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/comma-dangle,curly */
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import routes from 'virtual:generated-pages'
|
import routes from 'virtual:generated-pages'
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"build": "pnpm run build:all -- --filter=!@nhost/docs --filter=!@nhost-examples/*",
|
"build": "pnpm run build:all --filter=!@nhost/docs --filter=!@nhost-examples/*",
|
||||||
"build:docs": "pnpm run build:all -- --filter=@nhost/docs",
|
"build:docs": "pnpm run build:all --filter=@nhost/docs",
|
||||||
"build:all": "turbo run build --include-dependencies",
|
"build:all": "turbo run build --include-dependencies",
|
||||||
"dev": "turbo run dev --filter=!@nhost/docs --filter=!@nhost-examples/* --filter=!@nhost/docgen --no-deps --include-dependencies",
|
"dev": "turbo run dev --filter=!@nhost/docs --filter=!@nhost-examples/* --filter=!@nhost/docgen --no-deps --include-dependencies",
|
||||||
"clean:all": "pnpm clean && rm -rf ./{{packages,examples}/*,docs}/{.nhost,node_modules} node_modules",
|
"clean:all": "pnpm clean && rm -rf ./{{packages,examples}/*,docs}/{.nhost,node_modules} node_modules",
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"c8": "^7.11.2",
|
"c8": "^7.11.2",
|
||||||
"eslint": "^8.14.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"eslint-plugin-flowtype": "^8.0.3",
|
"eslint-plugin-flowtype": "^8.0.3",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
@@ -85,5 +86,8 @@
|
|||||||
"packageManager": "pnpm@6.24.0",
|
"packageManager": "pnpm@6.24.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "./config/.eslintrc.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/apollo
|
# @nhost/apollo
|
||||||
|
|
||||||
|
## 0.5.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [6f0a3005]
|
||||||
|
- @nhost/nhost-js@1.4.0
|
||||||
|
|
||||||
## 0.5.15
|
## 0.5.15
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/apollo",
|
"name": "@nhost/apollo",
|
||||||
"version": "0.5.15",
|
"version": "0.5.16",
|
||||||
"description": "Nhost Apollo Client library",
|
"description": "Nhost Apollo Client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/core
|
# @nhost/core
|
||||||
|
|
||||||
|
## 0.7.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 6f0a3005: `sendMfaOtp` now returns a promise
|
||||||
|
When using `useSignInEmailPassword`, the `sendMfaOtp` was `void`. It now returns a promise that resolves when the server returned the result of the OTP code submission, and returns `isSuccess`, `isError`, and `error`.
|
||||||
|
|
||||||
## 0.7.0
|
## 0.7.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/core",
|
"name": "@nhost/core",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"description": "Nhost core client library",
|
"description": "Nhost core client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export * from './sendVerificationEmail'
|
|||||||
export * from './signInAnonymous'
|
export * from './signInAnonymous'
|
||||||
export * from './signInEmailPassword'
|
export * from './signInEmailPassword'
|
||||||
export * from './signInEmailPasswordless'
|
export * from './signInEmailPasswordless'
|
||||||
|
export * from './signInMfaTotp'
|
||||||
export * from './signInSmsPasswordless'
|
export * from './signInSmsPasswordless'
|
||||||
export * from './signInSmsPasswordlessOtp'
|
export * from './signInSmsPasswordlessOtp'
|
||||||
export * from './signOut'
|
export * from './signOut'
|
||||||
|
|||||||
44
packages/core/src/promises/signInMfaTotp.ts
Normal file
44
packages/core/src/promises/signInMfaTotp.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { USER_ALREADY_SIGNED_IN } from '../errors'
|
||||||
|
import { AuthInterpreter } from '../types'
|
||||||
|
|
||||||
|
import { ActionLoadingState, SessionActionHandlerResult } from './types'
|
||||||
|
|
||||||
|
export interface SignInMfaTotpHandlerResult extends SessionActionHandlerResult {}
|
||||||
|
|
||||||
|
export interface SignInMfaTotpState extends SignInMfaTotpHandlerResult, ActionLoadingState {}
|
||||||
|
|
||||||
|
export const signInMfaTotpPromise = (interpreter: AuthInterpreter, otp: string, ticket?: string) =>
|
||||||
|
new Promise<SignInMfaTotpHandlerResult>((resolve) => {
|
||||||
|
const { changed, context } = interpreter.send('SIGNIN_MFA_TOTP', {
|
||||||
|
otp,
|
||||||
|
ticket
|
||||||
|
})
|
||||||
|
if (!changed) {
|
||||||
|
return resolve({
|
||||||
|
accessToken: context.accessToken.value,
|
||||||
|
error: USER_ALREADY_SIGNED_IN,
|
||||||
|
isError: true,
|
||||||
|
isSuccess: false,
|
||||||
|
user: context.user
|
||||||
|
})
|
||||||
|
}
|
||||||
|
interpreter.onTransition((state) => {
|
||||||
|
if (state.matches({ authentication: { signedOut: 'failed' } })) {
|
||||||
|
resolve({
|
||||||
|
accessToken: null,
|
||||||
|
error: state.context.errors.authentication || null,
|
||||||
|
isError: true,
|
||||||
|
isSuccess: false,
|
||||||
|
user: null
|
||||||
|
})
|
||||||
|
} else if (state.matches({ authentication: 'signedIn' })) {
|
||||||
|
resolve({
|
||||||
|
accessToken: state.context.accessToken.value,
|
||||||
|
error: null,
|
||||||
|
isError: false,
|
||||||
|
isSuccess: true,
|
||||||
|
user: state.context.user
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ErrorPayload, USER_UNAUTHENTICATED } from '../errors'
|
import { USER_UNAUTHENTICATED } from '../errors'
|
||||||
import { AuthInterpreter } from '../types'
|
import { AuthInterpreter } from '../types'
|
||||||
|
|
||||||
import { ActionErrorState, ActionLoadingState, ActionSuccessState } from './types'
|
import { ActionErrorState, ActionLoadingState, ActionSuccessState } from './types'
|
||||||
@@ -10,7 +10,7 @@ export const signOutPromise = async (
|
|||||||
interpreter: AuthInterpreter,
|
interpreter: AuthInterpreter,
|
||||||
all?: boolean
|
all?: boolean
|
||||||
): Promise<SignOutlessHandlerResult> =>
|
): Promise<SignOutlessHandlerResult> =>
|
||||||
new Promise<{ isSuccess: boolean; error: ErrorPayload | null; isError: boolean }>((resolve) => {
|
new Promise<SignOutlessHandlerResult>((resolve) => {
|
||||||
const { event } = interpreter.send('SIGNOUT', { all })
|
const { event } = interpreter.send('SIGNOUT', { all })
|
||||||
if (event.type !== 'SIGNED_OUT') {
|
if (event.type !== 'SIGNED_OUT') {
|
||||||
return resolve({ isSuccess: false, isError: true, error: USER_UNAUTHENTICATED })
|
return resolve({ isSuccess: false, isError: true, error: USER_UNAUTHENTICATED })
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# @nhost/hasura-auth-js
|
# @nhost/hasura-auth-js
|
||||||
|
|
||||||
|
## 1.3.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 6f0a3005: Complete sign-in when email+password MFA is activated
|
||||||
|
It was not possible to complete authentication with `nhost.auth.signIn` in sending the TOTP code when email+password MFA was activated.
|
||||||
|
An user that activated MFA can now sign in with the two following steps:
|
||||||
|
```js
|
||||||
|
await nhost.auth.signIn({ email: 'email@domain.com', password: 'not-my-birthday' })
|
||||||
|
// Get the one-time password with an OTP application e.g. Google Authenticator
|
||||||
|
await nhost.auth.signIn({ otp: '123456' })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [6f0a3005]
|
||||||
|
- @nhost/core@0.7.1
|
||||||
|
|
||||||
## 1.2.0
|
## 1.2.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/hasura-auth-js",
|
"name": "@nhost/hasura-auth-js",
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"description": "Hasura-auth client",
|
"description": "Hasura-auth client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user