diff --git a/.changeset/big-squids-hug.md b/.changeset/big-squids-hug.md new file mode 100644 index 0000000..4fa6977 --- /dev/null +++ b/.changeset/big-squids-hug.md @@ -0,0 +1,5 @@ +--- +"@marzee/react-auth-amplify": patch +--- + +v1.0.3 diff --git a/packages/react-auth-amplify/README.md b/packages/react-auth-amplify/README.md index 1e11745..496398f 100644 --- a/packages/react-auth-amplify/README.md +++ b/packages/react-auth-amplify/README.md @@ -6,20 +6,32 @@ ## Usage +1. Create a file called `lib/auth.ts` and export the following object: + +```ts +import { type AmplifyAuthConfig as Config } from "@marzee/react-auth-amplify"; +export const amplifyAuthConfig: Config = { + Auth: { + identityPoolId: process.env.IDENTITY_POOL_ID, + region: process.env.REGION, + userPoolId: process.env.USER_POOL_ID, + userPoolWebClientId: process.env.USER_POOL_WEB_CLIENT_ID + } + ssr: true // only if you plan on using SSR (with Next.js), otherwise simply omit it. +}; + +``` + 1. Wrap your app using `AuthProvider` ```tsx -import { AuthProvider } from "@marzeelabs/react-auth-amplify" +import { AuthProvider } from "@marzeelabs/react-auth-amplify"; +import { amplifyAuthConfig } from "lib/auth"; export function AppWrapper() { return ( { // do what you want after the signIn event has been fired (e.g., redirect to a page) @@ -31,7 +43,7 @@ export function AppWrapper() { } ``` -1. Use the `signIn/signOut/etc...` functions in combination with your components to control the authentication flow (see [API](#api) section below for all available functions) +3. Use the `signIn/signOut/etc...` functions in combination with your components to control the authentication flow (see [API](#api) section below for all available functions) ```tsx import { useForm } from 'react-hook-form'; @@ -64,7 +76,7 @@ export const SignInComponent = () => { } ``` -3. Get the current user using the `useAuthContext` hook +4. Get the current user using the `useAuthContext` hook ```tsx import { useAuthContext } from '@marzeelabs/react-auth-amplify'; @@ -77,7 +89,7 @@ export const Component = () => { ``` -4. If you want to extend the type of the `currentUser` you can do so by using module augmentation +5. If you want to extend the type of the `currentUser` you can do so by using module augmentation ```ts declare module '@marzeelabs/react-auth-amplify' { @@ -87,32 +99,25 @@ declare module '@marzeelabs/react-auth-amplify' { } ``` -5. If you to use this package in Next.js: - 1. you need to set the `ssr` option to `true` in the `AuthProvider` component (see below) +6. If you to use this package in Next.js: + 1. you need to set the `ssr` option to `true` in the `lib/auth.ts` file (see below) 2. Use the `getServerAuth` function to get the current user in the `getServerSideProps` function of your page ```tsx -import { AuthProvider } from "@marzeelabs/react-auth-amplify" - -export function AppWrapper() { - return ( - { - // do what you want after the signIn event has been fired (e.g., redirect to a page) - } - }}> - - - ) -} +// your other imports... +import { amplifyAuthConfig } from "lib/auth"; + +export const getServerSideProps: GetServerSideProps = async (ctx) => { + const currentUser = await getServerAuth({ + req: ctx.req, + config: amplifyAuthConfig + }); + return { + props: {} + }; +}; + +// the rest of your page... ``` ## API @@ -140,6 +145,7 @@ The following functions are available: | `updateUserAttributes` | Updates the current user's attributes in AWS Cognito using the provided partial user attributes. | | `deleteUserAttributes` | Deletes the specified attributes from the current user's attributes in AWS Cognito. | | `deleteUser` | Deletes the current user from AWS Cognito. | +| `getServerAuth` | Returns the currently authenticated user in an SSR context. | ## Install diff --git a/packages/react-auth-amplify/package.json b/packages/react-auth-amplify/package.json index 5700637..6cba942 100644 --- a/packages/react-auth-amplify/package.json +++ b/packages/react-auth-amplify/package.json @@ -1,6 +1,6 @@ { "name": "@marzee/react-auth-amplify", - "version": "1.0.2", + "version": "1.0.3", "description": "A React.js-Amplify Auth integration", "repository": { "type": "git", diff --git a/packages/react-auth-amplify/src/index.ts b/packages/react-auth-amplify/src/index.ts index 1b4d903..bc7024d 100644 --- a/packages/react-auth-amplify/src/index.ts +++ b/packages/react-auth-amplify/src/index.ts @@ -6,11 +6,12 @@ export { deleteUser, deleteUserAttributes, forgotPassword, - getServerAuth, signIn, signOut, signUp, updateUserAttributes, type UserAttributes } from './lib/functions'; +export { getServerAuth } from './lib/next'; export * from './react/provider'; +export type { AmplifyAuthConfig } from './types/amplify'; diff --git a/packages/react-auth-amplify/src/lib/functions.ts b/packages/react-auth-amplify/src/lib/functions.ts index ecb5ef0..f2a37ea 100644 --- a/packages/react-auth-amplify/src/lib/functions.ts +++ b/packages/react-auth-amplify/src/lib/functions.ts @@ -1,5 +1,4 @@ -import { Auth, withSSRContext } from 'aws-amplify'; -import type { GetServerSidePropsContext } from 'next'; +import { Auth } from 'aws-amplify'; type CognitoDefaults = { /** @@ -28,7 +27,7 @@ type DefaultUserAttributes = CognitoDefaults & { */ export interface UserAttributes extends DefaultUserAttributes {} -type FunctionOutput = +export type FunctionOutput = | { status: 'SUCCESS'; data: TData; @@ -348,15 +347,14 @@ export type CognitoUser = Record & { type CurrentAuthenticatedUserReturn = CognitoUser | undefined | null; /** * @internal This function returns the currently authenticated user. It is only meant for internal use. + * @param auth This is the Auth object that is returned from `withSSRContext`. */ -export async function getCurrentUser( - auth?: typeof Auth -): Promise> { +export async function getCurrentUser(): Promise< + FunctionOutput +> { try { - const authClass = auth ?? Auth; - const getCurrentAuthenticatedUser = authClass.currentAuthenticatedUser; const res: CurrentAuthenticatedUserReturn = - await getCurrentAuthenticatedUser(); + await Auth.currentAuthenticatedUser(); return { status: 'SUCCESS', data: res, @@ -451,12 +449,3 @@ export async function deleteUser(): Promise< }; } } - -/** - * @description This is a next.js specific option, it is to be used ONLY inside `getServerSideProps` - */ -export async function getServerAuth(ctx: GetServerSidePropsContext) { - const amplify = withSSRContext({ req: ctx.req }); - const auth: typeof Auth = amplify.Auth; - return await getCurrentUser(auth); -} diff --git a/packages/react-auth-amplify/src/lib/next.ts b/packages/react-auth-amplify/src/lib/next.ts new file mode 100644 index 0000000..55245d3 --- /dev/null +++ b/packages/react-auth-amplify/src/lib/next.ts @@ -0,0 +1,44 @@ +import { withSSRContext, type Auth } from 'aws-amplify'; +import type { GetServerSidePropsContext } from 'next'; +import type { AmplifyAuthConfig } from '../types/amplify'; +import { type FunctionOutput } from './functions'; + +type CognitoUserSSR = { + username: string; +} & Record; + +type CurrentAuthenticatedUserReturnSSR = CognitoUserSSR | undefined | null; + +type Params = { + req: GetServerSidePropsContext['req']; + config: AmplifyAuthConfig; +}; +/** + * @summary This is a next.js specific option, it is to be used ONLY inside `getServerSideProps` + * @description In SSR, Amplify creates a new instance scoped/per-request, and as such you must auth config every time you want to get the current user on the server side. + * @see https://docs.amplify.aws/lib/ssr/q/platform/js/#withssrcontext + */ +export async function getServerAuth({ + req, + config +}: Params): Promise> { + if (!config) throw new Error('No config provided for getServerAuth'); + const Amplify = withSSRContext({ req }); + Amplify.configure(config); + const auth: typeof Auth = Amplify.Auth; + try { + const data: CurrentAuthenticatedUserReturnSSR = + await auth.currentAuthenticatedUser(); + return { + status: 'SUCCESS', + data, + err: null + }; + } catch (err) { + return { + status: 'ERROR', + data: null, + err + }; + } +} diff --git a/packages/react-auth-amplify/src/lib/setup.ts b/packages/react-auth-amplify/src/lib/setup.ts index 2d2e3b6..7d68bf5 100644 --- a/packages/react-auth-amplify/src/lib/setup.ts +++ b/packages/react-auth-amplify/src/lib/setup.ts @@ -1,53 +1,13 @@ import { Amplify } from 'aws-amplify'; import { useEffect } from 'react'; -import type { AWSAmplifyConfig } from '../types/amplify'; - -type CommonProps = { - identityPoolId: string; - region: string; - userPoolId: string; - userPoolWebClientId: string; - /** - * This is a next.js specific option - * @default false - */ - ssr?: boolean; -}; - -type PropsWithSSR = CommonProps & { - /** - * @summary This should be set to the domain of the app (optional) - * @description You should set this to the (production) domain of the app. Note: You only need to set this on production env, not on development/localhost. - * @example '.example.com' - * @see https://docs.amplify.aws/lib/auth/getting-started/q/platform/js/#set-up-and-configure-amplify-auth - */ - cookieDomain?: string; - ssr: true; -}; - -type PropsWithoutSSR = CommonProps & { - cookieDomain?: undefined; - ssr?: false; -}; - -export type SetupProps = PropsWithSSR | PropsWithoutSSR; +import type { AmplifyAuthConfig } from '../types/amplify'; /** * @summary This is a hook that sets up Amplify */ -export function useSetupAmplify({ cookieDomain, ...props }: SetupProps) { +export function useSetupAmplify(config: AmplifyAuthConfig) { useEffect(() => { - Amplify.configure({ - Auth: { - ...props, - cookieStorage: { - domain: cookieDomain, - /** - * Although Chrome/FF allow cookies to be set on localhost, Safari does not. - */ - secure: process.env.NODE_ENV === 'production' - } - } - } satisfies AWSAmplifyConfig); + if (!config) throw new Error('Missing Amplify config for client-side'); + Amplify.configure(config); }, []); } diff --git a/packages/react-auth-amplify/src/react/provider.tsx b/packages/react-auth-amplify/src/react/provider.tsx index 74cd1bd..9b48b07 100644 --- a/packages/react-auth-amplify/src/react/provider.tsx +++ b/packages/react-auth-amplify/src/react/provider.tsx @@ -1,8 +1,12 @@ import { Hub } from 'aws-amplify'; import React from 'react'; import { getCurrentUser, type CognitoUser } from '../lib/functions'; -import { useSetupAmplify, type SetupProps } from '../lib/setup'; -import type { AuthEventType, HubEventHandler } from '../types/amplify'; +import { useSetupAmplify } from '../lib/setup'; +import type { + AmplifyAuthConfig, + AuthEventType, + HubEventHandler +} from '../types/amplify'; type CurrentUser = CognitoUser['attributes']; @@ -32,13 +36,13 @@ type AuthProviderProps = { userPoolWebClientId: process.env.USER_POOL_WEB_CLIENT_ID } */ - setup: SetupProps; + config: AmplifyAuthConfig; } & Partial; type Props = React.PropsWithChildren; -export function AuthProvider({ children, events, setup }: Props) { - useSetupAmplify(setup); +export function AuthProvider({ children, events, config }: Props) { + useSetupAmplify(config); const [currentUser, setCurrentUser] = React.useState( null diff --git a/packages/react-auth-amplify/src/types/amplify.ts b/packages/react-auth-amplify/src/types/amplify.ts index ea52de6..a90eef1 100644 --- a/packages/react-auth-amplify/src/types/amplify.ts +++ b/packages/react-auth-amplify/src/types/amplify.ts @@ -11,7 +11,7 @@ type AuthConfig = ReturnType; /** * Configuration for AWS Amplify. */ -export type AWSAmplifyConfig = { +export type AmplifyAuthConfig = { /** * Configuration for AWS Amplify Auth. */