diff --git a/i18n/mobile/auth/en.json b/i18n/mobile/auth/en.json index 3ff31d0..a26d921 100644 --- a/i18n/mobile/auth/en.json +++ b/i18n/mobile/auth/en.json @@ -11,11 +11,14 @@ "BUTTON_SIGN_IN": "Sign In" }, "GOOGLE_FORM": { - "BUTTON_CONTINUE_WITH_GOOGLE": "Continue with Google", - "OAUTH_WEB_VIEW_MODAL": { - "BUTTON_CLOSE": "Close", - "TEXT_THIS_SIGN_IN_METHOD_IS_UNAVAILABLE": "This sign-in method is unavailable. Please use another option to continue." - } + "BUTTON_CONTINUE_WITH_GOOGLE": "Continue with Google" + }, + "OIDC_FORM": { + "BUTTON_CONTINUE_WITH": "Continue with {{provider}}" + }, + "OAUTH_WEB_VIEW_MODAL": { + "BUTTON_CLOSE": "Close", + "TEXT_THIS_SIGN_IN_METHOD_IS_UNAVAILABLE": "This sign-in method is unavailable. Please use another option to continue." } } } diff --git a/libs/mobile/auth/features/email-sign-in-form/src/lib/component.tsx b/libs/mobile/auth/features/email-sign-in-form/src/lib/component.tsx index cdb9521..1e3661f 100644 --- a/libs/mobile/auth/features/email-sign-in-form/src/lib/component.tsx +++ b/libs/mobile/auth/features/email-sign-in-form/src/lib/component.tsx @@ -17,7 +17,7 @@ import { EmailFormSchema } from './forms'; interface EmailSignInFormProps { onSuccess: () => void; onApiUrlChange?: (url: string) => void; - setOauthProviders: (providers: Array) => void; + setOauthProviders: (providers: Partial>) => void; } export function EmailSignInForm({ onSuccess, onApiUrlChange, setOauthProviders }: EmailSignInFormProps): ReactElement { @@ -61,7 +61,7 @@ export function EmailSignInForm({ onSuccess, onApiUrlChange, setOauthProviders } useEffect(() => { setValue('url', query, { shouldValidate: true }); onApiUrlChange?.(query); - setOauthProviders([]); + setOauthProviders({}); }, [query]); useEffect(() => { @@ -71,7 +71,7 @@ export function EmailSignInForm({ onSuccess, onApiUrlChange, setOauthProviders } if (res?.name && res?.version) { appStorageService.apiUrl.set(debouncedQuery); - setOauthProviders(Object.values(res.oauth.providers)); + setOauthProviders(res.oauth.providers); } } }; diff --git a/libs/mobile/auth/features/google-sign-in-form/src/lib/component.tsx b/libs/mobile/auth/features/google-sign-in-form/src/lib/component.tsx index 4f10b6b..7513fbe 100644 --- a/libs/mobile/auth/features/google-sign-in-form/src/lib/component.tsx +++ b/libs/mobile/auth/features/google-sign-in-form/src/lib/component.tsx @@ -1,8 +1,9 @@ import { useTranslation } from '@ronas-it/react-native-common-modules/i18n'; import { Fragment, ReactElement, useState } from 'react'; +import { OauthWebView } from '@open-webui-react-native/mobile/auth/features/oauth-web-view'; import { AppButton } from '@open-webui-react-native/mobile/shared/ui/ui-kit'; +import { Provider } from '@open-webui-react-native/shared/data-access/api'; import { authState$ } from '@open-webui-react-native/shared/data-access/auth'; -import { OauthWebViewModal } from './components'; interface GoogleSignInFormProps { onSuccess?: () => void; @@ -34,10 +35,12 @@ export function GoogleSignInForm({ onSuccess }: GoogleSignInFormProps): ReactEle iconName='googleLogo' onPress={handleSignInWithGooglePress} /> - + onGetToken={handleToken} + /> ); } diff --git a/libs/mobile/auth/features/google-sign-in-form/src/lib/components/index.ts b/libs/mobile/auth/features/google-sign-in-form/src/lib/components/index.ts deleted file mode 100644 index 310c073..0000000 --- a/libs/mobile/auth/features/google-sign-in-form/src/lib/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './oauth-web-view-modal'; diff --git a/libs/mobile/auth/features/oauth-web-view/.babelrc b/libs/mobile/auth/features/oauth-web-view/.babelrc new file mode 100644 index 0000000..1ea870e --- /dev/null +++ b/libs/mobile/auth/features/oauth-web-view/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/mobile/auth/features/oauth-web-view/README.md b/libs/mobile/auth/features/oauth-web-view/README.md new file mode 100644 index 0000000..a66058e --- /dev/null +++ b/libs/mobile/auth/features/oauth-web-view/README.md @@ -0,0 +1,7 @@ +# mobile/auth/features/oauth-web-view + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test mobile/auth/features/oauth-web-view` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/mobile/auth/features/oauth-web-view/eslint.config.cjs b/libs/mobile/auth/features/oauth-web-view/eslint.config.cjs new file mode 100644 index 0000000..6f47c1a --- /dev/null +++ b/libs/mobile/auth/features/oauth-web-view/eslint.config.cjs @@ -0,0 +1,12 @@ +const nx = require('@nx/eslint-plugin'); +const baseConfig = require('../../../../../eslint.config.cjs'); + +module.exports = [ + ...baseConfig, + ...nx.configs['flat/react'], + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/libs/mobile/auth/features/oauth-web-view/project.json b/libs/mobile/auth/features/oauth-web-view/project.json new file mode 100644 index 0000000..3c03fc5 --- /dev/null +++ b/libs/mobile/auth/features/oauth-web-view/project.json @@ -0,0 +1,9 @@ +{ + "name": "mobile/auth/features/oauth-web-view", + "$schema": "../../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/mobile/auth/features/oauth-web-view/src", + "projectType": "library", + "tags": ["app:mobile", "scope:auth", "type:features"], + "// targets": "to see all targets run: nx show project mobile/auth/features/oauth-web-view --web", + "targets": {} +} diff --git a/libs/mobile/auth/features/oauth-web-view/src/index.ts b/libs/mobile/auth/features/oauth-web-view/src/index.ts new file mode 100644 index 0000000..f41a696 --- /dev/null +++ b/libs/mobile/auth/features/oauth-web-view/src/index.ts @@ -0,0 +1 @@ +export * from './lib'; diff --git a/libs/mobile/auth/features/google-sign-in-form/src/lib/components/oauth-web-view-modal/component.tsx b/libs/mobile/auth/features/oauth-web-view/src/lib/component.tsx similarity index 89% rename from libs/mobile/auth/features/google-sign-in-form/src/lib/components/oauth-web-view-modal/component.tsx rename to libs/mobile/auth/features/oauth-web-view/src/lib/component.tsx index a9134a2..a9213b9 100644 --- a/libs/mobile/auth/features/google-sign-in-form/src/lib/components/oauth-web-view-modal/component.tsx +++ b/libs/mobile/auth/features/oauth-web-view/src/lib/component.tsx @@ -10,20 +10,21 @@ import { FullScreenModal, View, } from '@open-webui-react-native/mobile/shared/ui/ui-kit'; -import { authService } from '@open-webui-react-native/shared/data-access/api'; +import { authService, Provider } from '@open-webui-react-native/shared/data-access/api'; import { appStorageService } from '@open-webui-react-native/shared/data-access/storage'; import { getApiUrl, getHost } from '@open-webui-react-native/shared/utils/config'; import { ToastService } from '@open-webui-react-native/shared/utils/toast-service'; import { mobileUserAgent, tokenCaptureScript } from './script'; -export type OauthWebViewModalProps = { +export type OauthWebViewProps = { isVisible: boolean; + provider: Provider; onClose: () => void; onGetToken: (token: string) => void; }; -export function OauthWebViewModal({ isVisible, onClose, onGetToken }: OauthWebViewModalProps): ReactElement { - const translate = useTranslation('AUTH.SIGN_IN.GOOGLE_FORM.OAUTH_WEB_VIEW_MODAL'); +export function OauthWebView({ isVisible, provider, onClose, onGetToken }: OauthWebViewProps): ReactElement { + const translate = useTranslation('AUTH.SIGN_IN.OAUTH_WEB_VIEW_MODAL'); const webViewRef = useRef(null); const isTokenCaptured = useRef(false); @@ -87,7 +88,7 @@ export function OauthWebViewModal({ isVisible, onClose, onGetToken }: OauthWebVi {isVisible && ( void; +} + +export function OdicSignIn({ providerName, onSuccess }: OdicSignInProps): ReactElement { + const translate = useTranslation('AUTH.SIGN_IN.OIDC_FORM'); + + const [isModalVisible, setIsModalVisible] = useState(false); + + const handleSignInPress = async (): Promise => setIsModalVisible(true); + + const handleCloseModal = (): void => setIsModalVisible(false); + + const handleToken = async (token: string): Promise => { + authState$.signIn(token); + // We need to delay to ensure the modal screen is closed before resetting navigation. + await new Promise((resolve) => { + handleCloseModal(); + setTimeout(() => resolve(), 100); + }); + onSuccess?.(); + }; + + return ( + + + + + ); +} diff --git a/libs/mobile/auth/features/odic-sign-in/src/lib/index.ts b/libs/mobile/auth/features/odic-sign-in/src/lib/index.ts new file mode 100644 index 0000000..bb82484 --- /dev/null +++ b/libs/mobile/auth/features/odic-sign-in/src/lib/index.ts @@ -0,0 +1 @@ +export * from './component'; diff --git a/libs/mobile/auth/features/odic-sign-in/tsconfig.json b/libs/mobile/auth/features/odic-sign-in/tsconfig.json new file mode 100644 index 0000000..ec74bfc --- /dev/null +++ b/libs/mobile/auth/features/odic-sign-in/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "extends": "../../../../../tsconfig.base.json" +} diff --git a/libs/mobile/auth/features/odic-sign-in/tsconfig.lib.json b/libs/mobile/auth/features/odic-sign-in/tsconfig.lib.json new file mode 100644 index 0000000..27f2b91 --- /dev/null +++ b/libs/mobile/auth/features/odic-sign-in/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../../dist/out-tsc", + "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/mobile/auth/features/sign-in/src/lib/component.tsx b/libs/mobile/auth/features/sign-in/src/lib/component.tsx index 88d132d..a2d9343 100644 --- a/libs/mobile/auth/features/sign-in/src/lib/component.tsx +++ b/libs/mobile/auth/features/sign-in/src/lib/component.tsx @@ -2,6 +2,7 @@ import { useTranslation } from '@ronas-it/react-native-common-modules/i18n'; import { ReactElement, useState } from 'react'; import { EmailSignInForm } from '@open-webui-react-native/mobile/auth/features/email-sign-in-form'; import { GoogleSignInForm } from '@open-webui-react-native/mobile/auth/features/google-sign-in-form'; +import { OdicSignIn } from '@open-webui-react-native/mobile/auth/features/odic-sign-in'; import { AppText, View } from '@open-webui-react-native/mobile/shared/ui/ui-kit'; import { Provider } from '@open-webui-react-native/shared/data-access/api'; import { isTestApiUrl } from '@open-webui-react-native/shared/utils/config'; @@ -15,9 +16,10 @@ export function SignIn(props: SignInProps): ReactElement { const { onSuccess } = props; const translate = useTranslation('AUTH.SIGN_IN'); const [apiUrlInput, setApiUrlInput] = useState(); - const [providers, setProviders] = useState>([]); + const [providers, setProviders] = useState>>({}); - const showGoogleSignIn = !isTestApiUrl(apiUrlInput) && providers.includes(Provider.GOOGLE); + const showGoogleSignIn = !isTestApiUrl(apiUrlInput) && Provider.GOOGLE in providers; + const showOidcSignIn = !isTestApiUrl(apiUrlInput) && Provider.OIDC in providers; const handleSuccess = (): void => { onSuccess(); @@ -41,6 +43,11 @@ export function SignIn(props: SignInProps): ReactElement { )} + {showOidcSignIn && ( + + + + )} ); } diff --git a/libs/shared/data-access/api/src/lib/app-configuration/enums/provider.ts b/libs/shared/data-access/api/src/lib/app-configuration/enums/provider.ts index 69d7dcf..bea5e3a 100644 --- a/libs/shared/data-access/api/src/lib/app-configuration/enums/provider.ts +++ b/libs/shared/data-access/api/src/lib/app-configuration/enums/provider.ts @@ -3,4 +3,5 @@ export enum Provider { MICROSOFT = 'microsoft', GITHUB = 'github', FEISHU = 'feishu', + OIDC = 'oidc', } diff --git a/libs/shared/utils/config/src/get-api-url.ts b/libs/shared/utils/config/src/get-api-url.ts index b4be194..68e62e8 100644 --- a/libs/shared/utils/config/src/get-api-url.ts +++ b/libs/shared/utils/config/src/get-api-url.ts @@ -1,4 +1,5 @@ - +/* eslint-disable @nx/enforce-module-boundaries */ + import { appStorageService } from '@open-webui-react-native/shared/data-access/storage'; import { appEnv } from '@open-webui-react-native/shared/utils/app-env'; diff --git a/package-lock.json b/package-lock.json index 9b3b195..beeb9bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,6 +117,7 @@ "@nx/jest": "20.4.6", "@nx/js": "20.4.6", "@nx/workspace": "20.4.6", + "@react-native/jest-preset": "^0.85.3", "@ronas-it/nx-generators": "^0.14.0", "@stylistic/eslint-plugin": "^2.12.1", "@swc-node/register": "~1.9.1", @@ -2606,9 +2607,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2630,9 +2628,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2654,9 +2649,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2678,9 +2670,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6544,9 +6533,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6564,9 +6550,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6584,9 +6567,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6604,9 +6584,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6921,9 +6898,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6945,9 +6919,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6969,9 +6940,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -6993,9 +6961,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7017,9 +6982,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7041,9 +7003,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7668,6 +7627,26 @@ "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, + "node_modules/@react-native/jest-preset": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/jest-preset/-/jest-preset-0.85.3.tgz", + "integrity": "sha512-ALPSrM0q2fU+5AXcOXzDKx7rxVKPMvygAZfsTWLdrGRVWIqf/HEfM0R8euQqIKUqmEuQ1TxMWN+px3h6gc4vow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^29.7.0", + "@react-native/js-polyfills": "0.85.3", + "babel-jest": "^29.7.0", + "jest-environment-node": "^29.7.0", + "regenerator-runtime": "^0.13.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, "node_modules/@react-native/js-polyfills": { "version": "0.85.3", "license": "MIT", @@ -7927,9 +7906,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7944,9 +7920,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7961,9 +7934,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7978,9 +7948,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8887,9 +8854,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8907,9 +8871,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8927,9 +8888,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -8947,9 +8905,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -10046,9 +10001,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -10063,9 +10015,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -10080,9 +10029,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -10097,9 +10043,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -10114,9 +10057,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -10131,9 +10071,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -10148,9 +10085,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -10165,9 +10099,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -10182,9 +10113,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -10199,9 +10127,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -20242,9 +20167,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -20265,9 +20187,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -20288,9 +20207,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -20311,9 +20227,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -24473,9 +24386,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -24496,9 +24406,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -24519,9 +24426,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -24542,9 +24446,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/package.json b/package.json index 74f2528..7055c6a 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "@nx/jest": "20.4.6", "@nx/js": "20.4.6", "@nx/workspace": "20.4.6", + "@react-native/jest-preset": "^0.85.3", "@ronas-it/nx-generators": "^0.14.0", "@stylistic/eslint-plugin": "^2.12.1", "@swc-node/register": "~1.9.1", diff --git a/tsconfig.base.json b/tsconfig.base.json index 5f7aff2..f80b811 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -29,6 +29,12 @@ "@open-webui-react-native/mobile/auth/features/google-sign-in-form": [ "libs/mobile/auth/features/google-sign-in-form/src/index.ts" ], + "@open-webui-react-native/mobile/auth/features/oauth-web-view": [ + "libs/mobile/auth/features/oauth-web-view/src/index.ts" + ], + "@open-webui-react-native/mobile/auth/features/odic-sign-in": [ + "libs/mobile/auth/features/odic-sign-in/src/index.ts" + ], "@open-webui-react-native/mobile/auth/features/sign-in": ["libs/mobile/auth/features/sign-in/src/index.ts"], "@open-webui-react-native/mobile/chat/data-access/archived-chats-filter-state": [ "libs/mobile/chat/data-access/archived-chats-filter-state/src/index.ts"