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'; diff --git a/src/index.tsx b/src/index.tsx index 681214f..cc714d9 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,29 +52,33 @@ 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; loginProvider: (provider: Provider) => void; - acceptInviteExternalUrl: (provider: Provider) => string; + acceptInviteExternalUrl: ( + provider: Provider, + autoRedirect: boolean + ) => string | undefined; settings: Settings; param: TokenParam; + verifyToken: () => Promise; }; const [_useIdentityContext, _IdentityCtxProvider] = createCtx< @@ -179,20 +181,42 @@ 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); + console.error(errors.tokenMissingOrInvalid); + return; } const url = goTrueInstance.acceptInviteExternalUrl(provider, param.token); // clean up consumed token setParam(defaultParam); + if (autoRedirect) { + window.location.href = url; + return; + } + return url; }, [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 @@ -237,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 @@ -259,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! @@ -274,7 +298,7 @@ export function useNetlifyIdentity( */ const getFreshJWT = useCallback(() => { if (!user) { - throw new Error(errors.noUserFound); + return Promise.reject(errors.noUserFound); } return user.jwt(); @@ -285,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.noUserTokenFound); } const defaultObj = { @@ -341,6 +365,7 @@ export function useNetlifyIdentity( acceptInviteExternalUrl, settings, param, + verifyToken, }; } 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)) {