From 76ddacd503633cc362646b7c820856ede34cb63e Mon Sep 17 00:00:00 2001 From: Gerrit Alex Date: Fri, 17 Jan 2020 22:07:36 +0100 Subject: [PATCH 1/5] fix(acceptInviteExternalUrl): add autoRedirect flag & redirect if true --- src/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 681214f..5cbe3be 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -74,7 +74,10 @@ export type ReactNetlifyIdentityAPI = { _goTrueInstance: GoTrue; _url: string; loginProvider: (provider: Provider) => void; - acceptInviteExternalUrl: (provider: Provider) => string; + acceptInviteExternalUrl: ( + provider: Provider, + autoRedirect: boolean + ) => void | string; settings: Settings; param: TokenParam; }; @@ -179,7 +182,7 @@ export function useNetlifyIdentity( * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L92 */ const acceptInviteExternalUrl = useCallback( - (provider: Provider) => { + (provider: Provider, autoRedirect: boolean = true) => { if (!param.token || param.type !== 'invite') { throw new Error(errors.tokenMissingOrInvalid); } @@ -188,6 +191,11 @@ export function useNetlifyIdentity( // clean up consumed token setParam(defaultParam); + if (autoRedirect) { + window.location.href = url; + return; + } + return url; }, [goTrueInstance, param] From b4caa6fe87f4f57efd1d85868786b321d51887b3 Mon Sep 17 00:00:00 2001 From: Gerrit Alex Date: Sat, 18 Jan 2020 00:57:55 +0100 Subject: [PATCH 2/5] feat(verifyToken): add & expose verifyToken reject instead of error --- src/index.tsx | 57 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 5cbe3be..88274e4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -41,8 +41,6 @@ const errors = { tokenMissingOrInvalid: 'either no token found or invalid for this purpose', }; -type MaybeUserPromise = Promise; - export type ReactNetlifyIdentityAPI = { user: User | undefined; /** not meant for normal use! you should mostly use one of the other exported methods to update the user instance */ @@ -54,22 +52,22 @@ export type ReactNetlifyIdentityAPI = { password: string, data: Object, directLogin: boolean - ) => MaybeUserPromise; + ) => Promise; loginUser: ( email: string, password: string, remember?: boolean - ) => MaybeUserPromise; - logoutUser: () => MaybeUserPromise; + ) => Promise; + logoutUser: () => Promise; requestPasswordRecovery: (email: string) => Promise; - recoverAccount: (remember?: boolean) => MaybeUserPromise; - updateUser: (fields: object) => MaybeUserPromise; - getFreshJWT: () => Promise; + recoverAccount: (remember?: boolean) => Promise; + updateUser: (fields: object) => Promise; + getFreshJWT: () => Promise | undefined; authedFetch: { - get: (endpoint: string, obj?: {}) => Promise; - post: (endpoint: string, obj?: {}) => Promise; - put: (endpoint: string, obj?: {}) => Promise; - delete: (endpoint: string, obj?: {}) => Promise; + get: (endpoint: string, obj?: RequestInit) => Promise; + post: (endpoint: string, obj?: RequestInit) => Promise; + put: (endpoint: string, obj?: RequestInit) => Promise; + delete: (endpoint: string, obj?: RequestInit) => Promise; }; _goTrueInstance: GoTrue; _url: string; @@ -77,9 +75,10 @@ export type ReactNetlifyIdentityAPI = { acceptInviteExternalUrl: ( provider: Provider, autoRedirect: boolean - ) => void | string; + ) => string | undefined; settings: Settings; param: TokenParam; + verifyToken: () => Promise; }; const [_useIdentityContext, _IdentityCtxProvider] = createCtx< @@ -184,7 +183,8 @@ export function useNetlifyIdentity( const acceptInviteExternalUrl = useCallback( (provider: Provider, autoRedirect: boolean = true) => { if (!param.token || param.type !== 'invite') { - throw new Error(errors.tokenMissingOrInvalid); + console.error(errors.tokenMissingOrInvalid); + return; } const url = goTrueInstance.acceptInviteExternalUrl(provider, param.token); @@ -201,6 +201,22 @@ export function useNetlifyIdentity( [goTrueInstance, param] ); + /** + * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L123 + */ + const verifyToken = useCallback(() => { + if (!param.type || !param.token) { + return Promise.reject(errors.tokenMissingOrInvalid); + } + + return goTrueInstance.verify(param.type, param.token).then(user => { + // cleanup consumed token + setParam(defaultParam); + + return user; + }); + }, [goTrueInstance, param]); + /******* email auth */ /** * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L50 @@ -245,7 +261,7 @@ export function useNetlifyIdentity( const recoverAccount = useCallback( (remember?: boolean) => { if (!param.token || param.type !== 'recovery') { - throw new Error(errors.tokenMissingOrInvalid); + return Promise.reject(errors.tokenMissingOrInvalid); } return goTrueInstance @@ -267,7 +283,7 @@ export function useNetlifyIdentity( const updateUser = useCallback( (fields: object) => { if (!user) { - throw new Error(errors.noUserFound); + return Promise.reject(errors.noUserFound); } return user! @@ -282,7 +298,7 @@ export function useNetlifyIdentity( */ const getFreshJWT = useCallback(() => { if (!user) { - throw new Error(errors.noUserFound); + return Promise.reject(errors.noUserFound); } return user.jwt(); @@ -293,18 +309,18 @@ export function useNetlifyIdentity( */ const logoutUser = useCallback(() => { if (!user) { - throw new Error(errors.noUserFound); + return Promise.reject(errors.noUserFound); } return user.logout().then(() => _setUser(undefined)); }, [user]); - const genericAuthedFetch = (method: string) => ( + const genericAuthedFetch = (method: RequestInit['method']) => ( endpoint: string, options: RequestInit = {} ) => { if (!user?.token?.access_token) { - throw new Error(errors.noUserTokenFound); + return Promise.reject(errors.noUserFound); } const defaultObj = { @@ -349,6 +365,7 @@ export function useNetlifyIdentity( acceptInviteExternalUrl, settings, param, + verifyToken, }; } From 7418a2afea3a15dcc40372c893e42acb6f127e94 Mon Sep 17 00:00:00 2001 From: Gerrit Alex Date: Sat, 18 Jan 2020 01:06:27 +0100 Subject: [PATCH 3/5] chore(readme): update --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 00219d7..abab0ed 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,16 @@ yarn add react-netlify-identity - `updateUser(fields: object)`: see [updateUser @ gotrue-js](https://github.com/netlify/gotrue-js#update-a-user) - `getFreshJWT()` - `authedFetch(endpoint: string, obj: RequestInit = {})` a thin axios-like wrapper over `fetch` that has the user's JWT attached, for convenience pinging Netlify Functions with Netlify Identity +- `recoverAccount(remember?: boolean)`: verifies and consumes the recovery token caught by `runRoutes`, sets user on success - `param: TokenParam` - a token exposing Netlify tokens a dev has to implement the actions for; namely `invite`, `recovery`, `email_change` and `access_denied` - - **Important:** tokens this package exposes no methods for are automatically handled and will not be passed down - see [runRoutes implementation](https://github.com/sw-yx/react-netlify-identity/master/src/runRoutes.tsx) + - **important:** tokens this package exposes no methods for are automatically handled and will not be passed down - see [runRoutes implementation](https://github.com/sw-yx/react-netlify-identity/master/src/runRoutes.tsx) - if you don't want this behaviour (added [here](https://github.com/sw-yx/react-netlify-identity/issues/12) in v.0.1.8), pass `runRoutes={false}` to the exposed hook - for further reference, please check the [type definition](https://github.com/sw-yx/react-netlify-identity/tree/master/src/token.ts) - an example implementation for a Recovery process can be found below -- `recoverAccount(remember?: boolean)`: verifies and consumes the recovery token caught by `runRoutes`, sets user on success +- `verifyToken()` + - consumes & verifies TokenParam based on the type and tries to retrieve a valid user object + - devs duty to show password field to afterwards call `signupUser(user.email, newPassword)` ```tsx import React from 'react'; From 0033b604f441e54b65037dfac8c733c9918eee42 Mon Sep 17 00:00:00 2001 From: Gerrit Alex Date: Sat, 18 Jan 2020 01:06:42 +0100 Subject: [PATCH 4/5] fix(hash): remove entirely --- src/runRoutes.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/runRoutes.tsx b/src/runRoutes.tsx index c29b49c..e45fe32 100644 --- a/src/runRoutes.tsx +++ b/src/runRoutes.tsx @@ -32,9 +32,15 @@ export function runRoutes( const hash = document.location.hash.replace(hashReplace, ''); - // todo: maybe replace with history.replaceState to completely clear the url? - // currently keeps # - document.location.hash = ''; + try { + history.pushState( + '', + document.title, + window.location.pathname + window.location.search + ); + } catch (_) { + window.location.href.substr(0, window.location.href.indexOf('#')); + } // earliest possible bail on any match if (hash.match(errorRoute)) { From 1859df6118771ceeb0d10af4c267bc1b08556375 Mon Sep 17 00:00:00 2001 From: Gerrit Alex Date: Sat, 18 Jan 2020 01:09:12 +0100 Subject: [PATCH 5/5] fix(error): correct error --- src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 88274e4..cc714d9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -320,7 +320,7 @@ export function useNetlifyIdentity( options: RequestInit = {} ) => { if (!user?.token?.access_token) { - return Promise.reject(errors.noUserFound); + return Promise.reject(errors.noUserTokenFound); } const defaultObj = {