@ttoss/react-auth 1.6.42 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,12 @@
11
11
  "value": "Required field"
12
12
  }
13
13
  ],
14
+ "42HafR": [
15
+ {
16
+ "type": 0,
17
+ "value": "Code"
18
+ }
19
+ ],
14
20
  "5E12mO": [
15
21
  {
16
22
  "type": 0,
package/i18n/lang/en.json CHANGED
@@ -7,6 +7,10 @@
7
7
  "defaultMessage": "Required field",
8
8
  "description": "Required field."
9
9
  },
10
+ "42HafR": {
11
+ "defaultMessage": "Code",
12
+ "description": "Code"
13
+ },
10
14
  "5E12mO": {
11
15
  "defaultMessage": "Email",
12
16
  "description": "Email label."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/react-auth",
3
- "version": "1.6.42",
3
+ "version": "1.7.0",
4
4
  "description": "ttoss authentication module for React apps.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "ttoss",
package/src/Auth.tsx CHANGED
@@ -1,6 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { Auth as AmplifyAuth } from 'aws-amplify';
3
3
  import { AuthConfirmSignUp } from './AuthConfirmSignUp';
4
+ import { AuthForgotPassword } from './AuthForgotPassword';
5
+ import { AuthForgotPasswordResetPassword } from './AuthForgotPasswordResetPassword';
4
6
  import { AuthFullScreen } from './AuthFullScreen';
5
7
  import { AuthSignIn } from './AuthSignIn';
6
8
  import { AuthSignUp } from './AuthSignUp';
@@ -9,7 +11,13 @@ import { assign, createMachine } from 'xstate';
9
11
  import { useAuth } from './AuthProvider';
10
12
  import { useMachine } from '@xstate/react';
11
13
  import { useNotifications } from '@ttoss/react-notifications';
12
- import type { OnConfirmSignUp, OnSignIn, OnSignUp } from './types';
14
+ import type {
15
+ OnConfirmSignUp,
16
+ OnForgotPassword,
17
+ OnForgotPasswordResetPassword,
18
+ OnSignIn,
19
+ OnSignUp,
20
+ } from './types';
13
21
 
14
22
  type AuthState =
15
23
  | {
@@ -27,6 +35,14 @@ type AuthState =
27
35
  | {
28
36
  value: 'signUpResendConfirmation';
29
37
  context: { email: string };
38
+ }
39
+ | {
40
+ value: 'forgotPassword';
41
+ context: Record<string, never>;
42
+ }
43
+ | {
44
+ value: 'forgotPasswordResetPassword';
45
+ context: { email: string };
30
46
  };
31
47
 
32
48
  type AuthEvent =
@@ -34,7 +50,10 @@ type AuthEvent =
34
50
  | { type: 'SIGN_UP_CONFIRM'; email: string }
35
51
  | { type: 'SIGN_UP_CONFIRMED'; email: string }
36
52
  | { type: 'SIGN_UP_RESEND_CONFIRMATION'; email: string }
37
- | { type: 'RETURN_TO_SIGN_IN' };
53
+ | { type: 'RETURN_TO_SIGN_IN' }
54
+ | { type: 'FORGOT_PASSWORD' }
55
+ | { type: 'FORGOT_PASSWORD_RESET_PASSWORD'; email: string }
56
+ | { type: 'FORGOT_PASSWORD_CONFIRMED'; email: string };
38
57
 
39
58
  type AuthContext = { email?: string };
40
59
 
@@ -50,6 +69,7 @@ const authMachine = createMachine<AuthContext, AuthEvent, AuthState>(
50
69
  actions: ['assignEmail'],
51
70
  target: 'signUpConfirm',
52
71
  },
72
+ FORGOT_PASSWORD: { target: 'forgotPassword' },
53
73
  },
54
74
  },
55
75
  signUp: {
@@ -69,13 +89,36 @@ const authMachine = createMachine<AuthContext, AuthEvent, AuthState>(
69
89
  },
70
90
  },
71
91
  },
92
+ forgotPassword: {
93
+ on: {
94
+ RETURN_TO_SIGN_IN: { target: 'signIn' },
95
+ SIGN_UP: { target: 'signUp' },
96
+ FORGOT_PASSWORD_RESET_PASSWORD: {
97
+ actions: ['assignEmail'],
98
+ target: 'forgotPasswordResetPassword',
99
+ },
100
+ },
101
+ },
102
+ forgotPasswordResetPassword: {
103
+ on: {
104
+ FORGOT_PASSWORD_CONFIRMED: {
105
+ actions: ['assignEmail'],
106
+ target: 'signIn',
107
+ },
108
+ RETURN_TO_SIGN_IN: { target: 'signIn' },
109
+ },
110
+ },
72
111
  },
73
112
  },
74
113
  {
75
114
  actions: {
76
115
  assignEmail: assign({
77
116
  email: (_, event) => {
78
- return (event as any).email;
117
+ if ('email' in event) {
118
+ return event.email;
119
+ }
120
+
121
+ return undefined;
79
122
  },
80
123
  }),
81
124
  },
@@ -95,11 +138,12 @@ const AuthLogic = () => {
95
138
  setLoading(true);
96
139
  await AmplifyAuth.signIn(email, password);
97
140
  // toast('Signed In');
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
142
  } catch (error: any) {
99
143
  switch (error.code) {
100
144
  case 'UserNotConfirmedException':
101
145
  await AmplifyAuth.resendSignUp(email);
102
- send({ type: 'SIGN_UP_RESEND_CONFIRMATION', email } as any);
146
+ send({ type: 'SIGN_UP_RESEND_CONFIRMATION', email });
103
147
  break;
104
148
  default:
105
149
  // toast(JSON.stringify(error, null, 2));
@@ -122,7 +166,8 @@ const AuthLogic = () => {
122
166
  attributes: { email },
123
167
  });
124
168
  // toast('Signed Up');
125
- send({ type: 'SIGN_UP_CONFIRM', email } as any);
169
+ send({ type: 'SIGN_UP_CONFIRM', email });
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
171
  } catch (error: any) {
127
172
  setNotifications({ type: 'error', message: error.message });
128
173
  // toast(JSON.stringify(error, null, 2));
@@ -139,7 +184,8 @@ const AuthLogic = () => {
139
184
  setLoading(true);
140
185
  await AmplifyAuth.confirmSignUp(email, code);
141
186
  // toast('Confirmed Signed In');
142
- send({ type: 'SIGN_UP_CONFIRMED', email } as any);
187
+ send({ type: 'SIGN_UP_CONFIRMED', email });
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
189
  } catch (error: any) {
144
190
  setNotifications({ type: 'error', message: error.message });
145
191
  // toast(JSON.stringify(error, null, 2));
@@ -154,6 +200,43 @@ const AuthLogic = () => {
154
200
  send({ type: 'RETURN_TO_SIGN_IN' });
155
201
  }, [send]);
156
202
 
203
+ const onForgotPassword = React.useCallback<OnForgotPassword>(
204
+ async ({ email }) => {
205
+ try {
206
+ setLoading(true);
207
+ await AmplifyAuth.forgotPassword(email);
208
+ // toast('Forgot Password');
209
+ send({ type: 'FORGOT_PASSWORD_RESET_PASSWORD', email });
210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
+ } catch (error: any) {
212
+ setNotifications({ type: 'error', message: error.message });
213
+ // toast(JSON.stringify(error, null, 2));
214
+ } finally {
215
+ setLoading(false);
216
+ }
217
+ },
218
+ [send, setLoading, setNotifications]
219
+ );
220
+
221
+ const onForgotPasswordResetPassword =
222
+ React.useCallback<OnForgotPasswordResetPassword>(
223
+ async ({ email, code, newPassword }) => {
224
+ try {
225
+ setLoading(true);
226
+ await AmplifyAuth.forgotPasswordSubmit(email, code, newPassword);
227
+ // toast('Forgot Password Reset Password');
228
+ send({ type: 'FORGOT_PASSWORD_CONFIRMED', email });
229
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
230
+ } catch (error: any) {
231
+ setNotifications({ type: 'error', message: error.message });
232
+ // toast(JSON.stringify(error, null, 2));
233
+ } finally {
234
+ setLoading(false);
235
+ }
236
+ },
237
+ [send, setLoading, setNotifications]
238
+ );
239
+
157
240
  if (isAuthenticated) {
158
241
  return null;
159
242
  }
@@ -168,7 +251,29 @@ const AuthLogic = () => {
168
251
  return (
169
252
  <AuthConfirmSignUp
170
253
  onConfirmSignUp={onConfirmSignUp}
171
- email={(state.context as any).email}
254
+ email={state.context.email}
255
+ />
256
+ );
257
+ }
258
+
259
+ if (state.matches('forgotPassword')) {
260
+ return (
261
+ <AuthForgotPassword
262
+ onForgotPassword={onForgotPassword}
263
+ onCancel={onReturnToSignIn}
264
+ onSignUp={() => {
265
+ return send('SIGN_UP');
266
+ }}
267
+ />
268
+ );
269
+ }
270
+
271
+ if (state.matches('forgotPasswordResetPassword')) {
272
+ return (
273
+ <AuthForgotPasswordResetPassword
274
+ email={state.context.email}
275
+ onForgotPasswordResetPassword={onForgotPasswordResetPassword}
276
+ onCancel={onReturnToSignIn}
172
277
  />
173
278
  );
174
279
  }
@@ -179,7 +284,10 @@ const AuthLogic = () => {
179
284
  onSignUp={() => {
180
285
  return send('SIGN_UP');
181
286
  }}
182
- defaultValues={{ email: (state.context as any).email }}
287
+ onForgotPassword={() => {
288
+ return send('FORGOT_PASSWORD');
289
+ }}
290
+ defaultValues={{ email: state.context.email }}
183
291
  />
184
292
  );
185
293
  };
@@ -38,7 +38,7 @@ export const AuthConfirmSignUp = ({
38
38
  })
39
39
  .required();
40
40
 
41
- const formMethods = useForm<yup.TypeOf<typeof schema>>({
41
+ const formMethods = useForm<yup.InferType<typeof schema>>({
42
42
  resolver: yupResolver(schema),
43
43
  });
44
44
 
@@ -3,14 +3,19 @@ import { Button, Link, Text } from '@ttoss/ui';
3
3
  import { Form, FormFieldInput, useForm, yup, yupResolver } from '@ttoss/forms';
4
4
  import { NotificationsBox } from '@ttoss/react-notifications';
5
5
  import { useI18n } from '@ttoss/react-i18n';
6
+ import type { OnForgotPassword } from './types';
6
7
 
7
- export type AuthRecoveryPasswordProps = {
8
- onRecoveryPassword: (email: string) => void;
8
+ export type AuthForgotPasswordProps = {
9
+ onForgotPassword: OnForgotPassword;
10
+ onCancel: () => void;
11
+ onSignUp: () => void;
9
12
  };
10
13
 
11
- export const AuthRecoveryPassword = ({
12
- onRecoveryPassword,
13
- }: AuthRecoveryPasswordProps) => {
14
+ export const AuthForgotPassword = ({
15
+ onForgotPassword,
16
+ onCancel,
17
+ onSignUp,
18
+ }: AuthForgotPasswordProps) => {
14
19
  const { intl } = useI18n();
15
20
 
16
21
  const schema = yup
@@ -33,7 +38,7 @@ export const AuthRecoveryPassword = ({
33
38
  })
34
39
  .required();
35
40
 
36
- const formMethods = useForm<yup.TypeOf<typeof schema>>({
41
+ const formMethods = useForm<yup.InferType<typeof schema>>({
37
42
  resolver: yupResolver(schema),
38
43
  mode: 'onBlur',
39
44
  });
@@ -45,7 +50,7 @@ export const AuthRecoveryPassword = ({
45
50
  maxWidth: '390px',
46
51
  }}
47
52
  onSubmit={({ email }) => {
48
- return onRecoveryPassword(email);
53
+ return onForgotPassword({ email });
49
54
  }}
50
55
  >
51
56
  <AuthCard
@@ -62,6 +67,7 @@ export const AuthRecoveryPassword = ({
62
67
  <Button
63
68
  sx={{ textAlign: 'center', display: 'initial' }}
64
69
  variant="secondary"
70
+ onClick={onCancel}
65
71
  >
66
72
  {intl.formatMessage({
67
73
  description: 'Cancel',
@@ -80,7 +86,11 @@ export const AuthRecoveryPassword = ({
80
86
 
81
87
  <NotificationsBox />
82
88
 
83
- <Text sx={{ marginTop: 'xl', cursor: 'pointer' }} as={Link}>
89
+ <Text
90
+ sx={{ marginTop: 'xl', cursor: 'pointer' }}
91
+ as={Link}
92
+ onClick={onSignUp}
93
+ >
84
94
  {intl.formatMessage({
85
95
  description: 'Sign up now',
86
96
  defaultMessage: 'Sign up now',
@@ -0,0 +1,158 @@
1
+ import * as React from 'react';
2
+ import { AuthCard } from './AuthCard';
3
+ import { Button } from '@ttoss/ui';
4
+ import {
5
+ Form,
6
+ FormFieldInput,
7
+ FormFieldPassword,
8
+ useForm,
9
+ yup,
10
+ yupResolver,
11
+ } from '@ttoss/forms';
12
+ import { NotificationsBox } from '@ttoss/react-notifications';
13
+ import { PASSWORD_MINIMUM_LENGTH } from '@ttoss/cloud-auth';
14
+ import { useI18n } from '@ttoss/react-i18n';
15
+ import type { OnForgotPasswordResetPassword } from './types';
16
+
17
+ export type AuthForgotPasswordResetPasswordProps = {
18
+ email: string;
19
+ onForgotPasswordResetPassword: OnForgotPasswordResetPassword;
20
+ onCancel: () => void;
21
+ };
22
+
23
+ export const AuthForgotPasswordResetPassword = ({
24
+ email,
25
+ onForgotPasswordResetPassword,
26
+ onCancel,
27
+ }: AuthForgotPasswordResetPasswordProps) => {
28
+ const { intl } = useI18n();
29
+
30
+ const schema = React.useMemo(() => {
31
+ return yup
32
+ .object()
33
+ .shape({
34
+ code: yup
35
+ .string()
36
+ .required(
37
+ intl.formatMessage({
38
+ description: 'Required field.',
39
+ defaultMessage: 'Required field',
40
+ })
41
+ )
42
+ .max(
43
+ 6,
44
+ intl.formatMessage(
45
+ {
46
+ description: 'Minimum {value} characters.',
47
+ defaultMessage: 'Minimum {value} characters',
48
+ },
49
+ { value: 6 }
50
+ )
51
+ ),
52
+ password: yup
53
+ .string()
54
+ .required(
55
+ intl.formatMessage({
56
+ description: 'Password is required.',
57
+ defaultMessage: 'Password field is required',
58
+ })
59
+ )
60
+ .min(
61
+ PASSWORD_MINIMUM_LENGTH,
62
+ intl.formatMessage(
63
+ {
64
+ description:
65
+ 'Password must be at least {value} characters long.',
66
+ defaultMessage: 'Password requires {value} characters',
67
+ },
68
+ { value: PASSWORD_MINIMUM_LENGTH }
69
+ )
70
+ )
71
+ .trim(),
72
+ confirmPassword: yup
73
+ .string()
74
+ .required(
75
+ intl.formatMessage({
76
+ description: 'Confirm Password is required.',
77
+ defaultMessage: 'Confirm password field is required',
78
+ })
79
+ )
80
+ .oneOf(
81
+ [yup.ref('password')],
82
+ intl.formatMessage({
83
+ description: 'Passwords are not the same',
84
+ defaultMessage: 'Passwords are not the same',
85
+ })
86
+ ),
87
+ })
88
+ .required();
89
+ }, [intl]);
90
+
91
+ const formMethods = useForm<yup.InferType<typeof schema>>({
92
+ resolver: yupResolver(schema),
93
+ mode: 'onBlur',
94
+ });
95
+
96
+ return (
97
+ <Form
98
+ {...formMethods}
99
+ sx={{
100
+ maxWidth: '390px',
101
+ }}
102
+ onSubmit={({ code, password }) => {
103
+ return onForgotPasswordResetPassword({
104
+ email,
105
+ code,
106
+ newPassword: password,
107
+ });
108
+ }}
109
+ >
110
+ <AuthCard
111
+ buttonLabel={intl.formatMessage({
112
+ description: 'Recover Password',
113
+ defaultMessage: 'Recover Password',
114
+ })}
115
+ isValidForm={formMethods.formState.isValid}
116
+ title={intl.formatMessage({
117
+ description: 'Recovering Password',
118
+ defaultMessage: 'Recovering Password',
119
+ })}
120
+ extraButton={
121
+ <Button
122
+ sx={{ textAlign: 'center', display: 'initial' }}
123
+ variant="secondary"
124
+ onClick={onCancel}
125
+ >
126
+ {intl.formatMessage({
127
+ description: 'Cancel',
128
+ defaultMessage: 'Cancel',
129
+ })}
130
+ </Button>
131
+ }
132
+ >
133
+ <FormFieldInput
134
+ name="code"
135
+ label={intl.formatMessage({
136
+ description: 'Code',
137
+ defaultMessage: 'Code',
138
+ })}
139
+ />
140
+ <FormFieldPassword
141
+ name="password"
142
+ label={intl.formatMessage({
143
+ description: 'Password label.',
144
+ defaultMessage: 'Password',
145
+ })}
146
+ />
147
+ <FormFieldPassword
148
+ name="confirmPassword"
149
+ label={intl.formatMessage({
150
+ description: 'Confirm Password label.',
151
+ defaultMessage: 'Confirm password',
152
+ })}
153
+ />
154
+ <NotificationsBox />
155
+ </AuthCard>
156
+ </Form>
157
+ );
158
+ };
@@ -17,7 +17,7 @@ import type { OnSignIn, OnSignInInput } from './types';
17
17
  export type AuthSignInProps = {
18
18
  onSignIn: OnSignIn;
19
19
  onSignUp: () => void;
20
- onForgotPassword?: () => void;
20
+ onForgotPassword: () => void;
21
21
  defaultValues?: Partial<OnSignInInput>;
22
22
  urlLogo?: string;
23
23
  };
@@ -141,7 +141,7 @@ export const AuthSignIn = ({
141
141
  /> */}
142
142
 
143
143
  <Text
144
- sx={{ marginLeft: 'auto' }}
144
+ sx={{ marginLeft: 'auto', cursor: 'pointer' }}
145
145
  as={Link}
146
146
  onClick={onForgotPassword}
147
147
  >
package/src/types.ts CHANGED
@@ -14,3 +14,11 @@ export type OnSignUpInput = {
14
14
  export type OnSignUp = (input: OnSignUpInput) => void;
15
15
 
16
16
  export type OnConfirmSignUp = (input: { email: string; code: string }) => void;
17
+
18
+ export type OnForgotPassword = (input: { email: string }) => void;
19
+
20
+ export type OnForgotPasswordResetPassword = (input: {
21
+ email: string;
22
+ code: string;
23
+ newPassword: string;
24
+ }) => void;