Compare commits

...

79 Commits

Author SHA1 Message Date
Pilou
8ee1df3be4 Merge pull request #343 from nhost/changeset-release/main
chore: update versions
2022-04-06 10:50:29 +02:00
Pilou
47ffca945e Update package.json 2022-04-06 10:46:11 +02:00
Pilou
d60f5e623c Update CHANGELOG.md 2022-04-06 10:45:41 +02:00
Pilou
6f80643ee0 Update package.json 2022-04-06 10:45:10 +02:00
Pilou
8d5084725d Update CHANGELOG.md 2022-04-06 10:44:56 +02:00
Pilou
693498dd09 Update CHANGELOG.md 2022-04-06 10:37:39 +02:00
Pilou
4d36a966ea Update CHANGELOG.md 2022-04-06 10:37:02 +02:00
Pilou
239a075f1d Update CHANGELOG.md 2022-04-06 10:36:37 +02:00
github-actions[bot]
931194812e chore: update versions 2022-04-06 08:33:17 +00:00
Pilou
c8f80c58f3 Merge pull request #335 from nhost/refactor/correct-react-hook-signature
React hook signature & add promises to hook action results
2022-04-06 10:32:33 +02:00
Pilou
7fdb5aee0a Merge pull request #334 from nhost/docs/bump-version-examples
docs: bump dependencies of react-apollo and nextjs examples
2022-04-05 20:52:12 +02:00
Pilou
1710808fef Merge pull request #321 from nhost/docs/correct-hljs
docs: pass language to react-syntax-highlighter
2022-04-05 20:52:00 +02:00
Pilou
696815d4a8 Merge pull request #311 from nhost/306-the-useuserdata-hook-does-not-return-the-emailverified-or-phonenumberverified-user-properties
Add `emailVerified`, `phoneNumber`, `phoneNumberVerified`, and `activeMfaType` to User type, and add missing providers types
2022-04-05 20:51:42 +02:00
Pilou
5cc9be00b6 Merge pull request #341 from nhost/changeset-release/main
chore: update versions
2022-04-05 16:14:26 +02:00
github-actions[bot]
28dae23a91 chore: update versions 2022-04-05 14:02:46 +00:00
Pilou
7819e20cf4 Merge pull request #340 from nhost/elitan-patch-1
Correct OAuth provider link
2022-04-05 16:01:42 +02:00
Johan Eliasson
6be3758668 Create ninety-eels-lick.md 2022-04-05 15:56:18 +02:00
Johan Eliasson
658c67faf4 Update hasura-auth-client.ts 2022-04-05 15:54:39 +02:00
Pierre-Louis Mercereau
e7f3a5f6e0 chore: ellaborate changesets 2022-04-05 10:50:55 +02:00
Pierre-Louis Mercereau
7135aee78b chore: add changeset 2022-04-05 10:42:44 +02:00
Pierre-Louis Mercereau
587eaff734 feat: add promise with the current context to hooks actions 2022-04-04 20:45:12 +02:00
Pierre-Louis Mercereau
7cf875f4b8 refactor: deprecate the use of values end as hook parameters 2022-04-04 16:56:46 +02:00
Pierre-Louis Mercereau
657cfb91c5 docs: update corresponding swagger api 2022-04-04 10:42:09 +02:00
Pierre-Louis Mercereau
103dd6e98e Merge branch 'main' into 306-the-useuserdata-hook-does-not-return-the-emailverified-or-phonenumberverified-user-properties 2022-04-04 10:40:05 +02:00
Pierre-Louis Mercereau
3c8caa680b docs: bump dependencies of react-apollo and nextjs examples
and remove workaround related to https://github.com/rsuite/rsuite/issues/2336
2022-04-03 23:45:06 +02:00
Pilou
1bcee357fe Merge pull request #325 from nhost/docs/auth-refresh-expiration
docs: change default refresh token expiration to 30 days
2022-04-03 22:32:21 +02:00
Pierre-Louis Mercereau
b729aa9290 ci: fix pnpm/npm/changeset 2022-04-03 22:26:58 +02:00
Pilou
57780ee645 Merge pull request #333 from nhost/changeset-release/main
chore: update versions
2022-04-03 15:35:58 +02:00
github-actions[bot]
aad8d22380 chore: update versions 2022-04-03 13:28:59 +00:00
Pilou
85d33c4de0 Merge pull request #332 from nhost/fix/auto-signin
fix: correct auto-signin bug introducted in previous version
2022-04-03 15:28:16 +02:00
Pierre-Louis Mercereau
ab3e2dcee9 refactor: remove log 2022-04-03 15:22:11 +02:00
Pierre-Louis Mercereau
12f4504b61 refactor: remove incorrect and unused snapshot script 2022-04-03 15:19:00 +02:00
Pierre-Louis Mercereau
71d7a11c96 refactor: correct script 2022-04-03 15:11:48 +02:00
Pierre-Louis Mercereau
16a6c5073e fix: correct auto-signin bug introducted in previous version 2022-04-03 15:05:13 +02:00
Pilou
3fcc86792a Merge branch 'main' into main 2022-04-03 13:40:31 +02:00
Pierre-Louis Mercereau
27909128e4 chore: bump versions to fix the workspace/package link bug 2022-04-02 11:25:38 +02:00
Pierre-Louis Mercereau
72371c72a1 chore: bump versions 2022-04-02 10:58:56 +02:00
Pierre-Louis Mercereau
d878414b10 ci: freeze node version 2022-04-02 10:52:27 +02:00
Pierre-Louis Mercereau
9b840f7c4a docs: change default refresh token expiration to 30 days 2022-04-01 21:15:40 +02:00
Pierre-Louis Mercereau
83d3c90f43 docs: pass language to react-syntax-highlighter 2022-03-31 21:57:40 +02:00
Pierre-Louis Mercereau
058956bdcb feat: update types 2022-03-31 12:27:47 +02:00
Pierre-Louis Mercereau
47c57ff665 ci: scope ci script 2022-03-10 13:12:08 +01:00
Pierre-Louis Mercereau
1cb330016b ci: wrap up 2022-03-10 13:06:17 +01:00
Pierre-Louis Mercereau
497652d1b4 ci: cd, not cp 2022-03-10 13:00:22 +01:00
Pierre-Louis Mercereau
20eb7aa381 ci: correct cp 2022-03-10 12:58:54 +01:00
Pierre-Louis Mercereau
4a3c2f92b1 ci: correct cp arg 2022-03-10 12:57:38 +01:00
Pierre-Louis Mercereau
5647e64265 ci: copy existing nhost project 2022-03-10 12:55:43 +01:00
Pierre-Louis Mercereau
c113debf46 ci: again 2022-03-10 12:52:49 +01:00
Pierre-Louis Mercereau
3f0ae4a58c ci: manually create nhost project 2022-03-10 12:50:36 +01:00
Pierre-Louis Mercereau
3d5b8183e8 ci: deactivate nhost 2022-03-10 12:47:38 +01:00
Pierre-Louis Mercereau
789ef8f783 ci: exec nhost in tmp 2022-03-10 12:44:45 +01:00
Pierre-Louis Mercereau
94df175ca3 ci: remove testing project 2022-03-10 12:41:24 +01:00
Pierre-Louis Mercereau
c8bcefb0e5 ci: remove testing project 2022-03-10 12:40:20 +01:00
Pierre-Louis Mercereau
fc52f59eb8 ci: again 2022-03-10 12:37:39 +01:00
Pierre-Louis Mercereau
a80389e5c7 ci: again 2022-03-10 12:30:57 +01:00
Pierre-Louis Mercereau
923276422b ci: desesperant 2022-03-10 12:28:43 +01:00
Pierre-Louis Mercereau
7c9192f3a0 ci: correct 2022-03-10 12:24:33 +01:00
Pierre-Louis Mercereau
51d139b7aa ci: single ci command 2022-03-10 12:21:58 +01:00
Pierre-Louis Mercereau
8fe1bdb6f7 ci: deactivate cache 2022-03-10 12:19:01 +01:00
Pierre-Louis Mercereau
5b288bc0d1 ci: add options 2022-03-10 12:18:38 +01:00
Pierre-Louis Mercereau
27cd769c76 ci: frozen lockfile 2022-03-10 12:16:08 +01:00
Pierre-Louis Mercereau
5bb370869d ci: clean 2022-03-10 12:13:57 +01:00
Pierre-Louis Mercereau
ec68f64db4 ci: 1.1.5 2022-03-10 12:11:48 +01:00
Pierre-Louis Mercereau
4cf8f146c9 ci: another turbo version 2022-03-10 12:08:16 +01:00
Pierre-Louis Mercereau
61cf317541 ci: decrease pnpm version 2022-03-10 12:04:03 +01:00
Pierre-Louis Mercereau
a2066c9b41 ci: increase depth 2022-03-10 12:02:25 +01:00
Pierre-Louis Mercereau
817b152704 ci: pwd 2022-03-10 12:00:40 +01:00
Pierre-Louis Mercereau
cc73494c91 ci: installl again 2022-03-10 11:58:05 +01:00
Pierre-Louis Mercereau
64ed4083b9 ci: reverse version 2022-03-10 11:54:12 +01:00
Pierre-Louis Mercereau
77e8c58cc6 ci: downgrade turborepo 2022-03-10 11:48:33 +01:00
Pierre-Louis Mercereau
0cd2eab309 ci: debug 2022-03-10 11:43:58 +01:00
Pierre-Louis Mercereau
7d8c843c74 ci: deactivate turbo cache 2022-03-10 11:41:34 +01:00
Pierre-Louis Mercereau
da1c2d6914 ci: add src to files 2022-03-10 11:38:58 +01:00
Pierre-Louis Mercereau
3a949301f9 ci: remove scope 2022-03-10 11:34:43 +01:00
Pierre-Louis Mercereau
585eebab49 ci: updated packages 2022-03-10 11:30:05 +01:00
Pierre-Louis Mercereau
45c3e4686e ci: debug 2022-03-10 11:26:04 +01:00
Pierre-Louis Mercereau
52f2e67952 ci: install dependencies 2022-03-10 11:22:24 +01:00
Pierre-Louis Mercereau
929774aa5b ci: matrix 2022-03-10 11:21:04 +01:00
Pierre-Louis Mercereau
e711e338e7 ci: clean ci 2022-03-10 11:18:56 +01:00
42 changed files with 1120 additions and 390 deletions

View File

@@ -16,29 +16,33 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - name: Checkout repository
uses: actions/checkout@v2
- name: Cache pnpm modules
uses: actions/cache@v2
with: with:
path: ~/.pnpm-store fetch-depth: 0
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-
- uses: pnpm/action-setup@v2.1.0 - uses: pnpm/action-setup@v2.1.0
with: with:
version: 6.32.3 version: 6.32.3
run_install: true # run_install: true
- name: Use Node.js 17
uses: actions/setup-node@v2
with:
node-version: '17.8.0'
cache: 'pnpm'
- name: Pick the right npm version
# * See: https://github.com/pnpm/pnpm/issues/4348
run: npm install --global npm@8.4
- name: Install dependencies
run: pnpm install
- name: Create PR or Publish release - name: Create PR or Publish release
id: changesets id: changesets
uses: changesets/action@v1 uses: changesets/action@v1
with: with:
version: pnpm ci:version version: pnpm run ci:version
commit: 'chore: update versions' commit: 'chore: update versions'
title: 'chore: update versions' title: 'chore: update versions'
publish: pnpm release publish: pnpm run release
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -2,15 +2,15 @@ import { lightNhostTheme } from '@/data/lightTheme'
import { useState } from 'react' import { useState } from 'react'
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter' import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript' import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'
import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript'
import Check from '../icons/Check' import Check from '../icons/Check'
import Copy from '../icons/Copy' import Copy from '../icons/Copy'
// @ts-ignore -> add to types SyntaxHighlighter.registerLanguage('language-js', js)
// @ts-ignore -> add to types SyntaxHighlighter.registerLanguage('language-ts', ts)
SyntaxHighlighter.registerLanguage('js', js)
// TODO highlight JSX // TODO highlight JSX
SyntaxHighlighter.registerLanguage('jsx', js) SyntaxHighlighter.registerLanguage('language-jsx', js)
export interface CodeEditorProps { export interface CodeEditorProps {
code: string code: string
@@ -24,7 +24,7 @@ export interface CodeEditorProps {
} }
const CodeEditor = (props: CodeEditorProps) => { const CodeEditor = (props: CodeEditorProps) => {
const { children, url } = props const { children, className } = props
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
return ( return (
@@ -53,6 +53,7 @@ const CodeEditor = (props: CodeEditorProps) => {
</button> </button>
</div> </div>
<SyntaxHighlighter <SyntaxHighlighter
language={className}
style={lightNhostTheme} style={lightNhostTheme}
wrapLongLines={true} wrapLongLines={true}
wrapLines={true} wrapLines={true}

View File

@@ -45,10 +45,10 @@ title: Environment Variables
| AUTH_SMS_TWILIO_FROM | | | | AUTH_SMS_TWILIO_FROM | | |
| AUTH_EMAIL_SIGNIN_EMAIL_VERIFIED_REQUIRED | When enabled, any email-based authentication requires emails to be verified by a link sent to this email. | `true` | | AUTH_EMAIL_SIGNIN_EMAIL_VERIFIED_REQUIRED | When enabled, any email-based authentication requires emails to be verified by a link sent to this email. | `true` |
| AUTH_ACCESS_CONTROL_ALLOWED_REDIRECT_URLS | | | | AUTH_ACCESS_CONTROL_ALLOWED_REDIRECT_URLS | | |
| AUTH_MFA_ENABLED | Enables users to use Multi Factor Authentication | `false` | | AUTH_MFA_ENABLED | Enables users to use Multi Factor Authentication. | `false` |
| AUTH_MFA_TOTP_ISSUER | The name of the One Time Password (OTP) issuer. Probably your app's name. | `hasura-auth` | | AUTH_MFA_TOTP_ISSUER | The name of the One Time Password (OTP) issuer. Probably your app's name. | `hasura-auth` |
| AUTH_ACCESS_TOKEN_EXPIRES_IN | | `900`(15 minutes) | | AUTH_ACCESS_TOKEN_EXPIRES_IN | Number of seconds before the access token (JWT) expires. | `900`(15 minutes) |
| AUTH_REFRESH_TOKEN_EXPIRES_IN | | `43200` (12 hours) | | AUTH_REFRESH_TOKEN_EXPIRES_IN | Number of seconds before the refresh token expires. | `2592000` (30 days) |
| AUTH_EMAIL_TEMPLATE_FETCH_URL | | | | AUTH_EMAIL_TEMPLATE_FETCH_URL | | |
| AUTH_JWT_CUSTOM_CLAIMS | | | | AUTH_JWT_CUSTOM_CLAIMS | | |

View File

@@ -8,23 +8,25 @@ title: 'Hooks'
```js ```js
const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } = const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
useSignUpEmailPassword(email?: string, password?: string, options?: Options ) useSignUpEmailPassword(options?: Options)
``` ```
| Name | Type | Notes | | Name | Type | Notes |
| ------------------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `signUpEmailPassword` | (email?: string, password?: string) => void | Used for a new user to sign up. The email/password arguments will take precedence over the possible state values used when creating the hook. | | `signUpEmailPassword` | (email?: string, password?: string) => void | Used for a new user to sign up. Returns a promise with the current context |
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. | | `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
| `needsEmailVerification` | boolean | Returns `true` if the sign-up has been accepted, but a verificaiton email has been sent and is awaiting. | | `needsEmailVerification` | boolean | Returns `true` if the sign-up has been accepted, but a verificaiton email has been sent and is awaiting. |
| `isSuccess` | boolean | Returns `true` if the sign-up suceeded. Returns `false` if the new email needs to be verified first, or if an error occurred. | | `isSuccess` | boolean | Returns `true` if the sign-up suceeded. Returns `false` if the new email needs to be verified first, or if an error occurred. |
| `isError` | boolean | Returns `true` if an error occurred. | | `isError` | boolean | Returns `true` if an error occurred. |
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. | | `error` | {status: number, error: string, message: string} \| null | Provides details about the error. |
| `options.locale` | string \| undefined | Locale of the user, in two digits, for instance `en`. | | `user` | User \| null | User information |
| `options.allowedRoles` | string[] \| undefined | Allowed roles of the user. Must be a subset of the default allowed roles defined in Hasura Auth. | | `accessToken` | string \| null | Access token (JWT) |
| `options.defaultRole` | string \| undefined | Default role of the user. Must be part of the default allowed roles defined in Hasura Auth. | | `options.locale` | string \| undefined | Locale of the user, in two digits, for instance `en`. |
| `options.displayName` | string \| undefined | | | `options.allowedRoles` | string[] \| undefined | Allowed roles of the user. Must be a subset of the default allowed roles defined in Hasura Auth. |
| `options.metadata` | Record<string, unknown> \| undefined | Custom additional user information stored in the `metadata` column. Can be any JSON object. | | `options.defaultRole` | string \| undefined | Default role of the user. Must be part of the default allowed roles defined in Hasura Auth. |
| `options.redirectTo` | string \| undefined | redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. | | `options.displayName` | string \| undefined | |
| `options.metadata` | Record<string, unknown> \| undefined | Custom additional user information stored in the `metadata` column. Can be any JSON object. |
| `options.redirectTo` | string \| undefined | redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. |
#### Usage #### Usage
@@ -36,7 +38,7 @@ const Component = () => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } = const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
useSignUpEmailPassword(email, password) useSignUpEmailPassword()
return ( return (
<div> <div>
<input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" /> <input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" />
@@ -45,7 +47,7 @@ const Component = () => {
onChange={(event) => setPassword(event.target.value)} onChange={(event) => setPassword(event.target.value)}
placeholder="Password" placeholder="Password"
/> />
<button onClick={signUpEmailPassword}>Register</button> <button onClick={() => signUpEmailPassword(email, password)}>Register</button>
{isSuccess && <div>Your account have beed created! You are now authenticated</div>} {isSuccess && <div>Your account have beed created! You are now authenticated</div>}
{needsEmailVerification && ( {needsEmailVerification && (
<div>Please check your mailbox and follow the verification link to verify your email</div> <div>Please check your mailbox and follow the verification link to verify your email</div>
@@ -58,20 +60,31 @@ const Component = () => {
### Email and Password Sign-In ### Email and Password Sign-In
```js ```js
const { signInEmailPassword, isLoading, needsEmailVerification, needsMfaOtp, sendMfaOtp, isSuccess, isError, error } = const {
useSignInEmailPassword(email?: string, password?: string) signInEmailPassword,
isLoading,
needsEmailVerification,
needsMfaOtp,
sendMfaOtp,
isSuccess,
isError,
error,
user
} = useSignInEmailPassword()
``` ```
| Name | Type | Notes | | Name | Type | Notes |
| ------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `signInEmailPassword` | (email?: string, password?: string) => void | Will try to authenticate. The email/password arguments will take precedence over the possible state values used when creating the hook. | | `signInEmailPassword` | (email?: string, password?: string) | Will try to authenticate. Returns a promise with the current context |
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. | | `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
| `needsEmailVerification` | boolean | Returns `true` if the user email is still pending email verification. | | `needsEmailVerification` | boolean | Returns `true` if the user email is still pending email verification. |
| `needsMfaOtp` | boolean | Returns `true` if the server is awaiting an MFA one-time password to complete the authentication. | | `needsMfaOtp` | boolean | Returns `true` if the server is awaiting an MFA one-time password to complete the authentication. |
| `sendMfaOtp` | (otp: string) => void | Sends MFA One-time password. Will turn either `isSuccess` or `isError` to true, and store potential error in `error`. | | `sendMfaOtp` | (otp: string) => void | Sends MFA One-time password. Will turn either `isSuccess` or `isError` to true, and store potential error in `error`. |
| `isSuccess` | boolean | Returns `true` if the user has successfully authenticated. Returns `false` in case or error or if the new email needs to be verified first. | | `isSuccess` | boolean | Returns `true` if the user has successfully authenticated. Returns `false` in case or error or if the new email needs to be verified first. |
| `isError` | boolean | Returns `true` if an error occurred. | | `isError` | boolean | Returns `true` if an error occurred. |
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. | | `error` | {status: number, error: string, message: string} \| null | Provides details about the error. |
| `user` | User \| null | User information |
| `accessToken` | string \| null | Access token (JWT) |
#### Usage #### Usage
@@ -83,7 +96,7 @@ const Component = () => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const { signInEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } = const { signInEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
useSignInEmailPassword(email, password) useSignInEmailPassword()
return ( return (
<div> <div>
<input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" /> <input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" />
@@ -92,7 +105,7 @@ const Component = () => {
onChange={(event) => setPassword(event.target.value)} onChange={(event) => setPassword(event.target.value)}
placeholder="Password" placeholder="Password"
/> />
<button onClick={signInEmailPassword}>Register</button> <button onClick={() => signInEmailPassword(email, password)}>Register</button>
{isSuccess && <div>Authentication suceeded</div>} {isSuccess && <div>Authentication suceeded</div>}
{needsEmailVerification && ( {needsEmailVerification && (
<div> <div>
@@ -139,12 +152,12 @@ const Component = () => {
```js ```js
const { signInEmailPasswordless, isLoading, isSuccess, isError, error } = const { signInEmailPasswordless, isLoading, isSuccess, isError, error } =
useSignInEmailPasswordless(email?: string, options?: Options) useSignInEmailPasswordless(options?: Options)
``` ```
| Name | Type | Notes | | Name | Type | Notes |
| ------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `signInEmailPasswordless` | (email?: string) => void | Sends a magic link to the given email The email argument will take precedence over the the possible state value used when creating the hook. | | `signInEmailPasswordless` | (email?: string) => void | Sends a magic link to the given email. |
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. | | `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
| `isSuccess` | boolean | Returns `true` if the magic link email user has successfully send. | | `isSuccess` | boolean | Returns `true` if the magic link email user has successfully send. |
| `isError` | boolean | Returns `true` if an error occurred. | | `isError` | boolean | Returns `true` if an error occurred. |
@@ -165,11 +178,11 @@ import { useSignInEmailPasswordless } from '@nhost/react'
const Component = () => { const Component = () => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const { signInEmailPasswordless, isLoading, isSuccess, isError, error } = const { signInEmailPasswordless, isLoading, isSuccess, isError, error } =
useSignInEmailPasswordless(email) useSignInEmailPasswordless()
return ( return (
<div> <div>
<input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" /> <input value={email} onChange={(event) => setEmail(event.target.value)} placeholder="Email" />
<button onClick={signInEmailPasswordless}>Authenticate</button> <button onClick={() => signInEmailPasswordless(email)}>Authenticate</button>
{isSuccess && ( {isSuccess && (
<div> <div>
An email has been sent to {email}. Please check your mailbox and click on the An email has been sent to {email}. Please check your mailbox and click on the
@@ -253,17 +266,17 @@ const accessToken = useAccessToken()
```js ```js
const { changeEmail, isLoading, isSuccess, needsEmailVerification, isError, error } = const { changeEmail, isLoading, isSuccess, needsEmailVerification, isError, error } =
useChangeEmail(email?: string, options?: { redirectTo?: string }) useChangeEmail(options?: { redirectTo?: string })
``` ```
| Name | Type | Notes | | Name | Type | Notes |
| ------------------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `changeEmail` | (email?: string) => void | Requests the email change. The arguement password will take precedence over the the possible state value used when creating the hook. | | `changeEmail` | (email?: string) => void | Requests the email change. Returns a promise with the current context |
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. | | `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
| `needsEmailVerification` | boolean | Returns `true` if the email change has been requested, but that a email has been sent to the user to verify the new email. | | `needsEmailVerification` | boolean | Returns `true` if the email change has been requested, but that a email has been sent to the user to verify the new email. |
| `isError` | boolean | Returns `true` if an error occurred. | | `isError` | boolean | Returns `true` if an error occurred. |
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. | | `error` | {status: number, error: string, message: string} \| null | Provides details about the error. |
| `redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. | | `redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. |
#### Usage #### Usage
@@ -273,12 +286,11 @@ import { useChangeEmail } from '@nhost/react'
const Component = () => { const Component = () => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const { changeEmail, isLoading, needsEmailVerification, isError, error } = const { changeEmail, isLoading, needsEmailVerification, isError, error } = useChangeEmail()
useChangeEmail(password)
return ( return (
<div> <div>
<input value={email} onChange={(event) => setEmail(event.target.value)} /> <input value={email} onChange={(event) => setEmail(event.target.value)} />
<button onClick={changeEmail}>Change password</button> <button onClick={() => changeEmail(email)}>Change email</button>
{needsEmailVerification && ( {needsEmailVerification && (
<div> <div>
Please check your mailbox and follow the verification link to confirm your new email Please check your mailbox and follow the verification link to confirm your new email
@@ -292,16 +304,16 @@ const Component = () => {
### Change password ### Change password
```js ```js
const { changePassword, isLoading, isSuccess, isError, error } = useChangePassword(password?: string) const { changePassword, isLoading, isSuccess, isError, error } = useChangePassword()
``` ```
| Name | Type | Notes | | Name | Type | Notes |
| ---------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `changePassword` | (password?: string) => void | Requests the password change. The arguement password will take precedence over the the possible state value used when creating the hook. | | `changePassword` | (password?: string) | Requests the password change. Returns a promise with the current context |
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. | | `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
| `isSuccess` | boolean | Returns `true` if the password has beed successfully changed. | | `isSuccess` | boolean | Returns `true` if the password has beed successfully changed. |
| `isError` | boolean | Returns `true` if an error occurred. | | `isError` | boolean | Returns `true` if an error occurred. |
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. | | `error` | {status: number, error: string, message: string} \| null | Provides details about the error. |
#### Usage #### Usage
@@ -311,11 +323,11 @@ import { useChangePassword } from '@nhost/react'
const Component = () => { const Component = () => {
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const { changePassword, isLoading, isSuccess, isError, error } = useChangePassword(password) const { changePassword, isLoading, isSuccess, isError, error } = useChangePassword()
return ( return (
<div> <div>
<input value={password} onChange={(event) => setPassword(event.target.value)} /> <input value={password} onChange={(event) => setPassword(event.target.value)} />
<button onClick={changePassword}>Change password</button> <button onClick={() => changePassword(password)}>Change password</button>
</div> </div>
) )
} }
@@ -326,17 +338,17 @@ const Component = () => {
If a user loses their password, we can resend them an email to authenticate so that they can change it to a new one: If a user loses their password, we can resend them an email to authenticate so that they can change it to a new one:
```js ```js
const { resetPassword, isLoading, isSent, isError, error } = useResetPassword(email?: string, options?: { redirectTo?: string }) const { resetPassword, isLoading, isSent, isError, error } = useResetPassword(options?: { redirectTo?: string })
``` ```
| Name | Type | Notes | | Name | Type | Notes |
| --------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resetPassword` | (email?: string) => void | Sends an email with a temporary connection link. The arguement email will take precedence over the the possible state value used when creating the hook. | | `resetPassword` | (email?: string) | Sends an email with a temporary connection link. Returns a promise with the current context |
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. | | `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
| `isSent` | boolean | Returns `true` when the email has been successfully sent. | | `isSent` | boolean | Returns `true` when the email has been successfully sent. |
| `isError` | boolean | Returns `true` if an error occurred. | | `isError` | boolean | Returns `true` if an error occurred. |
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. | | `error` | {status: number, error: string, message: string} \| null | Provides details about the error. |
| `redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. | | `redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. |
#### Usage #### Usage
@@ -345,12 +357,12 @@ import { useState } from 'react'
import { useResetPassword } from '@nhost/react' import { useResetPassword } from '@nhost/react'
const Component = () => { const Component = () => {
const [email, setEamil] = useState('') const [email, setEmail] = useState('')
const { resetPassword, isLoading, isSent, isError, error } = useResetPassword(email?: string) const { resetPassword, isLoading, isSent, isError, error } = useResetPassword()
return ( return (
<div> <div>
<input value={email} onChange={(event) => setEmail(event.target.value)} /> <input value={email} onChange={(event) => setEmail(event.target.value)} />
<button onClick={resetPassword}>Send reset link</button> <button onClick={() => resetPassword(email)}>Send reset link</button>
</div> </div>
) )
} }
@@ -360,17 +372,17 @@ const Component = () => {
```js ```js
const { sendEmail, isLoading, isSent, isError, error } = const { sendEmail, isLoading, isSent, isError, error } =
useSendVerificationEmail(email?: string, options?: { redirectTo?: string }) useSendVerificationEmail(options?: { redirectTo?: string })
``` ```
| Name | Type | Notes | | Name | Type | Notes |
| ------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sendEmail` | (email?: string) => void | Resend the verification email. | | `sendEmail` | (email?: string) | Resend the verification email. Returns a promise with the current context |
| `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. | | `isLoading` | boolean | Returns `true` when the action is executing, `false` when it finished its execution. |
| `isSent` | boolean | Returns `true` if the verification email has been sent | | `isSent` | boolean | Returns `true` if the verification email has been sent |
| `isError` | boolean | Returns `true` if an error occurred. | | `isError` | boolean | Returns `true` if an error occurred. |
| `error` | {status: number, error: string, message: string} \| undefined | Provides details about the error. | | `error` | {status: number, error: string, message: string} \| null | Provides details about the error. |
| `redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. | | `redirectTo` | string \| undefined | Redirection path in the client application that will be used in the link in the verification email. For instance, if you want to redirect to `https://myapp.com/success`, the `redirectTo` value is `'/success'`. |
#### Usage #### Usage
@@ -380,11 +392,11 @@ import { useSendVerificationEmail } from '@nhost/react'
const Component = () => { const Component = () => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const { sendEmail, isLoading, isSent, isError, error } = useSendVerificationEmail(email) const { sendEmail, isLoading, isSent, isError, error } = useSendVerificationEmail()
return ( return (
<div> <div>
<input value={email} onChange={(event) => setEmail(event.target.value)} /> <input value={email} onChange={(event) => setEmail(event.target.value)} />
<button onClick={sendEmail}>Send email verification</button> <button onClick={() => sendEmail(email)}>Send email verification</button>
{isSent && ( {isSent && (
<div>Please check your mailbox and follow the verification link to confirm your email</div> <div>Please check your mailbox and follow the verification link to confirm your email</div>
)} )}

View File

@@ -668,6 +668,23 @@
"additionalProperties": false, "additionalProperties": false,
"example": { "firstName": "John", "lastName": "Smith" }, "example": { "firstName": "John", "lastName": "Smith" },
"default": {} "default": {}
},
"activeMfaType": {
"anyOf": [
{ "type": "string", "enum": [""] },
{ "type": "string", "enum": ["totp"] }
],
"description": "Multi-factor authentication type. A null value deactivates MFA",
"example": "totp"
},
"emailVerified": { "type": "boolean", "default": false },
"phoneNumber": { "type": "string" },
"phoneNumberVerified": { "type": "boolean", "default": false },
"roles": {
"type": "array",
"items": { "oneOf": [{ "enum": ["me"] }, { "enum": ["user"] }] },
"example": ["me", "user"],
"default": ["me", "user"]
} }
}, },
"required": [ "required": [
@@ -678,7 +695,12 @@
"email", "email",
"isAnonymous", "isAnonymous",
"defaultRole", "defaultRole",
"metadata" "metadata",
"activeMfaType",
"emailVerified",
"phoneNumber",
"phoneNumberVerified",
"roles"
], ],
"additionalProperties": false "additionalProperties": false
}, },

View File

@@ -10,9 +10,9 @@
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.10", "@apollo/client": "^3.5.10",
"@nhost/nextjs": "^1.0.0", "@nhost/nextjs": "^1.0.8",
"@nhost/react": "^0.3.0", "@nhost/react": "^0.4.6",
"@nhost/react-apollo": "^4.0.0", "@nhost/react-apollo": "^4.0.8",
"graphql": "^16.3.0", "graphql": "^16.3.0",
"next": "12.1.0", "next": "12.1.0",
"react": "17.0.2", "react": "17.0.2",

View File

@@ -25,11 +25,11 @@ const Home: NextPage = () => {
const [newPassword, setNewPassword] = useState('') const [newPassword, setNewPassword] = useState('')
const accessToken = useAccessToken() const accessToken = useAccessToken()
const { signOut } = useSignOut() const { signOut } = useSignOut()
const { signUpEmailPassword, ...signUpResult } = useSignUpEmailPassword(email, password) const { signUpEmailPassword, ...signUpResult } = useSignUpEmailPassword()
const { signInEmailPassword } = useSignInEmailPassword(email, password) const { signInEmailPassword } = useSignInEmailPassword()
const { signInEmailPasswordless } = useSignInEmailPasswordless(email) const { signInEmailPasswordless } = useSignInEmailPasswordless()
const { changeEmail, ...changeEmailResult } = useChangeEmail(newEmail) const { changeEmail, ...changeEmailResult } = useChangeEmail()
const { changePassword, ...changePasswordResult } = useChangePassword(newPassword) const { changePassword, ...changePasswordResult } = useChangePassword()
const { loading, data, error } = useAuthQuery(BOOKS_QUERY) const { loading, data, error } = useAuthQuery(BOOKS_QUERY)
return ( return (
<div> <div>
@@ -37,20 +37,24 @@ const Home: NextPage = () => {
<> <>
<button onClick={signOut}>Logout</button> <button onClick={signOut}>Logout</button>
<input value={newEmail} onChange={(e) => setNewEmail(e.target.value)} /> <input value={newEmail} onChange={(e) => setNewEmail(e.target.value)} />
<button onClick={changeEmail}>Change email</button> <button onClick={() => changeEmail(email)}>Change email</button>
<div>{JSON.stringify(changeEmailResult)}</div> <div>{JSON.stringify(changeEmailResult)}</div>
<button onClick={changePassword}>Change password</button> <button onClick={() => changePassword(password)}>Change password</button>
<input value={newPassword} onChange={(e) => setNewPassword(e.target.value)} /> <input value={newPassword} onChange={(e) => setNewPassword(e.target.value)} />
<div>{JSON.stringify(changePasswordResult)}</div> <div>{JSON.stringify(changePasswordResult)}</div>
</> </>
) : ( ) : (
<> <>
<input value={email} onChange={(e) => setEmail(e.target.value)} /> <input value={email} onChange={(e) => setEmail(e.target.value)} />
<button onClick={signInEmailPasswordless}>Passwordless signin</button> <button onClick={() => signInEmailPasswordless(email)}>Passwordless signin</button>
<div>{JSON.stringify(signUpResult)}</div> <div>{JSON.stringify(signUpResult)}</div>
<input value={password} onChange={(e) => setPassword(e.target.value)} type="password" /> <input value={password} onChange={(e) => setPassword(e.target.value)} type="password" />
<button onClick={signUpEmailPassword}>Email + password sign-up</button> <button onClick={() => signUpEmailPassword(email, password)}>
<button onClick={signInEmailPassword}>Email + password sign-in</button> Email + password sign-up
</button>
<button onClick={() => signInEmailPassword(email, password)}>
Email + password sign-in
</button>
</> </>
)} )}

View File

@@ -136,71 +136,71 @@
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.0.tgz#d27e7e76c87a460a4da99c5bfdb1618dcd6cd064" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.0.tgz#d27e7e76c87a460a4da99c5bfdb1618dcd6cd064"
integrity sha512-aBvcbMwuanDH4EMrL2TthNJy+4nP59Bimn8egqv6GHMVj0a44cU6Au4PjOhLNqEh9l+IpRGBqMTzec94UdC5xg== integrity sha512-aBvcbMwuanDH4EMrL2TthNJy+4nP59Bimn8egqv6GHMVj0a44cU6Au4PjOhLNqEh9l+IpRGBqMTzec94UdC5xg==
"@nhost/apollo@0.3.0": "@nhost/apollo@^0.3.6":
version "0.3.0" version "0.3.6"
resolved "https://registry.yarnpkg.com/@nhost/apollo/-/apollo-0.3.0.tgz#89a66e97aa6af4de6045bf0c780e8b9590c06d9b" resolved "https://registry.yarnpkg.com/@nhost/apollo/-/apollo-0.3.6.tgz#c3902c8ea9250bbe7428098ddfe145d9a2d82f2f"
integrity sha512-97DSycgPEnQQtzAWPB36yENAuQFDEl8c7qLB9xOzSnplImv5NGces40XMAkTDEWam0eD//PtXP9zi+TYNeMNjw== integrity sha512-+UtPMKPkqQ9MuJ24TQB0p59uytoyBXOBYLJ0SEHvs6cAfhkOkLljk+WhWSBhUdRkD+k4iRmHrVvVCyVzLGFRPg==
dependencies: dependencies:
"@nhost/core" "0.3.0" "@nhost/core" "^0.3.7"
graphql "16" graphql "16"
subscriptions-transport-ws "^0.11.0" subscriptions-transport-ws "^0.11.0"
"@nhost/core@0.3.0": "@nhost/core@^0.3.7":
version "0.3.0" version "0.3.7"
resolved "https://registry.yarnpkg.com/@nhost/core/-/core-0.3.0.tgz#fa630afef50840cd7b8415e17894c7765cda273f" resolved "https://registry.yarnpkg.com/@nhost/core/-/core-0.3.7.tgz#115cd770dc8ea1d277a71802958c89c1aa83578b"
integrity sha512-wEdq+BLOHH7bOhBvF5gql9vtWuEtlLWvw65AtpstfX8pAUY8ce2yqamV6Z8kxr/RSYXZA+aLsKwig50qSZa99w== integrity sha512-sQZp+aet/gCbu4R7cLgRbOrZarfBc1ZTuUwsfpVChLCYtVlPtY5ZWk4pfvU9tbfGEpmbkC4Bk9KqD9AFOzT27A==
dependencies: dependencies:
axios "^0.25.0" axios "^0.25.0"
broadcast-channel "^4.10.0" broadcast-channel "^4.10.0"
js-cookie "^3.0.1" js-cookie "^3.0.1"
xstate "^4.30.5" xstate "^4.30.5"
"@nhost/hasura-auth-js@1.0.0": "@nhost/hasura-auth-js@^1.0.7":
version "1.0.0" version "1.0.7"
resolved "https://registry.yarnpkg.com/@nhost/hasura-auth-js/-/hasura-auth-js-1.0.0.tgz#0f3c267614ca328c944797f33a1921c8103dd3e5" resolved "https://registry.yarnpkg.com/@nhost/hasura-auth-js/-/hasura-auth-js-1.0.7.tgz#2c6a81d64155bd8b24bbf314154ed2e40396b151"
integrity sha512-hENPB1aMdekYxfDejthPAdoj9JarqaVkBKNRa+jun247Un1X6eFGDPo52Y2tZfP44pJEUK4EqTqkWH3DwT4uXg== integrity sha512-fj2aNY3gTt89SLMe3H4MYA50EhZz0OcBUdnES8sv1raOJyxjH94+8TfBVT0xNUqvubOM0J+0ewHAnDCkbodbXA==
dependencies: dependencies:
"@nhost/core" "0.3.0" "@nhost/core" "^0.3.7"
"@nhost/hasura-storage-js@0.2.0": "@nhost/hasura-storage-js@^0.2.0":
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/@nhost/hasura-storage-js/-/hasura-storage-js-0.2.0.tgz#e8c127d883d231313cd262553732da3b0dccb858" resolved "https://registry.yarnpkg.com/@nhost/hasura-storage-js/-/hasura-storage-js-0.2.0.tgz#e8c127d883d231313cd262553732da3b0dccb858"
integrity sha512-JumgUhnScU6Bv8SBmN2F4sY+LbrD3i25Ppr30Zjbv4MvbUguBclx9zzAwqub/P2n/azc7bjjRvYl2n2/jjKRXw== integrity sha512-JumgUhnScU6Bv8SBmN2F4sY+LbrD3i25Ppr30Zjbv4MvbUguBclx9zzAwqub/P2n/azc7bjjRvYl2n2/jjKRXw==
dependencies: dependencies:
axios "^0.21.1" axios "^0.21.1"
"@nhost/nextjs@^1.0.0": "@nhost/nextjs@^1.0.8":
version "1.0.0" version "1.0.8"
resolved "https://registry.yarnpkg.com/@nhost/nextjs/-/nextjs-1.0.0.tgz#2e91cce4352ec7c521099c8f9fccd5fef94ec902" resolved "https://registry.yarnpkg.com/@nhost/nextjs/-/nextjs-1.0.8.tgz#761a447fd6aaef43625d82c0304b027d62360d76"
integrity sha512-yrRXd3796wcLjabeqIbBgZ/IdD40xpC9LhBA9LoXHqHfNaqZ4ujtcMnSc7IZWJv45P5rDO3T8kKKk+Sm6vTAvQ== integrity sha512-lej1ZlSzh7+FNEizZuXrH4De9emRD7wvW8+huwRhTkFTxislsBuZt2tDrzktkRa5sdHSnyYyuVwZY3TEsm3JmA==
dependencies: dependencies:
"@nhost/nhost-js" "1.0.0" "@nhost/nhost-js" "^1.0.7"
cookies "^0.8.0" cookies "^0.8.0"
"@nhost/nhost-js@1.0.0": "@nhost/nhost-js@^1.0.7":
version "1.0.0" version "1.0.7"
resolved "https://registry.yarnpkg.com/@nhost/nhost-js/-/nhost-js-1.0.0.tgz#d57aa66c889922926b9bde6278d35000729f884c" resolved "https://registry.yarnpkg.com/@nhost/nhost-js/-/nhost-js-1.0.7.tgz#90ccac91f5d61d32caa8544029b71d47aa7c8b2f"
integrity sha512-ve5+TqYGcQbRwDqxVDMCJKZQlj5BFCsTzqEhIAUnLP6Gu8YlNVeUQL3Bbc19j71OkHDNhw8TAJbE2zcFUBT2Fw== integrity sha512-XBy370bVfP2IYAozeSu62/caQ+bvwvFjj+76LjatqYc7PhHwJV/Kd5SIyvWgqLFO1xQ3kAX0if137IGC+dgBOw==
dependencies: dependencies:
"@nhost/hasura-auth-js" "1.0.0" "@nhost/hasura-auth-js" "^1.0.7"
"@nhost/hasura-storage-js" "0.2.0" "@nhost/hasura-storage-js" "^0.2.0"
axios "^0.23.0" axios "^0.23.0"
jwt-decode "^3.1.2" jwt-decode "^3.1.2"
query-string "^7.0.1" query-string "^7.0.1"
"@nhost/react-apollo@^4.0.0": "@nhost/react-apollo@^4.0.8":
version "4.0.0" version "4.0.8"
resolved "https://registry.yarnpkg.com/@nhost/react-apollo/-/react-apollo-4.0.0.tgz#08be35946eadbb746f27435c3fc72cd092a97b61" resolved "https://registry.yarnpkg.com/@nhost/react-apollo/-/react-apollo-4.0.8.tgz#69c015086837c1aaa828c566f33e22b6c852529a"
integrity sha512-ZmFgBgHFJE2Z2ZBYx3U4Gry9TprUOb4NXw/K5lSKv11zPeEaclfpqn+aeQbtCj02oCnzRe+MJLsJenpcuhQRuw== integrity sha512-G9F5AFLhXJV67MKvraRwSrJpkebqd/oEGgznK5knGL+B9FYuuXtBVbr5m95ESyYkYL+y3iPU3BY5GufL/9pCGw==
dependencies: dependencies:
"@nhost/apollo" "0.3.0" "@nhost/apollo" "^0.3.6"
"@nhost/react@^0.3.0": "@nhost/react@^0.4.6":
version "0.3.0" version "0.4.6"
resolved "https://registry.yarnpkg.com/@nhost/react/-/react-0.3.0.tgz#5401922dce91bb66db5d575de1869df00fabbb8e" resolved "https://registry.yarnpkg.com/@nhost/react/-/react-0.4.6.tgz#e7f01d9cd1d3d24999a3a68a7b9c72cb3ef8fe04"
integrity sha512-jxyt4dYfphgv3bf6jvV7qDKnV5qUArFvdTYExKlBA9tsBQYta0heawMsJk0rEarEZGUw1iioiUYFKdZyl2Qi5Q== integrity sha512-bqQI2T91tDI0PkVpnfcCDsQUWgXqwcrU6r29S7u4dziEt5RVG0NJjjg2SK35VbqZvJ9vU5z0KHM+4Cd5PKql2g==
dependencies: dependencies:
"@nhost/nhost-js" "1.0.0" "@nhost/nhost-js" "^1.0.7"
"@xstate/react" "^2.0.1" "@xstate/react" "^2.0.1"
immer "^9.0.12" immer "^9.0.12"

View File

@@ -4,8 +4,8 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.10", "@apollo/client": "^3.5.10",
"@nhost/react": "^0.3.0", "@nhost/react": "^0.4.6",
"@nhost/react-apollo": "^4.0.0", "@nhost/react-apollo": "^4.0.8",
"@rsuite/icons": "^1.0.2", "@rsuite/icons": "^1.0.2",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"react": "^17.0.2", "react": "^17.0.2",
@@ -13,7 +13,7 @@
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"react-json-view": "^1.21.3", "react-json-view": "^1.21.3",
"react-router-dom": "^6.2.1", "react-router-dom": "^6.2.1",
"rsuite": "^5.6.2" "rsuite": "^5.7.1"
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -6,9 +6,13 @@ import React, { useState, useEffect } from 'react'
export const EmailPasswordlessForm: React.FC = () => { export const EmailPasswordlessForm: React.FC = () => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const navigate = useNavigate() const navigate = useNavigate()
const { signInEmailPasswordless, isError, isSuccess, error } = useSignInEmailPasswordless(email, { const { signInEmailPasswordless, isError, isSuccess, error } = useSignInEmailPasswordless(
redirectTo: '/profile' // TODO correct this once the new packages are released
}) undefined,
{
redirectTo: '/profile'
}
)
const [showError, setShowError] = useState(true) const [showError, setShowError] = useState(true)
useEffect(() => { useEffect(() => {
setShowError(false) setShowError(false)
@@ -42,7 +46,7 @@ export const EmailPasswordlessForm: React.FC = () => {
style={{ marginTop: '0.5em' }} style={{ marginTop: '0.5em' }}
onClick={() => { onClick={() => {
setShowError(true) setShowError(true)
signInEmailPasswordless() signInEmailPasswordless(email)
}} }}
> >
Continue with email Continue with email

View File

@@ -5,9 +5,13 @@ import { Button, FlexboxGrid, Input, Message, Panel, toaster, Notification } fro
export const ChangeEmail: React.FC = () => { export const ChangeEmail: React.FC = () => {
const [newEmail, setNewEmail] = useState('') const [newEmail, setNewEmail] = useState('')
const email = useEmail() const email = useEmail()
const { changeEmail, error, needsEmailVerification } = useChangeEmail(newEmail, { const { changeEmail, error, needsEmailVerification } = useChangeEmail(
redirectTo: '/profile' // TODO correct this once the new packages are released
}) undefined,
{
redirectTo: '/profile'
}
)
const [errorMessage, setErrorMessage] = useState('') const [errorMessage, setErrorMessage] = useState('')
useEffect(() => { useEffect(() => {
@@ -45,7 +49,7 @@ export const ChangeEmail: React.FC = () => {
<Input value={newEmail} onChange={setNewEmail} placeholder="New email" /> <Input value={newEmail} onChange={setNewEmail} placeholder="New email" />
</FlexboxGrid.Item> </FlexboxGrid.Item>
<FlexboxGrid.Item colspan={12}> <FlexboxGrid.Item colspan={12}>
<Button onClick={changeEmail} block appearance="primary"> <Button onClick={() => changeEmail(email)} block appearance="primary">
Change Change
</Button> </Button>
</FlexboxGrid.Item> </FlexboxGrid.Item>

View File

@@ -4,13 +4,9 @@ import { Button, FlexboxGrid, Input, Message, Panel, toaster, Notification } fro
export const ChangePassword: React.FC = () => { export const ChangePassword: React.FC = () => {
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const { changePassword, isSuccess, error } = useChangePassword(password) const { changePassword, isSuccess, error } = useChangePassword()
const [errorMessage, setErrorMessage] = useState('') const [errorMessage, setErrorMessage] = useState('')
// * See https://github.com/rsuite/rsuite/issues/2336
useEffect(() => {
toaster.push(<div />)
}, [])
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
setPassword('') setPassword('')
@@ -44,7 +40,7 @@ export const ChangePassword: React.FC = () => {
/> />
</FlexboxGrid.Item> </FlexboxGrid.Item>
<FlexboxGrid.Item colspan={12}> <FlexboxGrid.Item colspan={12}>
<Button onClick={changePassword} block appearance="primary"> <Button onClick={() => changePassword(password)} block appearance="primary">
Change Change
</Button> </Button>
</FlexboxGrid.Item> </FlexboxGrid.Item>

View File

@@ -4,8 +4,7 @@ import { Button, Input, Panel } from 'rsuite'
export const Mfa: React.FC = () => { export const Mfa: React.FC = () => {
const [code, setCode] = useState('') const [code, setCode] = useState('')
const { generateQrCode, activateMfa, isActivated, isGenerated, qrCodeDataUrl } = const { generateQrCode, activateMfa, isActivated, isGenerated, qrCodeDataUrl } = useConfigMfa()
useConfigMfa(code)
return ( return (
<Panel header="Activate 2-step verification" bordered> <Panel header="Activate 2-step verification" bordered>
@@ -18,7 +17,7 @@ export const Mfa: React.FC = () => {
<div> <div>
<img alt="qrcode" src={qrCodeDataUrl} /> <img alt="qrcode" src={qrCodeDataUrl} />
<Input value={code} onChange={setCode} placeholder="Enter activation code" /> <Input value={code} onChange={setCode} placeholder="Enter activation code" />
<Button block appearance="primary" onClick={activateMfa}> <Button block appearance="primary" onClick={() => activateMfa(code)}>
Activate Activate
</Button> </Button>
</div> </div>

View File

@@ -80,7 +80,7 @@ export const EmailPassword: React.FC = () => {
</Message> </Message>
)} )}
<Button appearance="primary" onClick={signInEmailPassword} block> <Button appearance="primary" onClick={() => signInEmailPassword(email, password)} block>
Sign in Sign in
</Button> </Button>
<Button as={NavLink} block to="/sign-in/forgot-password"> <Button as={NavLink} block to="/sign-in/forgot-password">

View File

@@ -5,7 +5,11 @@ import { NavLink } from 'react-router-dom'
export const ForgotPassword: React.FC = () => { export const ForgotPassword: React.FC = () => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const { resetPassword, isSent, error } = useResetPassword(email, { redirectTo: '/profile' }) const { resetPassword, isSent, error } = useResetPassword(
// TODO correct this once the new packages are released
undefined,
{ redirectTo: '/profile' }
)
const [errorMessage, setErrorMessage] = useState('') const [errorMessage, setErrorMessage] = useState('')
// * Set error message from the authentication hook errors // * Set error message from the authentication hook errors
@@ -16,10 +20,6 @@ export const ForgotPassword: React.FC = () => {
useEffect(() => { useEffect(() => {
setErrorMessage('') setErrorMessage('')
}, [email]) }, [email])
// * See https://github.com/rsuite/rsuite/issues/2336
useEffect(() => {
toaster.push(<div />)
}, [])
useEffect(() => { useEffect(() => {
if (isSent) { if (isSent) {
@@ -48,7 +48,7 @@ export const ForgotPassword: React.FC = () => {
</Message> </Message>
)} )}
<Button appearance="primary" onClick={resetPassword} block> <Button appearance="primary" onClick={() => resetPassword(email)} block>
Reset your password Reset your password
</Button> </Button>
<Divider /> <Divider />

View File

@@ -15,8 +15,9 @@ export const EmailPassword: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [confirmPassword, setConfirmPassword] = useState('') const [confirmPassword, setConfirmPassword] = useState('')
const { signUpEmailPassword, error, needsEmailVerification, isSuccess } = useSignUpEmailPassword( const { signUpEmailPassword, error, needsEmailVerification, isSuccess } = useSignUpEmailPassword(
email, // TODO change once the new packages have been release
password, undefined,
undefined,
options options
) )
const [errorMessage, setErrorMessage] = useState('') const [errorMessage, setErrorMessage] = useState('')
@@ -91,7 +92,7 @@ export const EmailPassword: React.FC = () => {
appearance="primary" appearance="primary"
onClick={() => { onClick={() => {
setErrorMessage('') setErrorMessage('')
signUpEmailPassword() signUpEmailPassword(email, password)
}} }}
block block
> >

View File

@@ -296,63 +296,63 @@
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0" resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0"
integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==
"@nhost/apollo@0.3.0": "@nhost/apollo@^0.3.6":
version "0.3.0" version "0.3.6"
resolved "https://registry.yarnpkg.com/@nhost/apollo/-/apollo-0.3.0.tgz#89a66e97aa6af4de6045bf0c780e8b9590c06d9b" resolved "https://registry.yarnpkg.com/@nhost/apollo/-/apollo-0.3.6.tgz#c3902c8ea9250bbe7428098ddfe145d9a2d82f2f"
integrity sha512-97DSycgPEnQQtzAWPB36yENAuQFDEl8c7qLB9xOzSnplImv5NGces40XMAkTDEWam0eD//PtXP9zi+TYNeMNjw== integrity sha512-+UtPMKPkqQ9MuJ24TQB0p59uytoyBXOBYLJ0SEHvs6cAfhkOkLljk+WhWSBhUdRkD+k4iRmHrVvVCyVzLGFRPg==
dependencies: dependencies:
"@nhost/core" "0.3.0" "@nhost/core" "^0.3.7"
graphql "16" graphql "16"
subscriptions-transport-ws "^0.11.0" subscriptions-transport-ws "^0.11.0"
"@nhost/core@0.3.0": "@nhost/core@^0.3.7":
version "0.3.0" version "0.3.7"
resolved "https://registry.yarnpkg.com/@nhost/core/-/core-0.3.0.tgz#fa630afef50840cd7b8415e17894c7765cda273f" resolved "https://registry.yarnpkg.com/@nhost/core/-/core-0.3.7.tgz#115cd770dc8ea1d277a71802958c89c1aa83578b"
integrity sha512-wEdq+BLOHH7bOhBvF5gql9vtWuEtlLWvw65AtpstfX8pAUY8ce2yqamV6Z8kxr/RSYXZA+aLsKwig50qSZa99w== integrity sha512-sQZp+aet/gCbu4R7cLgRbOrZarfBc1ZTuUwsfpVChLCYtVlPtY5ZWk4pfvU9tbfGEpmbkC4Bk9KqD9AFOzT27A==
dependencies: dependencies:
axios "^0.25.0" axios "^0.25.0"
broadcast-channel "^4.10.0" broadcast-channel "^4.10.0"
js-cookie "^3.0.1" js-cookie "^3.0.1"
xstate "^4.30.5" xstate "^4.30.5"
"@nhost/hasura-auth-js@1.0.0": "@nhost/hasura-auth-js@^1.0.7":
version "1.0.0" version "1.0.7"
resolved "https://registry.yarnpkg.com/@nhost/hasura-auth-js/-/hasura-auth-js-1.0.0.tgz#0f3c267614ca328c944797f33a1921c8103dd3e5" resolved "https://registry.yarnpkg.com/@nhost/hasura-auth-js/-/hasura-auth-js-1.0.7.tgz#2c6a81d64155bd8b24bbf314154ed2e40396b151"
integrity sha512-hENPB1aMdekYxfDejthPAdoj9JarqaVkBKNRa+jun247Un1X6eFGDPo52Y2tZfP44pJEUK4EqTqkWH3DwT4uXg== integrity sha512-fj2aNY3gTt89SLMe3H4MYA50EhZz0OcBUdnES8sv1raOJyxjH94+8TfBVT0xNUqvubOM0J+0ewHAnDCkbodbXA==
dependencies: dependencies:
"@nhost/core" "0.3.0" "@nhost/core" "^0.3.7"
"@nhost/hasura-storage-js@0.2.0": "@nhost/hasura-storage-js@^0.2.0":
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/@nhost/hasura-storage-js/-/hasura-storage-js-0.2.0.tgz#e8c127d883d231313cd262553732da3b0dccb858" resolved "https://registry.yarnpkg.com/@nhost/hasura-storage-js/-/hasura-storage-js-0.2.0.tgz#e8c127d883d231313cd262553732da3b0dccb858"
integrity sha512-JumgUhnScU6Bv8SBmN2F4sY+LbrD3i25Ppr30Zjbv4MvbUguBclx9zzAwqub/P2n/azc7bjjRvYl2n2/jjKRXw== integrity sha512-JumgUhnScU6Bv8SBmN2F4sY+LbrD3i25Ppr30Zjbv4MvbUguBclx9zzAwqub/P2n/azc7bjjRvYl2n2/jjKRXw==
dependencies: dependencies:
axios "^0.21.1" axios "^0.21.1"
"@nhost/nhost-js@1.0.0": "@nhost/nhost-js@^1.0.7":
version "1.0.0" version "1.0.7"
resolved "https://registry.yarnpkg.com/@nhost/nhost-js/-/nhost-js-1.0.0.tgz#d57aa66c889922926b9bde6278d35000729f884c" resolved "https://registry.yarnpkg.com/@nhost/nhost-js/-/nhost-js-1.0.7.tgz#90ccac91f5d61d32caa8544029b71d47aa7c8b2f"
integrity sha512-ve5+TqYGcQbRwDqxVDMCJKZQlj5BFCsTzqEhIAUnLP6Gu8YlNVeUQL3Bbc19j71OkHDNhw8TAJbE2zcFUBT2Fw== integrity sha512-XBy370bVfP2IYAozeSu62/caQ+bvwvFjj+76LjatqYc7PhHwJV/Kd5SIyvWgqLFO1xQ3kAX0if137IGC+dgBOw==
dependencies: dependencies:
"@nhost/hasura-auth-js" "1.0.0" "@nhost/hasura-auth-js" "^1.0.7"
"@nhost/hasura-storage-js" "0.2.0" "@nhost/hasura-storage-js" "^0.2.0"
axios "^0.23.0" axios "^0.23.0"
jwt-decode "^3.1.2" jwt-decode "^3.1.2"
query-string "^7.0.1" query-string "^7.0.1"
"@nhost/react-apollo@^4.0.0": "@nhost/react-apollo@^4.0.8":
version "4.0.0" version "4.0.8"
resolved "https://registry.yarnpkg.com/@nhost/react-apollo/-/react-apollo-4.0.0.tgz#08be35946eadbb746f27435c3fc72cd092a97b61" resolved "https://registry.yarnpkg.com/@nhost/react-apollo/-/react-apollo-4.0.8.tgz#69c015086837c1aaa828c566f33e22b6c852529a"
integrity sha512-ZmFgBgHFJE2Z2ZBYx3U4Gry9TprUOb4NXw/K5lSKv11zPeEaclfpqn+aeQbtCj02oCnzRe+MJLsJenpcuhQRuw== integrity sha512-G9F5AFLhXJV67MKvraRwSrJpkebqd/oEGgznK5knGL+B9FYuuXtBVbr5m95ESyYkYL+y3iPU3BY5GufL/9pCGw==
dependencies: dependencies:
"@nhost/apollo" "0.3.0" "@nhost/apollo" "^0.3.6"
"@nhost/react@^0.3.0": "@nhost/react@^0.4.6":
version "0.3.0" version "0.4.6"
resolved "https://registry.yarnpkg.com/@nhost/react/-/react-0.3.0.tgz#5401922dce91bb66db5d575de1869df00fabbb8e" resolved "https://registry.yarnpkg.com/@nhost/react/-/react-0.4.6.tgz#e7f01d9cd1d3d24999a3a68a7b9c72cb3ef8fe04"
integrity sha512-jxyt4dYfphgv3bf6jvV7qDKnV5qUArFvdTYExKlBA9tsBQYta0heawMsJk0rEarEZGUw1iioiUYFKdZyl2Qi5Q== integrity sha512-bqQI2T91tDI0PkVpnfcCDsQUWgXqwcrU6r29S7u4dziEt5RVG0NJjjg2SK35VbqZvJ9vU5z0KHM+4Cd5PKql2g==
dependencies: dependencies:
"@nhost/nhost-js" "1.0.0" "@nhost/nhost-js" "^1.0.7"
"@xstate/react" "^2.0.1" "@xstate/react" "^2.0.1"
immer "^9.0.12" immer "^9.0.12"
@@ -1409,10 +1409,10 @@ rsuite-table@^5.3.6:
lodash "^4.17.21" lodash "^4.17.21"
react-is "^17.0.2" react-is "^17.0.2"
rsuite@^5.6.2: rsuite@^5.7.1:
version "5.6.6" version "5.7.1"
resolved "https://registry.yarnpkg.com/rsuite/-/rsuite-5.6.6.tgz#03b97ec32a24212aaa95e3d14e5e85db2255175a" resolved "https://registry.yarnpkg.com/rsuite/-/rsuite-5.7.1.tgz#2c50161e568cbf0074b42e7b65592b25a3b9412d"
integrity sha512-N4xnfnOpbxkEQ4+6GP/ll76XC8motkCQUNy1WL5wayZGgmOjzbVLbOUUHn9iGN32sBLyH8/d3Lo/WxfAjprj3g== integrity sha512-vvqBadf9vJ49CW4gboFpj+Ol7M5sK7a72irwQxgxBxHBD2YEWUx9LQp137RIJV6w/nXg1P5yaObZULb5n77QoA==
dependencies: dependencies:
"@babel/runtime" "^7.8.4" "@babel/runtime" "^7.8.4"
"@juggle/resize-observer" "^3.3.1" "@juggle/resize-observer" "^3.3.1"

View File

@@ -28,11 +28,11 @@
"prettier:fix": "prettier --write .", "prettier:fix": "prettier --write .",
"lint": "pnpm turbo run lint --stream", "lint": "pnpm turbo run lint --stream",
"lint:fix": "pnpm turbo run lint:fix --stream", "lint:fix": "pnpm turbo run lint:fix --stream",
"test": "pnpm turbo run test --scope='@nhost/*' --no-deps --include-dependencies",
"prerelease": "pnpm clean && pnpm install && pnpm build", "prerelease": "pnpm clean && pnpm install && pnpm build",
"release": "pnpm run prerelease && changeset publish && git push --follow-tags && git status && pnpm -r publish", "release": "pnpm run prerelease && changeset publish",
"changeset": "changeset",
"snapshot": "pnpm prerelease && changeset version --snapshot preview && pnpm install && changeset publish --tag preview", "snapshot": "pnpm prerelease && changeset version --snapshot preview && pnpm install && changeset publish --tag preview",
"test": "pnpm turbo run test --scope='@nhost/*' --no-deps --include-dependencies",
"changeset": "changeset",
"wait": "wait-on http://localhost:1337/v1/auth/healthz -i 500 -t 120000" "wait": "wait-on http://localhost:1337/v1/auth/healthz -i 500 -t 120000"
}, },
"workspaces": [ "workspaces": [

View File

@@ -1,5 +1,34 @@
# @nhost/apollo # @nhost/apollo
## 0.3.7
### Patch Changes
- Updated dependencies [058956b]
- Updated dependencies [7cf875f]
- @nhost/core@0.3.8
## 0.3.5
### Patch Changes
- Updated dependencies [16a6c50]
- @nhost/core@0.3.4
## 0.3.3
### Patch Changes
- correct dependencies
See this related issues:
- [nhost](https://github.com/nhost/nhost/issues/326)
- [pnpm](https://github.com/pnpm/pnpm/issues/4348)
- Updated dependencies
- @nhost/core@0.3.2
## 0.3.1 ## 0.3.1
### Patch Changes ### Patch Changes
@@ -18,7 +47,6 @@
### Patch Changes ### Patch Changes
- Updated dependencies [744fd69]
- Updated dependencies [744fd69] - Updated dependencies [744fd69]
- @nhost/core@0.3.0 - @nhost/core@0.3.0

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/apollo", "name": "@nhost/apollo",
"version": "0.3.1", "version": "0.3.7",
"description": "Nhost Apollo Client library", "description": "Nhost Apollo Client library",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -1,5 +1,34 @@
# @nhost/core # @nhost/core
## 0.3.8
### Patch Changes
- 058956b: Add missing provider types
`strava`, `gitlab`, and `bitbucket` were missing from the list of providers in Typescript and are now available.
- 058956b: Add `emailVerified`, `phoneNumber`, `phoneNumberVerified`, and `activeMfaType` to User type
Some information is missing in the `User` payload (see [this issue](https://github.com/nhost/nhost/issues/306)). The above properties have been added in the Typescript `User` type and are available when using Hasura Auth versions from [this pull request](https://github.com/nhost/hasura-auth/pull/128) (tentative version number: `0.5.1`)
- 7cf875f: Export error code payloads and type
## 0.3.7
### Patch Changes
- 16a6c50: Correct autoSignIn
## 0.3.3
### Patch Changes
- correct dependencies
See this related issues:
- [nhost](https://github.com/nhost/nhost/issues/326)
- [pnpm](https://github.com/pnpm/pnpm/issues/4348)
## 0.3.1 ## 0.3.1
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/core", "name": "@nhost/core",
"version": "0.3.1", "version": "0.3.8",
"description": "Nhost core client library", "description": "Nhost core client library",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -2,6 +2,7 @@ export type { NhostClientOptions } from './client'
export { AuthClient } from './client' export { AuthClient } from './client'
export * from './constants' export * from './constants'
export { AuthClientSSR } from './coookie-client' export { AuthClientSSR } from './coookie-client'
export * from './errors'
export * from './machines' export * from './machines'
export * from './storage' export * from './storage'
export * from './types' export * from './types'

View File

@@ -71,6 +71,7 @@ export const createChangeEmailMachine = ({ backendUrl, clientUrl, interpreter }:
error: (_, { data: { error } }: any) => error error: (_, { data: { error } }: any) => error
}), }),
reportError: send((ctx) => ({ type: 'ERROR', error: ctx.error })), reportError: send((ctx) => ({ type: 'ERROR', error: ctx.error })),
// TODO change email in the main machine (context.user.email)
reportSuccess: send('SUCCESS') reportSuccess: send('SUCCESS')
}, },
guards: { guards: {

View File

@@ -634,26 +634,31 @@ export const createAuthMachine = ({
options: rewriteRedirectTo(clientUrl, options) options: rewriteRedirectTo(clientUrl, options)
}), }),
/**
* If autoSignIn is enabled, attempts to get the refreshToken from the current location's hash
* @returns
*/
autoSignIn: async () => { autoSignIn: async () => {
// TODO throwing errors is not really important as they are captured by the xstate invoker
// * Still, keep them for the moment as it needs to be tested in every environemnt e.g. nodejs, expo, react-native...
if (typeof window === 'undefined' || !window.location) if (typeof window === 'undefined' || !window.location)
throw Error('window is undefined or location does not exist') throw Error('window is undefined or location does not exist')
const { hash } = window.location const { hash } = window.location
if (!hash) return if (!hash) throw Error('No hash in window.location')
const params = new URLSearchParams(hash.slice(1)) const params = new URLSearchParams(hash.slice(1))
const refreshToken = params.get('refreshToken') const refreshToken = params.get('refreshToken')
if (refreshToken) { if (!refreshToken) throw Error('No refresh token in the location hash')
const session = await postRequest('/token', { const session = await postRequest('/token', {
refreshToken refreshToken
}) })
// * remove hash from the current url after consumming the token // * remove hash from the current url after consumming the token
// TODO remove the hash. For the moment, it is kept to avoid regression from the current SDK. // TODO remove the hash. For the moment, it is kept to avoid regression from the current SDK.
// * Then, only `refreshToken` will be in the hash, while `type` will be sent by hasura-auth as a query parameter // * Then, only `refreshToken` will be in the hash, while `type` will be sent by hasura-auth as a query parameter
// window.history.pushState({}, '', location.pathname) // window.history.pushState({}, '', location.pathname)
const channel = new BroadcastChannel('nhost') const channel = new BroadcastChannel('nhost')
// TODO broadcat session instead of token // ? broadcat session instead of token ?
channel.postMessage(refreshToken) channel.postMessage(refreshToken)
return { session } return { session }
}
}, },
importRefreshToken: async () => { importRefreshToken: async () => {
const stringExpiresAt = await clientStorageGetter(NHOST_JWT_EXPIRES_AT_KEY) const stringExpiresAt = await clientStorageGetter(NHOST_JWT_EXPIRES_AT_KEY)

View File

@@ -14,7 +14,7 @@ type RegistrationOptions = {
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
} }
type RedirectOption = { export type RedirectOption = {
redirectTo?: string redirectTo?: string
} }
@@ -26,6 +26,7 @@ export type SendVerificationEmailOptions = RedirectOption
export type DeanonymizeOptions = { email?: string; password?: string } & RegistrationOptions export type DeanonymizeOptions = { email?: string; password?: string } & RegistrationOptions
export type ProviderOptions = RegistrationOptions & RedirectOption export type ProviderOptions = RegistrationOptions & RedirectOption
// TODO share with hasura-auth
export type User = { export type User = {
id: string id: string
createdAt: string createdAt: string
@@ -37,9 +38,13 @@ export type User = {
defaultRole: string defaultRole: string
roles: string[] roles: string[]
metadata: Record<string, unknown> metadata: Record<string, unknown>
emailVerified: boolean
phoneNumber: string | null
phoneNumberVerified: boolean
activeMfaType: 'totp' | null
} }
// ! copy-paste from hasura-auth // TODO share with hasura-auth
export type NhostSession = { export type NhostSession = {
accessToken: string accessToken: string
accessTokenExpiresIn: number accessTokenExpiresIn: number
@@ -51,6 +56,7 @@ export type Mfa = {
ticket: string ticket: string
} }
// TODO share with hasura-auth
export type Provider = export type Provider =
| 'apple' | 'apple'
| 'facebook' | 'facebook'
@@ -60,3 +66,6 @@ export type Provider =
| 'spotify' | 'spotify'
| 'twitter' | 'twitter'
| 'windowslive' | 'windowslive'
| 'strava'
| 'gitlab'
| 'bitbucket'

View File

@@ -1,5 +1,46 @@
# @nhost/hasura-auth-js # @nhost/hasura-auth-js
## 1.0.9
### Patch Changes
- 058956b: Add missing provider types
`strava`, `gitlab`, and `bitbucket` were missing from the list of providers in Typescript and are now available.
- 058956b: Add `emailVerified`, `phoneNumber`, `phoneNumberVerified`, and `activeMfaType` to User type
Some information is missing in the `User` payload (see [this issue](https://github.com/nhost/nhost/issues/306)). The above properties have been added in the Typescript `User` type and are available when using Hasura Auth versions from [this pull request](https://github.com/nhost/hasura-auth/pull/128) (tentative version number: `0.5.1`)
- Updated dependencies [058956b]
- Updated dependencies [7cf875f]
- @nhost/core@0.3.8
## 1.0.8
### Patch Changes
- 6be3758: bug: Correct OAuth provider link.
## 1.0.7
### Patch Changes
- Updated dependencies [16a6c50]
- @nhost/core@0.3.4
## 1.0.4
### Patch Changes
- correct dependencies
See this related issues:
- [nhost](https://github.com/nhost/nhost/issues/326)
- [pnpm](https://github.com/pnpm/pnpm/issues/4348)
- Updated dependencies
- @nhost/core@0.3.2
## 1.0.2 ## 1.0.2
### Patch Changes ### Patch Changes
@@ -41,7 +82,6 @@
2. hasura-auth validates the link, attaches the token and redirects to the frontend 2. hasura-auth validates the link, attaches the token and redirects to the frontend
3. the sdk gets the refresh token from the url 3. the sdk gets the refresh token from the url
4. the sdk consumes the refresh token 4. the sdk consumes the refresh token
- Updated dependencies [744fd69]
- Updated dependencies [744fd69] - Updated dependencies [744fd69]
- @nhost/core@0.3.0 - @nhost/core@0.3.0

View File

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

View File

@@ -157,7 +157,7 @@ export class HasuraAuthClient {
if ('provider' in params) { if ('provider' in params) {
const { provider, options } = params const { provider, options } = params
const providerUrl = encodeQueryParameters( const providerUrl = encodeQueryParameters(
`${this.#client.backendUrl}/v1/auth/signin/provider/${provider}`, `${this.#client.backendUrl}/signin/provider/${provider}`,
rewriteRedirectTo(this.#client.clientUrl, options) rewriteRedirectTo(this.#client.clientUrl, options)
) )
if (isBrowser()) { if (isBrowser()) {

View File

@@ -1,5 +1,15 @@
import { AuthClient, StorageGetter, StorageSetter, User } from '@nhost/core' import {
AuthClient,
PasswordlessOptions,
Provider,
ProviderOptions,
RedirectOption,
SignUpOptions,
StorageGetter,
StorageSetter,
User
} from '@nhost/core'
export type { AuthClient, Provider, StorageGetter, StorageSetter, User }
export interface NhostAuthConstructorParams { export interface NhostAuthConstructorParams {
url: string url: string
refreshIntervalTime?: number refreshIntervalTime?: number
@@ -30,14 +40,7 @@ export interface ApiError {
export interface SignUpEmailPasswordParams { export interface SignUpEmailPasswordParams {
email: string email: string
password: string password: string
options?: { options?: SignUpOptions
locale?: string
allowedRoles?: string[]
defaultRole?: string
displayName?: string
redirectTo?: string
metadata?: Record<string, unknown>
}
} }
export type SignUpParams = SignUpEmailPasswordParams export type SignUpParams = SignUpEmailPasswordParams
@@ -54,55 +57,21 @@ export interface SignInEmailPasswordParams {
export interface SignInPasswordlessEmailParams { export interface SignInPasswordlessEmailParams {
email: string email: string
options?: { options?: PasswordlessOptions
locale?: string
allowedRoles?: string[]
defaultRole?: string
displayName?: string
redirectTo?: string
metadata?: Record<string, unknown>
}
} }
export interface SignInPasswordlessSmsParams { export interface SignInPasswordlessSmsParams {
phoneNumber: string phoneNumber: string
options?: { options?: PasswordlessOptions
locale?: string
allowedRoles?: string[]
defaultRole?: string
displayName?: string
redirectTo?: string
metadata?: Record<string, unknown>
}
} }
export interface SignInPasswordlessSmsOtpParams { export interface SignInPasswordlessSmsOtpParams {
phoneNumber: string phoneNumber: string
otp: string otp: string
} }
export type Provider =
| 'facebook'
| 'github'
| 'google'
| 'linkedin'
| 'spotify'
| 'discord'
| 'twitch'
| 'apple'
| 'twitter'
| 'windowslive'
export interface SignInWithProviderOptions { export interface SignInWithProviderOptions {
provider: Provider provider: Provider
options?: { options?: ProviderOptions
locale?: string
allowedRoles?: string[]
defaultRole?: string
displayName?: string
redirectTo?: string
metadata?: Record<string, unknown>
}
} }
export type SignInParams = export type SignInParams =
@@ -125,18 +94,15 @@ export interface ChangePasswordParams {
export interface SendVerificationEmailParams { export interface SendVerificationEmailParams {
email: string email: string
options?: { options?: RedirectOption
redirectTo?: string
}
} }
export interface ChangeEmailParams { export interface ChangeEmailParams {
newEmail: string newEmail: string
options?: { options?: RedirectOption
redirectTo?: string
}
} }
// TODO define type in @nhost/core
export interface DeanonymizeParams { export interface DeanonymizeParams {
signInMethod: 'email-password' | 'passwordless' signInMethod: 'email-password' | 'passwordless'
email: string email: string

View File

@@ -1,5 +1,44 @@
# @nhost/nextjs # @nhost/nextjs
## 1.0.10
### Patch Changes
- Updated dependencies [7cf875f]
- Updated dependencies [7135aee]
- Updated dependencies [587eaff]
- @nhost/react@0.5.0
- @nhost/nhost-js@1.0.9
## 1.0.9
### Patch Changes
- @nhost/nhost-js@1.0.8
- @nhost/react@0.4.7
## 1.0.8
### Patch Changes
- @nhost/nhost-js@1.0.5
- @nhost/react@0.4.4
## 1.0.5
### Patch Changes
- correct dependencies
See this related issues:
- [nhost](https://github.com/nhost/nhost/issues/326)
- [pnpm](https://github.com/pnpm/pnpm/issues/4348)
- Updated dependencies
- @nhost/nhost-js@1.0.3
- @nhost/react@0.4.2
## 1.0.3 ## 1.0.3
### Patch Changes ### Patch Changes

View File

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

View File

@@ -1,5 +1,39 @@
# @nhost/nhost-js # @nhost/nhost-js
## 1.0.9
### Patch Changes
- Updated dependencies [058956b]
- @nhost/hasura-auth-js@1.0.9
## 1.0.8
### Patch Changes
- Updated dependencies [6be3758]
- @nhost/hasura-auth-js@1.0.8
## 1.0.7
### Patch Changes
- @nhost/hasura-auth-js@1.0.5
## 1.0.4
### Patch Changes
- correct dependencies
See this related issues:
- [nhost](https://github.com/nhost/nhost/issues/326)
- [pnpm](https://github.com/pnpm/pnpm/issues/4348)
- Updated dependencies
- @nhost/hasura-auth-js@1.0.3
## 1.0.2 ## 1.0.2
### Patch Changes ### Patch Changes
@@ -32,8 +66,6 @@
### Patch Changes ### Patch Changes
- Updated dependencies [744fd69]
- Updated dependencies [744fd69]
- Updated dependencies [744fd69] - Updated dependencies [744fd69]
- @nhost/hasura-auth-js@1.0.0 - @nhost/hasura-auth-js@1.0.0
- @nhost/hasura-storage-js@0.2.0 - @nhost/hasura-storage-js@0.2.0
@@ -53,7 +85,6 @@
Some systems based on older versions of Webpack or Babel don't support the current esbuild configuration e.g, [this issue](https://github.com/nhost/nhost/issues/275). Some systems based on older versions of Webpack or Babel don't support the current esbuild configuration e.g, [this issue](https://github.com/nhost/nhost/issues/275).
- Updated dependencies [e688600] - Updated dependencies [e688600]
- Updated dependencies [8f7643a] - Updated dependencies [8f7643a]
- Updated dependencies [e688600]
- Updated dependencies [50b9d76] - Updated dependencies [50b9d76]
- @nhost/hasura-auth-js@0.1.15 - @nhost/hasura-auth-js@0.1.15
- @nhost/hasura-storage-js@0.0.12 - @nhost/hasura-storage-js@0.0.12

View File

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

View File

@@ -1,5 +1,44 @@
# @nhost/react-apollo # @nhost/react-apollo
## 4.0.10
### Patch Changes
- Updated dependencies [7cf875f]
- Updated dependencies [7135aee]
- Updated dependencies [587eaff]
- @nhost/react@0.5.0
- @nhost/apollo@0.3.7
## 4.0.9
### Patch Changes
- @nhost/react@0.4.7
- @nhost/apollo@0.3.6
## 4.0.8
### Patch Changes
- @nhost/apollo@0.3.4
- @nhost/react@0.4.4
## 4.0.5
### Patch Changes
- correct dependencies
See this related issues:
- [nhost](https://github.com/nhost/nhost/issues/326)
- [pnpm](https://github.com/pnpm/pnpm/issues/4348)
- Updated dependencies
- @nhost/apollo@0.3.2
- @nhost/react@0.4.2
## 4.0.3 ## 4.0.3
### Patch Changes ### Patch Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/react-apollo", "name": "@nhost/react-apollo",
"version": "4.0.3", "version": "4.0.10",
"description": "Nhost React Apollo client", "description": "Nhost React Apollo client",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [

View File

@@ -1,5 +1,83 @@
# @nhost/react # @nhost/react
## 0.5.0
### Minor Changes
- 7135aee: Add user and accessToken to authentication hooks
Hooks that can complete a successful authentication now have two additional `user` and `accessToken` exported states:
- `useSignInEmailPassword`
- `useSignInAnonymous`
- `useSignUpEmailPassword`
- 587eaff: Return a promise with the current context to hooks actions
It is now possible to get the result of an action. Hook handlers return the action context in a promise.
```jsx
const { signInEmailPasswordless, isError } = useSignInEmailPasswordless()
const MyComponent = () => {
return <div>
<button onClick={async () => {
const { isSuccess, isError, error } = await signInEmailPasswordless('johan@ikea.se')
if (isError) {
console.log(error)
}}}/>
{isError && <div>an error occurred</div>}
<div>
}
```
### Patch Changes
- 7cf875f: Deprecate the use of values sent as hook parameters
Although handlers parameters of authentication hooks can be given when creating the hook, it is recommended to use them when executing the handler. For instance, instead of:
```js
const { signInEmailPasswordless } = useSignInEmailPasswordless('nuno@fcporto.pt')
signInEmailPasswordless()
```
It is recommended to use the following syntax:
```js
const { signInEmailPasswordless } = useSignInEmailPasswordless()
signInEmailPasswordless('nuno@fcporto.pt')
```
No breaking change has been introduced. For instance, `useSignUpEmailPassword('szilard@brussels.be','1234', options)` will appear as deprecated but will work, while `useSignUpEmailPassword(options)` will work too.
- @nhost/nhost-js@1.0.9
## 0.4.7
### Patch Changes
- @nhost/nhost-js@1.0.8
## 0.4.6
### Patch Changes
- @nhost/nhost-js@1.0.5
## 0.4.3
### Patch Changes
- correct dependencies
See this related issues:
- [nhost](https://github.com/nhost/nhost/issues/326)
- [pnpm](https://github.com/pnpm/pnpm/issues/4348)
- Updated dependencies
- @nhost/nhost-js@1.0.3
## 0.4.1 ## 0.4.1
### Patch Changes ### Patch Changes

View File

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

View File

@@ -5,35 +5,123 @@ import {
PasswordlessOptions, PasswordlessOptions,
Provider, Provider,
ProviderOptions, ProviderOptions,
rewriteRedirectTo rewriteRedirectTo,
User
} from '@nhost/core' } from '@nhost/core'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { NhostReactContext } from '../provider' import { NhostReactContext } from '../provider'
import { useAuthenticated, useAuthInterpreter } from './common' import { ActionHookState, useAuthenticated, useAuthInterpreter } from './common'
export const useSignInEmailPassword = ( type SignInEmailPasswordHookState = ActionHookState & {
needsMfaOtp: boolean
needsEmailVerification: boolean
user: User | null
accessToken: string | null
}
type SignInEmailPasswordHandlerResult = Omit<SignInEmailPasswordHookState, 'isLoading'>
type SignInEmailPasswordHandler = {
(email: string, password: string): Promise<SignInEmailPasswordHandlerResult>
/** @deprecated */
(email?: unknown, password?: string): Promise<SignInEmailPasswordHandlerResult>
}
type SendMfaOtpHander = {
(otp: string): void
/** @deprecated */
(otp?: unknown): void
}
type SignInEmailPasswordHookResult = {
signInEmailPassword: SignInEmailPasswordHandler
sendMfaOtp: SendMfaOtpHander
} & SignInEmailPasswordHookState
type SignInEmailPasswordHook = {
(): SignInEmailPasswordHookResult
/** @deprecated */
(email?: string, password?: string, otp?: string): SignInEmailPasswordHookResult
}
export const useSignInEmailPassword: SignInEmailPasswordHook = (
stateEmail?: string, stateEmail?: string,
statePassword?: string, statePassword?: string,
stateOtp?: string stateOtp?: string
) => { ) => {
const service = useAuthInterpreter() const service = useAuthInterpreter()
const signInEmailPassword = (valueEmail?: string | unknown, valuePassword?: string | unknown) => const signInEmailPassword: SignInEmailPasswordHandler = (
service.send({ valueEmail?: string | unknown,
type: 'SIGNIN_PASSWORD', valuePassword?: string
email: typeof valueEmail === 'string' ? valueEmail : stateEmail, ) =>
password: typeof valuePassword === 'string' ? valuePassword : statePassword new Promise<SignInEmailPasswordHandlerResult>((resolve) => {
service.send({
type: 'SIGNIN_PASSWORD',
email: typeof valueEmail === 'string' ? valueEmail : stateEmail,
password: typeof valuePassword === 'string' ? valuePassword : statePassword
})
service.onTransition((state) => {
if (state.matches({ authentication: { signedOut: 'needsEmailVerification' } })) {
resolve({
accessToken: null,
error: null,
isError: false,
isSuccess: false,
needsEmailVerification: true,
needsMfaOtp: false,
user: null
})
} else if (state.matches({ authentication: { signedOut: 'needsMfa' } })) {
resolve({
accessToken: null,
error: null,
isError: false,
isSuccess: false,
needsEmailVerification: false,
needsMfaOtp: true,
user: null
})
} else if (state.matches({ authentication: { signedOut: 'failed' } })) {
resolve({
accessToken: null,
error: state.context.errors.authentication || null,
isError: true,
isSuccess: false,
needsEmailVerification: false,
needsMfaOtp: false,
user: null
})
} else if (state.matches({ authentication: 'signedIn' })) {
resolve({
accessToken: state.context.accessToken.value,
error: null,
isError: false,
isSuccess: true,
needsEmailVerification: false,
needsMfaOtp: false,
user: state.context.user
})
}
})
}) })
const sendMfaOtp = (valueOtp?: string | unknown) => {
const sendMfaOtp: SendMfaOtpHander = (valueOtp?: string | unknown) => {
service.send({ service.send({
type: 'SIGNIN_MFA_TOTP', type: 'SIGNIN_MFA_TOTP',
otp: typeof valueOtp === 'string' ? valueOtp : stateOtp otp: typeof valueOtp === 'string' ? valueOtp : stateOtp
}) })
} }
const user = useSelector(
service,
(state) => state.context.user,
(a, b) => a?.id === b?.id
)
const accessToken = useSelector(service, (state) => state.context.accessToken.value)
const error = useSelector( const error = useSelector(
service, service,
(state) => state.context.errors.authentication, (state) => state.context.errors.authentication || null,
(a, b) => a?.error === b?.error (a, b) => a?.error === b?.error
) )
const isSuccess = useAuthenticated() const isSuccess = useAuthenticated()
@@ -59,32 +147,70 @@ export const useSignInEmailPassword = (
) )
return { return {
signInEmailPassword, accessToken,
error,
isError,
isLoading, isLoading,
isSuccess, isSuccess,
needsEmailVerification, needsEmailVerification,
needsMfaOtp, needsMfaOtp,
sendMfaOtp, sendMfaOtp,
isError, signInEmailPassword,
error user
} }
} }
export const useSignInEmailPasswordless = ( type SignInEmailPasswordlessHandlerResult = Omit<ActionHookState, 'isLoading'>
stateEmail?: string, type SignInEmailPasswordlessHandler = {
stateOptions?: PasswordlessOptions (email: string, options?: PasswordlessOptions): Promise<SignInEmailPasswordlessHandlerResult>
/** @deprecated */
(email?: unknown, options?: PasswordlessOptions): Promise<SignInEmailPasswordlessHandlerResult>
}
type SignInEmailPasswordlessHookResult = {
signInEmailPasswordless: SignInEmailPasswordlessHandler
} & ActionHookState
type SignInEmailPasswordlessdHook = {
(options?: PasswordlessOptions): SignInEmailPasswordlessHookResult
/** @deprecated */
(email?: string, options?: PasswordlessOptions): SignInEmailPasswordlessHookResult
}
export const useSignInEmailPasswordless: SignInEmailPasswordlessdHook = (
a?: string | PasswordlessOptions,
b?: PasswordlessOptions
) => { ) => {
const stateEmail = typeof a === 'string' ? a : undefined
const stateOptions = typeof a === 'string' ? b : a
const service = useAuthInterpreter() const service = useAuthInterpreter()
const signInEmailPasswordless = (valueEmail?: string | unknown, valueOptions = stateOptions) =>
service.send({ const signInEmailPasswordless: SignInEmailPasswordlessHandler = (
type: 'SIGNIN_PASSWORDLESS_EMAIL', valueEmail?: string | unknown,
email: typeof valueEmail === 'string' ? valueEmail : stateEmail, valueOptions = stateOptions
options: valueOptions ) =>
new Promise<SignInEmailPasswordlessHandlerResult>((resolve) => {
service.send({
type: 'SIGNIN_PASSWORDLESS_EMAIL',
email: typeof valueEmail === 'string' ? valueEmail : stateEmail,
options: valueOptions
})
service.onTransition((state) => {
if (state.matches({ authentication: { signedOut: 'failed' } })) {
resolve({
error: state.context.errors.authentication || null,
isError: true,
isSuccess: false
})
} else if (state.matches({ authentication: { signedOut: 'needsEmailVerification' } })) {
resolve({ error: null, isError: false, isSuccess: true })
}
})
}) })
const error = useSelector( const error = useSelector(
service, service,
(state) => state.context.errors.authentication, (state) => state.context.errors.authentication || null,
(a, b) => a?.error === b?.error (a, b) => a?.error === b?.error
) )
const isLoading = const isLoading =
@@ -93,9 +219,9 @@ export const useSignInEmailPasswordless = (
const isSuccess = const isSuccess =
!!service.status && !!service.status &&
service.state.matches({ authentication: { signedOut: 'needsEmailVerification' } }) service.state.matches({ authentication: { signedOut: 'needsEmailVerification' } })
const isError = const isError =
!!service.status && service.state.matches({ authentication: { signedOut: 'failed' } }) !!service.status && service.state.matches({ authentication: { signedOut: 'failed' } })
return { signInEmailPasswordless, isLoading, isSuccess, isError, error } return { signInEmailPasswordless, isLoading, isSuccess, isError, error }
} }
@@ -115,7 +241,13 @@ export const useSignInAnonymous = () => {
const isSuccess = useAuthenticated() const isSuccess = useAuthenticated()
const isError = const isError =
!!service.status && service.state.matches({ authentication: { signedOut: 'failed' } }) !!service.status && service.state.matches({ authentication: { signedOut: 'failed' } })
return { signInAnonymous, isLoading, isSuccess, isError, error } const user = useSelector(
service,
(state) => state.context.user,
(a, b) => a?.id === b?.id
)
const accessToken = useSelector(service, (state) => state.context.accessToken.value)
return { accessToken, error, isError, isLoading, isSuccess, signInAnonymous, user }
} }
export const useProviderLink = (options?: ProviderOptions) => { export const useProviderLink = (options?: ProviderOptions) => {

View File

@@ -1,12 +1,19 @@
import { useContext, useEffect, useState } from 'react' import { useContext, useEffect, useState } from 'react'
import { InterpreterFrom } from 'xstate' import { InterpreterFrom } from 'xstate'
import { AuthMachine } from '@nhost/core' import { AuthMachine, ErrorPayload } from '@nhost/core'
import { NhostClient } from '@nhost/nhost-js' import { NhostClient } from '@nhost/nhost-js'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { NhostReactContext } from '../provider' import { NhostReactContext } from '../provider'
export type ActionHookState<T extends string = 'isSuccess'> = {
isLoading: boolean
isError: boolean
error: ErrorPayload | null
} & Record<T, boolean>
export const useNhostClient = (): NhostClient => { export const useNhostClient = (): NhostClient => {
const nhost = useContext(NhostReactContext) const nhost = useContext(NhostReactContext)
return nhost return nhost

View File

@@ -1,21 +1,55 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { SignUpOptions } from '@nhost/core' import { SignUpOptions, User } from '@nhost/core'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { useAuthenticationStatus, useAuthInterpreter } from './common' import { ActionHookState, useAuthenticationStatus, useAuthInterpreter } from './common'
export const useSignUpEmailPassword = ( type SignUpEmailPasswordHookState = ActionHookState & {
stateEmail?: string, needsEmailVerification: boolean
statePassword?: string, user: User | null
stateOptions?: SignUpOptions accessToken: string | null
}
type SignUpEmailPasswordHandlerResult = Omit<SignUpEmailPasswordHookState, 'isLoading'>
type SignUpEmailPasswordHandler = {
(
email: string,
password: string,
options?: SignUpOptions
): Promise<SignUpEmailPasswordHandlerResult>
/** @deprecated */
(
email?: unknown,
password?: string,
options?: SignUpOptions
): Promise<SignUpEmailPasswordHandlerResult>
}
type SignUpEmailPasswordHookResult = {
signUpEmailPassword: SignUpEmailPasswordHandler
} & SignUpEmailPasswordHookState
type SignUpEmailPasswordHook = {
(options?: SignUpOptions): SignUpEmailPasswordHookResult
/** @deprecated */
(email?: string, password?: string, options?: SignUpOptions): SignUpEmailPasswordHookResult
}
export const useSignUpEmailPassword: SignUpEmailPasswordHook = (
a?: string | SignUpOptions,
b?: string,
c?: SignUpOptions
) => { ) => {
const stateEmail: string | undefined = typeof a === 'string' ? a : undefined
const statePassword: string | undefined = typeof b === 'string' ? b : undefined
const stateOptions = c || (typeof a !== 'string' ? a : undefined)
const service = useAuthInterpreter() const service = useAuthInterpreter()
const isError = const isError =
!!service.status && service.state.matches({ authentication: { signedOut: 'failed' } }) !!service.status && service.state.matches({ authentication: { signedOut: 'failed' } })
const error = useSelector( const error = useSelector(
service, service,
(state) => state.context.errors.registration, (state) => state.context.errors.registration || null,
(a, b) => a?.error === b?.error (a, b) => a?.error === b?.error
) )
const { isLoading: loading, isAuthenticated: isSuccess } = useAuthenticationStatus() const { isLoading: loading, isAuthenticated: isSuccess } = useAuthenticationStatus()
@@ -24,23 +58,65 @@ export const useSignUpEmailPassword = (
!!service.status && !!service.status &&
service.state.matches({ authentication: { signedOut: 'needsEmailVerification' } }) service.state.matches({ authentication: { signedOut: 'needsEmailVerification' } })
const signUpEmailPassword = ( const signUpEmailPassword: SignUpEmailPasswordHandler = (
valueEmail?: string | unknown, valueEmail?: string | unknown,
valuePassword = statePassword, valuePassword = statePassword,
valueOptions = stateOptions valueOptions = stateOptions
) => ) =>
service.send({ new Promise<SignUpEmailPasswordHandlerResult>((resolve) => {
type: 'SIGNUP_EMAIL_PASSWORD', service.send({
email: typeof valueEmail === 'string' ? valueEmail : stateEmail, type: 'SIGNUP_EMAIL_PASSWORD',
password: valuePassword, email: typeof valueEmail === 'string' ? valueEmail : stateEmail,
options: valueOptions password: valuePassword,
options: valueOptions
})
service.onTransition((state) => {
if (state.matches({ authentication: { signedOut: 'failed' } })) {
resolve({
accessToken: null,
error: state.context.errors.registration || null,
isError: true,
isSuccess: false,
needsEmailVerification: false,
user: null
})
} else if (state.matches({ authentication: { signedOut: 'needsEmailVerification' } })) {
resolve({
accessToken: null,
error: null,
isError: false,
isSuccess: false,
needsEmailVerification: true,
user: null
})
} else if (state.matches({ authentication: 'signedIn' })) {
resolve({
accessToken: state.context.accessToken.value,
error: null,
isError: false,
isSuccess: true,
needsEmailVerification: false,
user: state.context.user
})
}
})
}) })
const user = useSelector(
service,
(state) => state.context.user,
(a, b) => a?.id === b?.id
)
const accessToken = useSelector(service, (state) => state.context.accessToken.value)
return { return {
signUpEmailPassword, accessToken,
error,
isError,
isLoading, isLoading,
isSuccess, isSuccess,
isError, needsEmailVerification,
error, signUpEmailPassword,
needsEmailVerification user
} }
} }

View File

@@ -1,4 +1,4 @@
import { useMemo } from 'react' import { useMemo, useState } from 'react'
import { import {
ChangeEmailOptions, ChangeEmailOptions,
@@ -7,64 +7,171 @@ import {
createEnableMfaMachine, createEnableMfaMachine,
createResetPasswordMachine, createResetPasswordMachine,
createSendVerificationEmailMachine, createSendVerificationEmailMachine,
ErrorPayload,
ResetPasswordOptions, ResetPasswordOptions,
SendVerificationEmailOptions SendVerificationEmailOptions
} from '@nhost/core' } from '@nhost/core'
import { useMachine, useSelector } from '@xstate/react' import { useMachine, useSelector } from '@xstate/react'
import { useAuthInterpreter, useNhostClient } from './common' import { ActionHookState, useAuthInterpreter, useNhostClient } from './common'
export const useChangeEmail = (stateEmail?: string, stateOptions?: ChangeEmailOptions) => { type ChangeEmailHookState = ActionHookState<'needsEmailVerification'>
type ChangeEmailHandlerResult = Omit<ChangeEmailHookState, 'isLoading'>
type ChangeEmailHandler = {
(email: string, options?: ChangeEmailOptions): Promise<ChangeEmailHandlerResult>
/** @deprecated */
(email?: unknown, options?: ChangeEmailOptions): Promise<ChangeEmailHandlerResult>
}
type ChangeEmailHookResult = {
changeEmail: ChangeEmailHandler
} & ChangeEmailHookState
type ChangeEmailHook = {
(options?: ChangeEmailOptions): ChangeEmailHookResult
/** @deprecated */
(email?: string, options?: ChangeEmailOptions): ChangeEmailHookResult
}
export const useChangeEmail: ChangeEmailHook = (
a?: string | ChangeEmailOptions,
b?: ChangeEmailOptions
) => {
const stateEmail = typeof a === 'string' ? a : undefined
const stateOptions = typeof a !== 'string' ? a : b
const nhost = useNhostClient() const nhost = useNhostClient()
const machine = useMemo(() => createChangeEmailMachine(nhost.auth.client), [nhost]) const machine = useMemo(() => createChangeEmailMachine(nhost.auth.client), [nhost])
const [current, send] = useMachine(machine) const [, send, service] = useMachine(machine)
const [isError, setIsError] = useState(false)
const isError = current.matches({ idle: 'error' }) const [needsEmailVerification, setNeedsEmailVerification] = useState(false)
const needsEmailVerification = current.matches({ idle: 'success' }) const [error, setError] = useState<ErrorPayload | null>(null)
const error = current.context.error const [isLoading, setIsLoading] = useState(false)
const isLoading = current.matches('requesting') const changeEmail: ChangeEmailHandler = async (
valueEmail?: string | unknown,
const changeEmail = (valueEmail?: string | unknown, valueOptions = stateOptions) => valueOptions = stateOptions
send({ ) =>
type: 'REQUEST', new Promise<ChangeEmailHandlerResult>((resolve) => {
email: typeof valueEmail === 'string' ? valueEmail : stateEmail, send({
options: valueOptions type: 'REQUEST',
email: typeof valueEmail === 'string' ? valueEmail : stateEmail,
options: valueOptions
})
service.onTransition((state) => {
if (state.matches({ idle: 'error' })) {
const error = state.context.error
setIsError(true)
setError(error)
setIsLoading(false)
resolve({ isError: true, error, needsEmailVerification })
} else if (state.matches('requesting')) {
setIsLoading(true)
} else if (state.matches({ idle: 'success' })) {
setIsError(false)
setError(null)
setIsLoading(false)
setNeedsEmailVerification(true)
resolve({ isError: false, error: null, needsEmailVerification: true })
}
})
}) })
return { changeEmail, isLoading, needsEmailVerification, isError, error } return { changeEmail, isLoading, needsEmailVerification, isError, error }
} }
export const useChangePassword = (statePassword?: string) => { type ChangePasswordHandlerResult = Omit<ActionHookState, 'isLoading'>
type ChangePasswordHandler = {
(password: string): Promise<ChangePasswordHandlerResult>
/** @deprecated */
(password?: unknown): Promise<ChangePasswordHandlerResult>
}
type ChangePasswordHookResult = {
changePassword: ChangePasswordHandler
} & ActionHookState
type ChangePasswordHook = {
(): ChangePasswordHookResult
/** @deprecated */
(email?: string): ChangePasswordHookResult
}
export const useChangePassword: ChangePasswordHook = (statePassword?: string) => {
const nhost = useNhostClient() const nhost = useNhostClient()
const machine = useMemo(() => createChangePasswordMachine(nhost.auth.client), [nhost]) const machine = useMemo(() => createChangePasswordMachine(nhost.auth.client), [nhost])
const [current, send] = useMachine(machine) const [current, send, service] = useMachine(machine)
const isError = current.matches({ idle: 'error' }) const isError = current.matches({ idle: 'error' })
const isSuccess = current.matches({ idle: 'success' }) const isSuccess = current.matches({ idle: 'success' })
const error = current.context.error const error = current.context.error
const isLoading = current.matches('requesting') const isLoading = current.matches('requesting')
const changePassword = (valuePassword?: string | unknown) => const changePassword: ChangePasswordHandler = (valuePassword?: string | unknown) =>
send({ new Promise<ChangePasswordHandlerResult>((resolve) => {
type: 'REQUEST', send({
password: typeof valuePassword === 'string' ? valuePassword : statePassword type: 'REQUEST',
password: typeof valuePassword === 'string' ? valuePassword : statePassword
})
service.onTransition((state) => {
if (state.matches({ idle: 'error' })) {
resolve({ error: state.context.error, isError: true, isSuccess: false })
} else if (state.matches({ idle: 'success' })) {
resolve({ error: null, isError: false, isSuccess: true })
}
})
}) })
return { changePassword, isLoading, isSuccess, isError, error } return { changePassword, isLoading, isSuccess, isError, error }
} }
export const useResetPassword = (stateEmail?: string, stateOptions?: ResetPasswordOptions) => { type ResetPasswordHandlerResult = Omit<ResetPasswordHookState, 'isLoading'>
type ResetPasswordHandler = {
(email: string, options?: ResetPasswordOptions): Promise<ResetPasswordHandlerResult>
/** @deprecated */
(email?: unknown, options?: ResetPasswordOptions): Promise<ResetPasswordHandlerResult>
}
type ResetPasswordHookState = ActionHookState<'isSent'>
type ResetPasswordHookResult = {
resetPassword: ResetPasswordHandler
} & ResetPasswordHookState
type ResetPasswordHook = {
(options?: ResetPasswordOptions): ResetPasswordHookResult
/** @deprecated */
(email?: string, options?: ResetPasswordOptions): ResetPasswordHookResult
}
export const useResetPassword: ResetPasswordHook = (
a?: string | ResetPasswordOptions,
b?: ResetPasswordOptions
) => {
const stateEmail = typeof a === 'string' ? a : undefined
const stateOptions = typeof a !== 'string' ? a : b
const nhost = useNhostClient() const nhost = useNhostClient()
const machine = useMemo(() => createResetPasswordMachine(nhost.auth.client), [nhost]) const machine = useMemo(() => createResetPasswordMachine(nhost.auth.client), [nhost])
const [current, send] = useMachine(machine) const [current, send, service] = useMachine(machine)
const isError = current.matches({ idle: 'error' }) const isError = current.matches({ idle: 'error' })
const isSent = current.matches({ idle: 'success' }) const isSent = current.matches({ idle: 'success' })
const error = current.context.error const error = current.context.error
const isLoading = current.matches('requesting') const isLoading = current.matches('requesting')
const resetPassword = (valueEmail?: string | unknown, valueOptions = stateOptions) => const resetPassword: ResetPasswordHandler = (
send({ valueEmail?: string | unknown,
type: 'REQUEST', valueOptions = stateOptions
email: typeof valueEmail === 'string' ? valueEmail : stateEmail, ) =>
options: valueOptions new Promise<ResetPasswordHandlerResult>((resolve) => {
send({
type: 'REQUEST',
email: typeof valueEmail === 'string' ? valueEmail : stateEmail,
options: valueOptions
})
service.onTransition((state) => {
if (state.matches({ idle: 'error' })) {
resolve({ error: state.context.error, isError: true, isSent: false })
} else if (state.matches({ idle: 'success' })) {
resolve({ error: null, isError: false, isSent: true })
}
})
}) })
return { resetPassword, isLoading, isSent, isError, error } return { resetPassword, isLoading, isSent, isError, error }
} }
@@ -150,37 +257,103 @@ export const useUserRoles = () => {
) )
} }
export const useSendVerificationEmail = ( type SendVerificationEmailHandlerResult = Omit<SendVerificationEmailHookState, 'isLoading'>
stateEmail?: string, type SendVerificationEmailHandler = {
stateOptions?: SendVerificationEmailOptions (
email: string,
options?: SendVerificationEmailOptions
): Promise<SendVerificationEmailHandlerResult>
/** @deprecated */
(
email?: unknown,
options?: SendVerificationEmailOptions
): Promise<SendVerificationEmailHandlerResult>
}
type SendVerificationEmailHookState = ActionHookState<'isSent'>
type SendVerificationEmailHookResult = {
sendEmail: SendVerificationEmailHandler
} & SendVerificationEmailHookState
type SendVerificationEmailHook = {
(options?: SendVerificationEmailOptions): SendVerificationEmailHookResult
/** @deprecated */
(email?: string, options?: SendVerificationEmailOptions): SendVerificationEmailHookResult
}
export const useSendVerificationEmail: SendVerificationEmailHook = (
a?: string | SendVerificationEmailOptions,
b?: SendVerificationEmailOptions
) => { ) => {
const stateEmail = typeof a === 'string' ? a : undefined
const stateOptions = typeof a !== 'string' ? a : b
const nhost = useNhostClient() const nhost = useNhostClient()
const machine = useMemo(() => createSendVerificationEmailMachine(nhost.auth.client), [nhost]) const machine = useMemo(() => createSendVerificationEmailMachine(nhost.auth.client), [nhost])
const [current, send] = useMachine(machine) const [current, send, service] = useMachine(machine)
const isError = current.matches({ idle: 'error' }) const isError = current.matches({ idle: 'error' })
const isSent = current.matches({ idle: 'success' }) const isSent = current.matches({ idle: 'success' })
const error = current.context.error const error = current.context.error
const isLoading = current.matches('requesting') const isLoading = current.matches('requesting')
const sendEmail = (valueEmail?: string | unknown, valueOptions = stateOptions) => const sendEmail: SendVerificationEmailHandler = (
send({ valueEmail?: string | unknown,
type: 'REQUEST', valueOptions = stateOptions
email: typeof valueEmail === 'string' ? valueEmail : stateEmail, ) =>
options: valueOptions new Promise<SendVerificationEmailHandlerResult>((resolve) => {
send({
type: 'REQUEST',
email: typeof valueEmail === 'string' ? valueEmail : stateEmail,
options: valueOptions
})
service.onTransition((state) => {
if (state.matches({ idle: 'error' })) {
resolve({ error: state.context.error, isError: true, isSent: false })
} else if (state.matches({ idle: 'success' })) {
resolve({ error: null, isError: false, isSent: true })
}
})
}) })
return { sendEmail, isLoading, isSent, isError, error } return { sendEmail, isLoading, isSent, isError, error }
} }
type ActivateMfaHookState = {
isActivating: boolean
isActivated: boolean
isError: boolean
error: ErrorPayload | null
}
type GenerateQrCodeHookState = {
qrCodeDataUrl: string
isGenerating: boolean
isGenerated: boolean
isError: boolean
error: ErrorPayload | null
}
type ActivateMfaHandlerResult = Omit<ActivateMfaHookState, 'isActivating'>
type ActivateMfaHandler = (code: string) => Promise<ActivateMfaHandlerResult>
type GenerateQrCodeHandlerResult = Omit<GenerateQrCodeHookState, 'isGenerating'>
type GenerateQrCodeHandler = () => Promise<GenerateQrCodeHandlerResult>
type ConfigMfaHookState = ActivateMfaHookState &
GenerateQrCodeHookState & {
generateQrCode: GenerateQrCodeHandler
activateMfa: ActivateMfaHandler
}
type ConfigMfaHook = () => ConfigMfaHookState
// TODO documentation when available in Nhost Cloud - see changelog // TODO documentation when available in Nhost Cloud - see changelog
export const useConfigMfa = (stateCode?: string) => { export const useConfigMfa: ConfigMfaHook = () => {
const nhost = useNhostClient() const nhost = useNhostClient()
const machine = useMemo(() => createEnableMfaMachine(nhost.auth.client), [nhost]) const machine = useMemo(() => createEnableMfaMachine(nhost.auth.client), [nhost])
const [current, send] = useMachine(machine) const [current, send, service] = useMachine(machine)
const isError = useMemo(() => { const isError = useMemo(
current.matches({ idle: 'error' }) || current.matches({ generated: { idle: 'error' } }) () => current.matches({ idle: 'error' }) || current.matches({ generated: { idle: 'error' } }),
}, [current]) [current]
)
const isGenerating = current.matches('generating') const isGenerating = current.matches('generating')
const isGenerated = current.matches('generated') const isGenerated = current.matches('generated')
const isActivating = current.matches({ generated: 'activating' }) const isActivating = current.matches({ generated: 'activating' })
@@ -188,12 +361,41 @@ export const useConfigMfa = (stateCode?: string) => {
const error = current.context.error const error = current.context.error
const qrCodeDataUrl = current.context.imageUrl || '' const qrCodeDataUrl = current.context.imageUrl || ''
const generateQrCode = () => send('GENERATE') const generateQrCode: GenerateQrCodeHandler = () =>
const activateMfa = (valueCode?: string | unknown) => new Promise<GenerateQrCodeHandlerResult>((resolve) => {
send({ send('GENERATE')
type: 'ACTIVATE', service.onTransition((state) => {
activeMfaType: 'totp', if (state.matches('generated')) {
code: typeof valueCode === 'string' ? valueCode : stateCode resolve({
error: null,
isError: false,
isGenerated: true,
qrCodeDataUrl: state.context.imageUrl || ''
})
} else if (state.matches({ idle: 'error' })) {
resolve({
error: state.context.error || null,
isError: true,
isGenerated: false,
qrCodeDataUrl: ''
})
}
})
})
const activateMfa: ActivateMfaHandler = (code: string) =>
new Promise<ActivateMfaHandlerResult>((resolve) => {
send({
type: 'ACTIVATE',
activeMfaType: 'totp',
code
})
service.onTransition((state) => {
if (state.matches({ generated: 'activated' })) {
resolve({ error: null, isActivated: true, isError: false })
} else if (state.matches({ generated: { idle: 'error' } })) {
resolve({ error: state.context.error, isActivated: false, isError: true })
}
})
}) })
return { return {
generateQrCode, generateQrCode,