@ttoss/react-auth 1.1.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.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # @ttoss/react-auth
2
+
3
+ ## About
4
+
5
+ This module handles auth in your applications and other ttoss modules.
6
+
7
+ This module is intended to use with AWS Cognito. It uses [AWS Amplify](https://docs.amplify.aws/lib/auth/getting-started/q/platform/js) under the hood.
8
+
9
+ [Amplify Auth configuration](https://docs.amplify.aws/lib/auth/start/q/platform/js#re-use-existing-authentication-resource) must be provided in your App to make Auth Module works properly.
10
+
11
+ ## Getting Started
12
+
13
+ ### Install
14
+
15
+ ```shell
16
+ $ yarn add @ttoss/auth @ttoss/react-notifications
17
+ ```
18
+
19
+ ## Examples of use
20
+
21
+ ### Amplify config
22
+
23
+ ```ts
24
+ import Amplify from 'aws-amplify';
25
+
26
+ Amplify.configure({
27
+ Auth: {
28
+ // REQUIRED only for Federated Authentication - Amazon Cognito Identity Pool ID
29
+ identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab',
30
+
31
+ // REQUIRED - Amazon Cognito Region
32
+ region: 'XX-XXXX-X',
33
+
34
+ // OPTIONAL - Amazon Cognito Federated Identity Pool Region
35
+ // Required only if it's different from Amazon Cognito Region
36
+ identityPoolRegion: 'XX-XXXX-X',
37
+
38
+ // OPTIONAL - Amazon Cognito User Pool ID
39
+ userPoolId: 'XX-XXXX-X_abcd1234',
40
+
41
+ // OPTIONAL - Amazon Cognito Web Client ID (26-char alphanumeric string)
42
+ userPoolWebClientId: 'a1b2c3d4e5f6g7h8i9j0k1l2m3',
43
+
44
+ // OPTIONAL - Enforce user authentication prior to accessing AWS resources or not
45
+ mandatorySignIn: false,
46
+
47
+ // OPTIONAL - Configuration for cookie storage
48
+ // Note: if the secure flag is set to true, then the cookie transmission requires a secure protocol
49
+ cookieStorage: {
50
+ // REQUIRED - Cookie domain (only required if cookieStorage is provided)
51
+ domain: '.yourdomain.com',
52
+ // OPTIONAL - Cookie path
53
+ path: '/',
54
+ // OPTIONAL - Cookie expiration in days
55
+ expires: 365,
56
+ // OPTIONAL - See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
57
+ sameSite: 'strict' | 'lax',
58
+ // OPTIONAL - Cookie secure flag
59
+ // Either true or false, indicating if the cookie transmission requires a secure protocol (https).
60
+ secure: true,
61
+ },
62
+
63
+ // OPTIONAL - customized storage object
64
+ storage: MyStorage,
65
+
66
+ // OPTIONAL - Manually set the authentication flow type. Default is 'USER_SRP_AUTH'
67
+ authenticationFlowType: 'USER_PASSWORD_AUTH',
68
+
69
+ // OPTIONAL - Manually set key value pairs that can be passed to Cognito Lambda Triggers
70
+ clientMetadata: { myCustomKey: 'myCustomValue' },
71
+
72
+ // OPTIONAL - Hosted UI configuration
73
+ oauth: {
74
+ domain: 'your_cognito_domain',
75
+ scope: [
76
+ 'phone',
77
+ 'email',
78
+ 'profile',
79
+ 'openid',
80
+ 'aws.cognito.signin.user.admin',
81
+ ],
82
+ redirectSignIn: 'http://localhost:3000/',
83
+ redirectSignOut: 'http://localhost:3000/',
84
+ responseType: 'code', // or 'token', note that REFRESH token will only be generated when the responseType is code
85
+ },
86
+ },
87
+ });
88
+ ```
89
+
90
+ ### PrivateRoute component
91
+
92
+ ```tsx
93
+ import { useAuth } from '@ttoss/react-auth';
94
+
95
+ const PrivateRoute = (props: any) => {
96
+ const { isAuthenticated } = useAuth();
97
+
98
+ if (!isAuthenticated) {
99
+ return <Navigate to="/login" state={{ redirectTo: props.path || '/' }} />;
100
+ }
101
+ return <Route {...props} />;
102
+ };
103
+ ```
104
+
105
+ ### Login Page
106
+
107
+ ```tsx
108
+ import { Auth, useAuth } from '@ttoss/react-auth';
109
+
110
+ const Login = () => {
111
+ const auth = useAuth();
112
+
113
+ const onSuccess = () => {
114
+ // Navigate to logged-area
115
+ };
116
+
117
+ return (
118
+ <div>
119
+ <h1>Login Page</h1>
120
+
121
+ <Auth onSignIn={onSuccess} />
122
+
123
+ <button onClick={auth.signOut}>Logout</button>
124
+ </div>
125
+ );
126
+ };
127
+ export default Login;
128
+ ```
129
+
130
+ ## Auth with Progressbar
131
+
132
+ ```tsx
133
+ import { AuthProvider } from '@ttoss/react-auth';
134
+ import { NotificationsProvider } from '@ttoss/react-notifications';
135
+
136
+ ReactDOM.render(
137
+ <React.StrictMode>
138
+ <NotificationsProvider>
139
+ <AuthProvider>
140
+ <App />
141
+ </AuthProvider>
142
+ </NotificationsProvider>
143
+ </React.StrictMode>,
144
+ document.getElementById('root')
145
+ );
146
+ ```
@@ -0,0 +1,529 @@
1
+ /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
+
3
+ // src/AuthProvider.tsx
4
+ import * as React from "react";
5
+ import { Auth, Hub } from "aws-amplify";
6
+ import { jsx } from "react/jsx-runtime";
7
+ var signOut = () => {
8
+ return Auth.signOut();
9
+ };
10
+ var AuthContext = React.createContext({
11
+ signOut,
12
+ isAuthenticated: false,
13
+ user: null,
14
+ tokens: null
15
+ });
16
+ var AuthProvider = ({ children }) => {
17
+ const [user, setUser] = React.useState(null);
18
+ const [tokens, setTokens] = React.useState(null);
19
+ React.useEffect(() => {
20
+ const updateUser = () => {
21
+ Auth.currentAuthenticatedUser().then(({ attributes, signInUserSession }) => {
22
+ setUser({
23
+ id: attributes.sub,
24
+ email: attributes.email,
25
+ emailVerified: attributes["email_verified"]
26
+ });
27
+ setTokens({
28
+ idToken: signInUserSession.idToken.jwtToken,
29
+ accessToken: signInUserSession.accessToken.jwtToken,
30
+ refreshToken: signInUserSession.refreshToken.token
31
+ });
32
+ }).catch(() => {
33
+ setUser(null);
34
+ setTokens(null);
35
+ });
36
+ };
37
+ const updateUserListener = Hub.listen("auth", updateUser);
38
+ updateUser();
39
+ return () => {
40
+ updateUserListener();
41
+ };
42
+ }, []);
43
+ const isAuthenticated = !!user;
44
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value: { signOut, isAuthenticated, user, tokens }, children });
45
+ };
46
+ var useAuth = () => {
47
+ return React.useContext(AuthContext);
48
+ };
49
+ var AuthProvider_default = AuthProvider;
50
+
51
+ // src/Auth.tsx
52
+ import * as React3 from "react";
53
+ import { Auth as AmplifyAuth } from "aws-amplify";
54
+
55
+ // src/AuthCard.tsx
56
+ import * as React2 from "react";
57
+ import { Button, Card, Flex, Link, Text } from "@ttoss/ui";
58
+ import { useNotifications } from "@ttoss/react-notifications";
59
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
60
+ var LogoContext = React2.createContext({});
61
+ var LogoProvider = ({ children, ...values }) => {
62
+ return /* @__PURE__ */ jsx2(LogoContext.Provider, { value: values, children });
63
+ };
64
+ var AuthCard = ({
65
+ children,
66
+ title,
67
+ buttonLabel,
68
+ links = []
69
+ }) => {
70
+ const { logo } = React2.useContext(LogoContext);
71
+ const { isLoading } = useNotifications();
72
+ return /* @__PURE__ */ jsx2(Card, { sx: { maxWidth: "564px" }, children: /* @__PURE__ */ jsxs(Flex, { sx: { flexDirection: "column", gap: 3 }, children: [
73
+ logo && /* @__PURE__ */ jsx2(Flex, { sx: { width: "100%", justifyContent: "center" }, children: logo }),
74
+ /* @__PURE__ */ jsx2(
75
+ Text,
76
+ {
77
+ variant: "title",
78
+ sx: { alignSelf: "center", marginY: 4, fontSize: 5 },
79
+ children: title
80
+ }
81
+ ),
82
+ children,
83
+ /* @__PURE__ */ jsx2(Flex, { sx: { justifyContent: "space-between", marginTop: 3 }, children: /* @__PURE__ */ jsx2(
84
+ Button,
85
+ {
86
+ type: "submit",
87
+ "aria-label": "submit-login",
88
+ variant: "cta",
89
+ disabled: isLoading,
90
+ sx: { width: "100%" },
91
+ children: buttonLabel
92
+ }
93
+ ) }),
94
+ /* @__PURE__ */ jsx2(
95
+ Flex,
96
+ {
97
+ sx: {
98
+ justifyContent: "space-between",
99
+ flexDirection: "column",
100
+ gap: 3,
101
+ marginTop: 4,
102
+ color: "text"
103
+ },
104
+ children: links.map((link) => {
105
+ return link && /* @__PURE__ */ jsx2(Link, { onClick: link.onClick, children: link.label }, link.label);
106
+ })
107
+ }
108
+ )
109
+ ] }) });
110
+ };
111
+
112
+ // src/AuthConfirmSignUp.tsx
113
+ import { Form, FormFieldInput, useForm, yup, yupResolver } from "@ttoss/forms";
114
+ import { useI18n } from "@ttoss/react-i18n";
115
+ import { jsx as jsx3 } from "react/jsx-runtime";
116
+ var AuthConfirmSignUp = ({
117
+ email,
118
+ onConfirmSignUp
119
+ }) => {
120
+ const { intl } = useI18n();
121
+ const schema = yup.object().shape({
122
+ code: yup.string().required(
123
+ intl.formatMessage({
124
+ description: "Required field.",
125
+ defaultMessage: "Required field"
126
+ })
127
+ ).max(
128
+ 6,
129
+ intl.formatMessage(
130
+ {
131
+ description: "Minimum {value} characters.",
132
+ defaultMessage: "Minimum {value} characters"
133
+ },
134
+ { value: 6 }
135
+ )
136
+ )
137
+ }).required();
138
+ const formMethods = useForm({
139
+ resolver: yupResolver(schema)
140
+ });
141
+ return /* @__PURE__ */ jsx3(
142
+ Form,
143
+ {
144
+ ...formMethods,
145
+ onSubmit: ({ code }) => {
146
+ return onConfirmSignUp({ code, email });
147
+ },
148
+ children: /* @__PURE__ */ jsx3(
149
+ AuthCard,
150
+ {
151
+ buttonLabel: intl.formatMessage({
152
+ description: "Confirm",
153
+ defaultMessage: "Confirm"
154
+ }),
155
+ title: intl.formatMessage({
156
+ description: "Confirmation",
157
+ defaultMessage: "Confirmation"
158
+ }),
159
+ children: /* @__PURE__ */ jsx3(
160
+ FormFieldInput,
161
+ {
162
+ name: "code",
163
+ label: intl.formatMessage({
164
+ description: "Sign up confirmation code",
165
+ defaultMessage: "Code"
166
+ })
167
+ }
168
+ )
169
+ }
170
+ )
171
+ }
172
+ );
173
+ };
174
+
175
+ // src/AuthSignIn.tsx
176
+ import { Form as Form2, FormFieldInput as FormFieldInput2, useForm as useForm2, yup as yup2, yupResolver as yupResolver2 } from "@ttoss/forms";
177
+
178
+ // ../cloud-auth/dist/esm/index.js
179
+ var PASSWORD_MINIMUM_LENGTH = 8;
180
+
181
+ // src/AuthSignIn.tsx
182
+ import { useI18n as useI18n2 } from "@ttoss/react-i18n";
183
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
184
+ var AuthSignIn = ({
185
+ onSignIn,
186
+ onSignUp,
187
+ defaultValues
188
+ }) => {
189
+ const { intl } = useI18n2();
190
+ const schema = yup2.object().shape({
191
+ email: yup2.string().required(
192
+ intl.formatMessage({
193
+ description: "Email is a required field.",
194
+ defaultMessage: "Email field is required"
195
+ })
196
+ ).email(
197
+ intl.formatMessage({
198
+ description: "Invalid email.",
199
+ defaultMessage: "Invalid email"
200
+ })
201
+ ),
202
+ password: yup2.string().required(
203
+ intl.formatMessage({
204
+ description: "Password is required.",
205
+ defaultMessage: "Password field is required"
206
+ })
207
+ ).min(
208
+ PASSWORD_MINIMUM_LENGTH,
209
+ intl.formatMessage(
210
+ {
211
+ description: "Password must be at least {value} characters long.",
212
+ defaultMessage: "Password requires {value} characters"
213
+ },
214
+ { value: PASSWORD_MINIMUM_LENGTH }
215
+ )
216
+ ).trim()
217
+ });
218
+ const formMethods = useForm2({
219
+ defaultValues,
220
+ resolver: yupResolver2(schema)
221
+ });
222
+ const onSubmitForm = (data) => {
223
+ return onSignIn(data);
224
+ };
225
+ return /* @__PURE__ */ jsx4(Form2, { ...formMethods, onSubmit: onSubmitForm, children: /* @__PURE__ */ jsxs2(
226
+ AuthCard,
227
+ {
228
+ title: intl.formatMessage({
229
+ description: "Sign in title.",
230
+ defaultMessage: "Login"
231
+ }),
232
+ buttonLabel: intl.formatMessage({
233
+ description: "Button label.",
234
+ defaultMessage: "Login"
235
+ }),
236
+ links: [
237
+ {
238
+ onClick: onSignUp,
239
+ label: intl.formatMessage({
240
+ description: "Link to retrieve password.",
241
+ defaultMessage: "Do you forgot your password?"
242
+ })
243
+ },
244
+ {
245
+ onClick: onSignUp,
246
+ label: intl.formatMessage({
247
+ description: "Link to sign up.",
248
+ defaultMessage: "Don't have an account? Sign up"
249
+ })
250
+ }
251
+ ],
252
+ children: [
253
+ /* @__PURE__ */ jsx4(
254
+ FormFieldInput2,
255
+ {
256
+ name: "email",
257
+ label: intl.formatMessage({
258
+ description: "Email label.",
259
+ defaultMessage: "Email"
260
+ })
261
+ }
262
+ ),
263
+ /* @__PURE__ */ jsx4(
264
+ FormFieldInput2,
265
+ {
266
+ name: "password",
267
+ label: intl.formatMessage({
268
+ description: "Password label.",
269
+ defaultMessage: "Password"
270
+ })
271
+ }
272
+ )
273
+ ]
274
+ }
275
+ ) });
276
+ };
277
+
278
+ // src/AuthSignUp.tsx
279
+ import { Form as Form3, FormFieldInput as FormFieldInput3, useForm as useForm3, yup as yup3, yupResolver as yupResolver3 } from "@ttoss/forms";
280
+ import { useI18n as useI18n3 } from "@ttoss/react-i18n";
281
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
282
+ var AuthSignUp = ({ onSignUp, onReturnToSignIn }) => {
283
+ const { intl } = useI18n3();
284
+ const schema = yup3.object().shape({
285
+ email: yup3.string().required(
286
+ intl.formatMessage({
287
+ description: "Email is a required field.",
288
+ defaultMessage: "Email field is required"
289
+ })
290
+ ).email(
291
+ intl.formatMessage({
292
+ description: "Invalid email.",
293
+ defaultMessage: "Invalid email"
294
+ })
295
+ ),
296
+ password: yup3.string().required(
297
+ intl.formatMessage({
298
+ description: "Password is required.",
299
+ defaultMessage: "Password field is required"
300
+ })
301
+ ).min(
302
+ PASSWORD_MINIMUM_LENGTH,
303
+ intl.formatMessage(
304
+ {
305
+ description: "Password must be at least {value} characters long.",
306
+ defaultMessage: "Password requires {value} characters"
307
+ },
308
+ { value: PASSWORD_MINIMUM_LENGTH }
309
+ )
310
+ ).trim()
311
+ });
312
+ const formMethods = useForm3({
313
+ resolver: yupResolver3(schema)
314
+ });
315
+ const onSubmitForm = (data) => {
316
+ return onSignUp(data);
317
+ };
318
+ return /* @__PURE__ */ jsx5(Form3, { ...formMethods, onSubmit: onSubmitForm, children: /* @__PURE__ */ jsxs3(
319
+ AuthCard,
320
+ {
321
+ buttonLabel: intl.formatMessage({
322
+ description: "Create account.",
323
+ defaultMessage: "Create account"
324
+ }),
325
+ title: intl.formatMessage({
326
+ description: "Title on sign up.",
327
+ defaultMessage: "Register"
328
+ }),
329
+ links: [
330
+ {
331
+ label: intl.formatMessage({
332
+ description: "Link to sign in on sign up.",
333
+ defaultMessage: "Do you already have an account? Sign in"
334
+ }),
335
+ onClick: onReturnToSignIn
336
+ }
337
+ ],
338
+ children: [
339
+ /* @__PURE__ */ jsx5(
340
+ FormFieldInput3,
341
+ {
342
+ name: "email",
343
+ label: intl.formatMessage({
344
+ description: "Email label.",
345
+ defaultMessage: "Email"
346
+ })
347
+ }
348
+ ),
349
+ /* @__PURE__ */ jsx5(
350
+ FormFieldInput3,
351
+ {
352
+ name: "password",
353
+ label: intl.formatMessage({
354
+ description: "Password label.",
355
+ defaultMessage: "Password"
356
+ })
357
+ }
358
+ )
359
+ ]
360
+ }
361
+ ) });
362
+ };
363
+
364
+ // src/Auth.tsx
365
+ import { assign, createMachine } from "xstate";
366
+ import { useMachine } from "@xstate/react";
367
+ import { useNotifications as useNotifications2 } from "@ttoss/react-notifications";
368
+ import { jsx as jsx6 } from "react/jsx-runtime";
369
+ var authMachine = createMachine(
370
+ {
371
+ predictableActionArguments: true,
372
+ initial: "signIn",
373
+ states: {
374
+ signIn: {
375
+ on: {
376
+ SIGN_UP: { target: "signUp" },
377
+ SIGN_UP_RESEND_CONFIRMATION: {
378
+ actions: ["assignEmail"],
379
+ target: "signUpConfirm"
380
+ }
381
+ }
382
+ },
383
+ signUp: {
384
+ on: {
385
+ SIGN_UP_CONFIRM: {
386
+ actions: ["assignEmail"],
387
+ target: "signUpConfirm"
388
+ },
389
+ RETURN_TO_SIGN_IN: { target: "signIn" }
390
+ }
391
+ },
392
+ signUpConfirm: {
393
+ on: {
394
+ SIGN_UP_CONFIRMED: {
395
+ actions: ["assignEmail"],
396
+ target: "signIn"
397
+ }
398
+ }
399
+ }
400
+ }
401
+ },
402
+ {
403
+ actions: {
404
+ assignEmail: assign({
405
+ email: (_, event) => {
406
+ return event.email;
407
+ }
408
+ })
409
+ }
410
+ }
411
+ );
412
+ var AuthWithoutLogo = () => {
413
+ const { isAuthenticated } = useAuth();
414
+ const [state, send] = useMachine(authMachine);
415
+ const { setLoading } = useNotifications2();
416
+ const onSignIn = React3.useCallback(
417
+ async ({ email, password }) => {
418
+ try {
419
+ setLoading(true);
420
+ await AmplifyAuth.signIn(email, password);
421
+ } catch (error) {
422
+ switch (error.code) {
423
+ case "UserNotConfirmedException":
424
+ await AmplifyAuth.resendSignUp(email);
425
+ send({ type: "SIGN_UP_RESEND_CONFIRMATION", email });
426
+ break;
427
+ default:
428
+ }
429
+ } finally {
430
+ setLoading(false);
431
+ }
432
+ },
433
+ [send, setLoading]
434
+ );
435
+ const onSignUp = React3.useCallback(
436
+ async ({ email, password }) => {
437
+ try {
438
+ setLoading(true);
439
+ await AmplifyAuth.signUp({
440
+ username: email,
441
+ password,
442
+ attributes: { email }
443
+ });
444
+ send({ type: "SIGN_UP_CONFIRM", email });
445
+ } catch (error) {
446
+ } finally {
447
+ setLoading(false);
448
+ }
449
+ },
450
+ [send, setLoading]
451
+ );
452
+ const onConfirmSignUp = React3.useCallback(
453
+ async ({ email, code }) => {
454
+ try {
455
+ setLoading(true);
456
+ await AmplifyAuth.confirmSignUp(email, code);
457
+ send({ type: "SIGN_UP_CONFIRMED", email });
458
+ } catch (error) {
459
+ } finally {
460
+ setLoading(false);
461
+ }
462
+ },
463
+ [send, setLoading]
464
+ );
465
+ const onReturnToSignIn = React3.useCallback(() => {
466
+ send({ type: "RETURN_TO_SIGN_IN" });
467
+ }, [send]);
468
+ if (isAuthenticated) {
469
+ return null;
470
+ }
471
+ if (state.matches("signUp")) {
472
+ return /* @__PURE__ */ jsx6(AuthSignUp, { onSignUp, onReturnToSignIn });
473
+ }
474
+ if (state.matches("signUpConfirm")) {
475
+ return /* @__PURE__ */ jsx6(
476
+ AuthConfirmSignUp,
477
+ {
478
+ onConfirmSignUp,
479
+ email: state.context.email
480
+ }
481
+ );
482
+ }
483
+ return /* @__PURE__ */ jsx6(
484
+ AuthSignIn,
485
+ {
486
+ onSignIn,
487
+ onSignUp: () => {
488
+ return send("SIGN_UP");
489
+ },
490
+ defaultValues: { email: state.context.email }
491
+ }
492
+ );
493
+ };
494
+ var withLogo = (Component) => {
495
+ const WithLogo = ({ logo, ...componentProps }) => {
496
+ return /* @__PURE__ */ jsx6(LogoProvider, { logo, children: /* @__PURE__ */ jsx6(Component, { ...componentProps }) });
497
+ };
498
+ WithLogo.displayName = "WithLogo";
499
+ return WithLogo;
500
+ };
501
+ var Auth2 = withLogo(AuthWithoutLogo);
502
+
503
+ // src/AuthContainer.tsx
504
+ import { Flex as Flex2 } from "@ttoss/ui";
505
+ import { jsx as jsx7 } from "react/jsx-runtime";
506
+ var AuthContainer = ({ sx, ...props }) => {
507
+ return /* @__PURE__ */ jsx7(
508
+ Flex2,
509
+ {
510
+ ...props,
511
+ sx: {
512
+ height: "100vh",
513
+ justifyContent: "center",
514
+ alignItems: "center",
515
+ margin: 0,
516
+ backgroundPosition: "center",
517
+ backgroundRepeat: "no-repeat",
518
+ backgroundSize: "cover",
519
+ ...sx
520
+ }
521
+ }
522
+ );
523
+ };
524
+ export {
525
+ Auth2 as Auth,
526
+ AuthContainer,
527
+ AuthProvider_default as AuthProvider,
528
+ useAuth
529
+ };