@truworth/twc-auth 3.0.3 → 3.0.5

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.
@@ -3,25 +3,40 @@ import { WebView } from '@truworth/twc-rn-common';
3
3
  import { Layout } from '@ui-kitten/components';
4
4
  import { Text } from 'react-native';
5
5
  import parse from 'url-parse';
6
- import { useState } from 'react';
6
+ import { useState, useCallback } from 'react';
7
+ const SAML_CALLBACK_PATHS = ['/saml', 'thewellnesscorner.com/saml'];
7
8
  const SSOAuthWebView = ({ navigation, route }) => {
8
- const { clientId, authenticationUrl, redirectUri } = route.params;
9
+ const { clientId, authenticationUrl, redirectUri, authMethod } = route.params;
9
10
  const [error, setError] = useState(null);
11
+ const handleNavigationStateChange = useCallback((navState) => {
12
+ setError(null);
13
+ const parsedUrl = parse(navState.url, true);
14
+ const currentUrl = parsedUrl.href;
15
+ const authUrl = parse(authenticationUrl, true).href;
16
+ if (currentUrl === authUrl)
17
+ return;
18
+ if (authMethod === 'saml') {
19
+ const isSamlCallback = SAML_CALLBACK_PATHS.some(path => navState.url.includes(path));
20
+ if (isSamlCallback) {
21
+ const { code } = parsedUrl.query;
22
+ if (code) {
23
+ navigation.replace('SSOCallback', { clientId, code: code, authMethod: 'saml' });
24
+ }
25
+ }
26
+ }
27
+ else {
28
+ if (redirectUri && navState.url.includes(redirectUri)) {
29
+ const { code } = parsedUrl.query;
30
+ navigation.replace('SSOCallback', { clientId, code: code, authMethod: 'oidc' });
31
+ }
32
+ }
33
+ }, [authenticationUrl, authMethod, clientId, navigation, redirectUri]);
10
34
  return (_jsxs(Layout, { style: { flex: 1 }, children: [error && (_jsx(Layout, { style: { padding: 16, backgroundColor: '#fee' }, children: _jsx(Text, { style: { color: 'red' }, children: error }) })), _jsx(WebView, { source: { uri: authenticationUrl }, style: { flex: 1 }, onError: (syntheticEvent) => {
11
35
  const { nativeEvent } = syntheticEvent;
12
36
  setError(`Failed to load authentication page: ${nativeEvent.description}`);
13
37
  }, onHttpError: (syntheticEvent) => {
14
38
  const { nativeEvent } = syntheticEvent;
15
39
  setError(`Authentication provider error: ${nativeEvent.statusCode}`);
16
- }, onNavigationStateChange: (navState) => {
17
- setError(null);
18
- const currentUrl = parse(navState.url, true).href;
19
- const authUrl = parse(authenticationUrl, true).href;
20
- if (currentUrl != authUrl && navState.url.includes(redirectUri)) {
21
- const query = parse(navState.url, true).query;
22
- const { code } = query;
23
- navigation.replace('SSOCallback', { clientId, code });
24
- }
25
- } })] }));
40
+ }, onNavigationStateChange: handleNavigationStateChange })] }));
26
41
  };
27
42
  export default SSOAuthWebView;
@@ -17,8 +17,9 @@ const useSSOAuthenticationMethods = () => {
17
17
  }).then((res) => {
18
18
  if (!isMountedRef.current)
19
19
  return;
20
- const { authenticationUrl, redirectUri } = res.data;
21
- onSSOLoginInitiated({ clientId, authenticationUrl, redirectUri });
20
+ const { authenticationUrl, redirectUri, authMethod } = res.data;
21
+ const resolvedAuthMethod = authMethod === 'saml' ? 'saml' : 'oidc';
22
+ onSSOLoginInitiated({ clientId, authenticationUrl, redirectUri, authMethod: resolvedAuthMethod });
22
23
  }).catch((error) => {
23
24
  if (!isMountedRef.current)
24
25
  return;
@@ -29,6 +29,7 @@ const SSOAuthenticationMethods = ({ client, onPressBack, handleMobileLogin }) =>
29
29
  const onSSOLoginInitiated = (result) => {
30
30
  try {
31
31
  localStorage.setItem('clientId', String(result.clientId));
32
+ localStorage.setItem('authMethod', result.authMethod);
32
33
  const u = new URL(result.authenticationUrl);
33
34
  window.location.href = u.toString();
34
35
  }
@@ -43,7 +43,12 @@ const SSOAuthenticationMethods = ({ route }) => {
43
43
  return (_jsx(View, { style: { marginTop: 70, alignSelf: 'center' }, children: _jsx(Logo, {}) }));
44
44
  };
45
45
  const onSSOLoginInitiated = (result) => {
46
- navigation.navigate('SSOAuthWebView', { ...result });
46
+ navigation.navigate('SSOAuthWebView', {
47
+ clientId: result.clientId,
48
+ authenticationUrl: result.authenticationUrl,
49
+ redirectUri: result.redirectUri,
50
+ authMethod: result.authMethod,
51
+ });
47
52
  };
48
53
  return (_jsx(ScreenLayout, { title: "", containerStyle: { flex: 1, backgroundColor: primary.white }, children: _jsxs(View, { style: { flex: 1, justifyContent: 'center' }, children: [client.image ?
49
54
  _jsx(FastImage, { source: { uri: client.image }, resizeMode: 'contain', style: { width: 180, aspectRatio: aspectRatio, alignSelf: 'center', marginTop: -150 } })
@@ -1,33 +1,61 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { Suspense, lazy, useEffect, useState } from 'react';
2
+ import React, { Suspense, lazy, useEffect, useState, Component } from 'react';
3
3
  import { Flex, Typography } from '@truworth/twc-web-design';
4
4
  import { useSSOCallback } from '../../hooks/internal/useSSOCallback';
5
5
  import { useNavigator } from '../../../../../hooks/useNavigator';
6
6
  import redirectAnimation from '../../../../../../assets/animation/redirect-home.json';
7
7
  import { RegistrationMethod } from '../../../../../enums';
8
8
  const Lottie = lazy(() => import('react-lottie'));
9
- const SSOCallbackComponents = () => {
9
+ class LottieErrorBoundary extends Component {
10
+ constructor(props) {
11
+ super(props);
12
+ this.state = { hasError: false };
13
+ }
14
+ static getDerivedStateFromError() {
15
+ return { hasError: true };
16
+ }
17
+ componentDidCatch() { }
18
+ render() {
19
+ if (this.state.hasError)
20
+ return this.props.fallback;
21
+ return this.props.children;
22
+ }
23
+ }
24
+ const SSOCallbackComponents = ({ authMethodOverride } = {}) => {
10
25
  const navigator = useNavigator();
11
26
  const [clientId, setClientId] = useState('');
12
27
  const [code, setCode] = useState('');
13
- // Resolve code/token from query (preferred) or localStorage; clientId/authMethod from localStorage only
28
+ const [authMethod, setAuthMethod] = useState(authMethodOverride ?? null);
29
+ const [isReady, setIsReady] = useState(false);
30
+ // Resolve code from query params; clientId from localStorage (only needed for OIDC)
14
31
  useEffect(() => {
15
32
  const query = navigator.query;
16
33
  const codeFromQuery = query.code;
17
34
  if (typeof window !== 'undefined') {
18
35
  try {
36
+ // clientId only needed for OIDC, not SAML
19
37
  const storedClientId = localStorage.getItem('clientId') || '';
38
+ const storedAuthMethod = localStorage.getItem('authMethod') || 'oidc';
20
39
  setClientId(storedClientId);
40
+ if (!authMethodOverride) {
41
+ setAuthMethod(storedAuthMethod);
42
+ }
21
43
  setCode(typeof codeFromQuery === 'string' ? codeFromQuery : '');
44
+ setIsReady(true);
22
45
  }
23
46
  catch (error) {
24
47
  console.log('Failed to read from localStorage:', error);
25
48
  setCode(typeof codeFromQuery === 'string' ? codeFromQuery : '');
26
- // Fallback: proceed without stored values
49
+ setIsReady(true);
27
50
  }
28
51
  }
29
- }, [navigator.query]);
30
- const { result, error } = useSSOCallback({ clientId, code });
52
+ }, [navigator.query, authMethodOverride]);
53
+ const { result, error } = useSSOCallback({
54
+ clientId,
55
+ code,
56
+ authMethod: authMethod ?? 'oidc',
57
+ isReady,
58
+ });
31
59
  useEffect(() => {
32
60
  if (result?.registrationToken) {
33
61
  navigator.pushAbsolute('/registration', {
@@ -45,14 +73,15 @@ const SSOCallbackComponents = () => {
45
73
  }, 1500);
46
74
  }
47
75
  }, [error, navigator]);
48
- return (_jsx(Flex, { justify: 'center', align: 'center', className: 'h-[100vh]', children: _jsxs("div", { children: [_jsx(Suspense, { fallback: _jsx("div", { className: "h-[230px] w-[250px]" }), children: _jsx(Lottie
49
- //@ts-ignore
50
- , {
76
+ const loadingFallback = _jsx("div", { className: "h-[230px] w-[250px]" });
77
+ return (_jsx(Flex, { justify: 'center', align: 'center', className: 'h-[100vh]', children: _jsxs("div", { children: [_jsx(LottieErrorBoundary, { fallback: loadingFallback, children: _jsx(Suspense, { fallback: loadingFallback, children: _jsx(Lottie
51
78
  //@ts-ignore
52
- options: {
53
- animationData: redirectAnimation,
54
- loop: true,
55
- autoplay: true
56
- }, height: 230, width: 250 }) }), _jsx(Typography, { size: 'h6', type: 'heading', color: 'gray-400', children: "Authenticating and Redirecting ..." })] }) }));
79
+ , {
80
+ //@ts-ignore
81
+ options: {
82
+ animationData: redirectAnimation,
83
+ loop: true,
84
+ autoplay: true
85
+ }, height: 230, width: 250 }) }) }), _jsx(Typography, { size: 'h6', type: 'heading', color: 'gray-400', children: "Authenticating and Redirecting ..." })] }) }));
57
86
  };
58
87
  export { SSOCallbackComponents };
@@ -7,17 +7,27 @@ import { RegistrationMethod } from "../../../../../enums";
7
7
  * @internal
8
8
  * Hook for managing SSOCallback screen state and auth context integration.
9
9
  * This hook is not exposed to package consumers.
10
+ *
11
+ * Supports both OIDC and SAML flows:
12
+ * - OIDC: Calls /callback endpoint with authorization code
13
+ * - SAML: Calls /saml-sso-complete endpoint with exchange code
10
14
  */
11
- const useSSOCallback = ({ clientId, code }) => {
15
+ const useSSOCallback = ({ clientId, code, authMethod, isReady = true }) => {
12
16
  const [error, setError] = useState(null);
13
17
  const [result, setResult] = useState(null);
18
+ const [hasProcessed, setHasProcessed] = useState(false);
14
19
  const { onLogin, onRegistrationMethodChange, onTokenChange } = useAuthPackageContext();
15
- const processOAuthCallback = useCallback((code) => {
20
+ const processSSOCallback = useCallback((codeToProcess) => {
16
21
  setError(null);
22
+ // SAML: Use simplified endpoint that doesn't require clientId
23
+ // OIDC: Use legacy endpoint with clientId
24
+ const endpoint = authMethod === 'saml'
25
+ ? `/auth/saml/complete`
26
+ : `/auth/login-sso/${clientId}/callback`;
17
27
  axiosClient({
18
- url: `/auth/login-sso/${clientId}/callback`,
28
+ url: endpoint,
19
29
  method: 'POST',
20
- data: { code },
30
+ data: { code: codeToProcess },
21
31
  }).then((res) => {
22
32
  if (res?.data?.registrationToken) {
23
33
  onRegistrationMethodChange(RegistrationMethod.SSO);
@@ -32,12 +42,17 @@ const useSSOCallback = ({ clientId, code }) => {
32
42
  setError(errorMessage);
33
43
  return showMessage({ message: errorMessage });
34
44
  });
35
- }, [clientId, code]);
45
+ }, [clientId, authMethod, onLogin, onRegistrationMethodChange, onTokenChange]);
36
46
  useEffect(() => {
37
- if (code && clientId) {
38
- processOAuthCallback(code);
47
+ if (!isReady || !code || hasProcessed)
48
+ return;
49
+ // SAML only needs code; OIDC needs both code and clientId
50
+ const canProcess = authMethod === 'saml' || clientId;
51
+ if (canProcess) {
52
+ setHasProcessed(true);
53
+ processSSOCallback(code);
39
54
  }
40
- }, [code, clientId]);
55
+ }, [isReady, code, clientId, authMethod, hasProcessed, processSSOCallback]);
41
56
  return { result, error };
42
57
  };
43
58
  export { useSSOCallback };
@@ -8,8 +8,8 @@ import loadingSpinner from '../../../../assets/loading-spinner.json';
8
8
  import Lottie from 'lottie-react-native';
9
9
  const { gray } = Colors;
10
10
  const SSOCallback = ({ navigation, route }) => {
11
- const { clientId, code } = route.params;
12
- const { result, error } = useSSOCallback({ clientId, code });
11
+ const { clientId, code, authMethod } = route.params;
12
+ const { result, error } = useSSOCallback({ clientId, code, authMethod });
13
13
  useEffect(() => {
14
14
  if (result?.registrationToken) {
15
15
  navigation.replace('SignUp', { ...result });
@@ -1,5 +1,6 @@
1
1
  import { type NativeStackScreenProps } from '@react-navigation/native-stack';
2
2
  import type { Client, PersonalDetails } from '../types/types';
3
+ import type { AuthMethod } from '../screens/SSOLogin/AuthenticationMethods/types';
3
4
  declare const AuthNavigator: () => import("react/jsx-runtime").JSX.Element;
4
5
  export { AuthNavigator };
5
6
  export type AuthStackParamList = {
@@ -55,11 +56,13 @@ export type AuthStackParamList = {
55
56
  SSOCallback: {
56
57
  code?: string;
57
58
  clientId: number;
59
+ authMethod?: AuthMethod;
58
60
  };
59
61
  SSOAuthWebView: {
60
62
  clientId: number;
61
63
  authenticationUrl: string;
62
- redirectUri: string;
64
+ redirectUri?: string;
65
+ authMethod: AuthMethod;
63
66
  };
64
67
  EnterMobile: undefined;
65
68
  LoginWithMobileOTP: {
@@ -1,12 +1,14 @@
1
1
  import type { Client } from "../../../types/types";
2
+ type AuthMethod = 'oidc' | 'saml';
2
3
  interface SSOInitiationData {
3
4
  clientId: number;
4
5
  authenticationUrl: string;
5
- redirectUri: string;
6
+ redirectUri?: string;
7
+ authMethod: AuthMethod;
6
8
  }
7
9
  interface SSOAuthenticationMethodsProps {
8
10
  client: Client;
9
11
  onPressBack: () => void;
10
12
  handleMobileLogin: () => void;
11
13
  }
12
- export type { SSOAuthenticationMethodsProps, SSOInitiationData };
14
+ export type { SSOAuthenticationMethodsProps, SSOInitiationData, AuthMethod };
@@ -1,2 +1,6 @@
1
- declare const SSOCallbackComponents: () => import("react/jsx-runtime").JSX.Element;
1
+ import type { AuthMethod } from '../../../AuthenticationMethods/types';
2
+ interface SSOCallbackComponentsProps {
3
+ authMethodOverride?: AuthMethod;
4
+ }
5
+ declare const SSOCallbackComponents: ({ authMethodOverride }?: SSOCallbackComponentsProps) => import("react/jsx-runtime").JSX.Element;
2
6
  export { SSOCallbackComponents };
@@ -3,8 +3,12 @@ import type { useSSOCallbackProps, SignUpData } from "../../types";
3
3
  * @internal
4
4
  * Hook for managing SSOCallback screen state and auth context integration.
5
5
  * This hook is not exposed to package consumers.
6
+ *
7
+ * Supports both OIDC and SAML flows:
8
+ * - OIDC: Calls /callback endpoint with authorization code
9
+ * - SAML: Calls /saml-sso-complete endpoint with exchange code
6
10
  */
7
- declare const useSSOCallback: ({ clientId, code }: useSSOCallbackProps) => {
11
+ declare const useSSOCallback: ({ clientId, code, authMethod, isReady }: useSSOCallbackProps) => {
8
12
  result: SignUpData | null;
9
13
  error: string | null;
10
14
  };
@@ -1,3 +1,4 @@
1
+ import type { AuthMethod } from '../AuthenticationMethods/types';
1
2
  interface SignUpData {
2
3
  email: string;
3
4
  firstName: string;
@@ -7,5 +8,7 @@ interface SignUpData {
7
8
  interface useSSOCallbackProps {
8
9
  code?: string;
9
10
  clientId: number | string;
11
+ authMethod?: AuthMethod;
12
+ isReady?: boolean;
10
13
  }
11
14
  export type { useSSOCallbackProps, SignUpData };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "description": "Truworth Auth Package for React Native and Web",
7
- "version": "3.0.3",
7
+ "version": "3.0.5",
8
8
  "main": "build/src/index.js",
9
9
  "types": "build/types/index.d.ts",
10
10
  "files": [