From e967d3366e188670e0a8f6c05a92a7fa1f2c2f67 Mon Sep 17 00:00:00 2001 From: Ashley Davis Date: Sat, 13 Apr 2024 08:44:32 +1000 Subject: [PATCH] Implemented authentication on Android. --- mobile/android/app/capacitor.build.gradle | 2 + .../android/app/src/main/AndroidManifest.xml | 7 ++ mobile/android/capacitor.settings.gradle | 8 ++- mobile/frontend/src/app.tsx | 45 +++++++++++- mobile/ios/App/Podfile | 8 ++- mobile/package.json | 2 + .../src/context/auth-context.tsx | 69 ++++++++++++++++--- packages/user-interface/src/main.tsx | 4 +- pnpm-lock.yaml | 38 +++++++--- 9 files changed, 157 insertions(+), 26 deletions(-) diff --git a/mobile/android/app/capacitor.build.gradle b/mobile/android/app/capacitor.build.gradle index 259821da..8b96a680 100644 --- a/mobile/android/app/capacitor.build.gradle +++ b/mobile/android/app/capacitor.build.gradle @@ -9,6 +9,8 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { + implementation project(':capacitor-app') + implementation project(':capacitor-browser') implementation project(':capacitor-splash-screen') } diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index fa6454a7..4442749e 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -23,6 +23,13 @@ + + + + + + + - + { + console.log(`>>>> Opening URL: ${url}`); + // + // Redirect using Capacitor's Browser plugin + // https://auth0.com/docs/quickstart/native/ionic-react/01-login + // + await Browser.open({ + url, + windowName: "_self" + }); + }} + > + @@ -38,3 +55,25 @@ export function App() { ); } + +// +// This component handles the Auth0 callback. +// +function HandleAuthCallback() { + const { handleRedirectCallback } = useAuth0(); + + useEffect(() => { + // Handles the 'appUrlOpen' event and calls `handleRedirectCallback`. + CapacitorApp.addListener('appUrlOpen', async ({ url }) => { + console.log(`>>>> Handling appUrlOpen for URL: ${url}`); + if (url.includes('state') && (url.includes('code') || url.includes('error'))) { + await handleRedirectCallback(url); + } + + // No-op on Android. + await Browser.close(); + }); + }, [handleRedirectCallback]); + + return <>; +} \ No newline at end of file diff --git a/mobile/ios/App/Podfile b/mobile/ios/App/Podfile index 02330bef..c1b73a14 100644 --- a/mobile/ios/App/Podfile +++ b/mobile/ios/App/Podfile @@ -1,4 +1,4 @@ -require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' +require_relative '../../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios/scripts/pods_helpers' platform :ios, '13.0' use_frameworks! @@ -9,8 +9,10 @@ use_frameworks! install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods - pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' - pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' + pod 'Capacitor', :path => '../../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios' + pod 'CapacitorCordova', :path => '../../../node_modules/.pnpm/@capacitor+ios@5.7.4_@capacitor+core@5.7.4/node_modules/@capacitor/ios' + pod 'CapacitorApp', :path => '../../../node_modules/.pnpm/@capacitor+app@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/app' + pod 'CapacitorBrowser', :path => '../../../node_modules/.pnpm/@capacitor+browser@5.2.0_@capacitor+core@5.7.4/node_modules/@capacitor/browser' pod 'CapacitorSplashScreen', :path => '../../../node_modules/.pnpm/@capacitor+splash-screen@5.0.7_@capacitor+core@5.7.4/node_modules/@capacitor/splash-screen' end diff --git a/mobile/package.json b/mobile/package.json index 5955cb75..e053a1ac 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -12,6 +12,8 @@ }, "dependencies": { "@capacitor/android": "^5.7.4", + "@capacitor/app": "^5.0.7", + "@capacitor/browser": "^5.2.0", "@capacitor/core": "^5.7.4", "@capacitor/ios": "^5.7.4", "@capacitor/splash-screen": "^5.0.7" diff --git a/packages/user-interface/src/context/auth-context.tsx b/packages/user-interface/src/context/auth-context.tsx index 9598baa2..a0374f0f 100644 --- a/packages/user-interface/src/context/auth-context.tsx +++ b/packages/user-interface/src/context/auth-context.tsx @@ -1,5 +1,5 @@ import React, { createContext, ReactNode, useContext, useEffect, useRef } from "react"; -import { useAuth0 } from "@auth0/auth0-react"; +import { useAuth0, User } from "@auth0/auth0-react"; export interface IAuthContext { @@ -13,6 +13,16 @@ export interface IAuthContext { // isAuthenticated: boolean; + // + // The logged in user. + // + user: User | undefined; + + // + // The authentication error, if one occured. + // + error: Error | undefined; + // // Loads the users access token. // @@ -33,15 +43,21 @@ export interface IAuthContext { // logout(): Promise; + } const AuthContext = createContext(undefined); -export interface IProps { +export interface IAuthContextProviderProps { + // + // Used to control the login/logout redirect and not rely on the SDK to do the actual redirect. + // + openUrl?: (url: string) => Promise | void; + children: ReactNode | ReactNode[]; } -export function AuthContextProvider({ children }: IProps) { +export function AuthContextProvider({ openUrl, children }: IAuthContextProviderProps) { const { isLoading, @@ -53,6 +69,29 @@ export function AuthContextProvider({ children }: IProps) { getAccessTokenSilently, } = useAuth0(); + useEffect(() => { + validateAuthSettings(); + }, []); + + useEffect(() => { + if (isAuthenticated) { + console.log(`User is authenticated, loading access token.`); + loadToken() + .then(() => { + console.log(`Access token loaded.`); + }) + .catch(error => { + token.current = undefined; + console.error(`Error loading access token:`); + console.error(error); + }); + } + else { + console.log(`User is not authenticated, clearing access token.`) + token.current = undefined; + } + }, [isAuthenticated]); + // // The user's access token. // @@ -73,11 +112,22 @@ export function AuthContextProvider({ children }: IProps) { } } + function stackTrace() { + function getStackTrace() { + const error = new Error(); + return error.stack; + } + + console.log(getStackTrace()); + } + // // Gets an access token for the user. // function getToken(): string { if (!token.current) { + console.error(`Access token is not loaded!`); + stackTrace(); throw new Error(`Access token is not loaded!`); } return token.current; @@ -87,7 +137,9 @@ export function AuthContextProvider({ children }: IProps) { // Logs in. // async function login(): Promise { - await loginWithRedirect({}); + await loginWithRedirect({ + openUrl, + }); } // @@ -97,17 +149,16 @@ export function AuthContextProvider({ children }: IProps) { _logout({ logoutParams: { returnTo: `${process.env.AUTH0_ORIGIN}/on_logout`, - } + }, + openUrl, }); } - useEffect(() => { - validateAuthSettings(); - }, []); - const value: IAuthContext = { isLoading, isAuthenticated, + user, + error, loadToken, getToken, login, diff --git a/packages/user-interface/src/main.tsx b/packages/user-interface/src/main.tsx index f33f0720..a642b767 100644 --- a/packages/user-interface/src/main.tsx +++ b/packages/user-interface/src/main.tsx @@ -181,7 +181,7 @@ export function Main({ computerPage }: IMainProps) { onClick={login} > - Log in + Log in )} @@ -192,7 +192,7 @@ export function Main({ computerPage }: IMainProps) { onClick={logout} > - Log out + Log out )} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4783585..cef4c336 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -324,14 +324,20 @@ importers: mobile: dependencies: '@capacitor/android': - specifier: ^5.7.2 - version: 5.7.2(@capacitor/core@5.7.4) + specifier: ^5.7.4 + version: 5.7.4(@capacitor/core@5.7.4) + '@capacitor/app': + specifier: ^5.0.7 + version: 5.0.7(@capacitor/core@5.7.4) + '@capacitor/browser': + specifier: ^5.2.0 + version: 5.2.0(@capacitor/core@5.7.4) '@capacitor/core': specifier: ^5.7.4 version: 5.7.4 '@capacitor/ios': - specifier: ^5.7.2 - version: 5.7.2(@capacitor/core@5.7.4) + specifier: ^5.7.4 + version: 5.7.4(@capacitor/core@5.7.4) '@capacitor/splash-screen': specifier: ^5.0.7 version: 5.0.7(@capacitor/core@5.7.4) @@ -893,14 +899,30 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@capacitor/android@5.7.2(@capacitor/core@5.7.4): - resolution: {integrity: sha512-T4U+15R/1PyokW0Le92j7AV19kuO25his2ymF2xf2I04fZUDj8RjmXA+za7i3K8vhxtKkTdY2dPAywrfNAM09Q==} + /@capacitor/android@5.7.4(@capacitor/core@5.7.4): + resolution: {integrity: sha512-9ETwrCaLaimLHbwGpgfsPS9cHcPMFLmKFdlYsFsYIusMO6aOrcQTA9Q4xVAkr55ava4Wk+pVRniRYsekrbOLdw==} peerDependencies: '@capacitor/core': ^5.7.0 dependencies: '@capacitor/core': 5.7.4 dev: false + /@capacitor/app@5.0.7(@capacitor/core@5.7.4): + resolution: {integrity: sha512-oad0jwQu+vgQDukeS9UV56yG10dlxkAGGl26IQpZlTmg3dTI9qSJtvhmlLfkF0nEtoj5IsVQUPE+NLH1oZkgGQ==} + peerDependencies: + '@capacitor/core': ^5.0.0 + dependencies: + '@capacitor/core': 5.7.4 + dev: false + + /@capacitor/browser@5.2.0(@capacitor/core@5.7.4): + resolution: {integrity: sha512-pWFrsNPOG6DlDWGl+bJqTWLvoJ2vvV27LKaSZ/A7S4td4bK3umwZmgGlp8ispjpYiurjliT25Ae8I+ywMV8X9w==} + peerDependencies: + '@capacitor/core': ^5.0.0 + dependencies: + '@capacitor/core': 5.7.4 + dev: false + /@capacitor/cli@5.7.4: resolution: {integrity: sha512-Cn7ndfMHWfMT+A/wRoeac4/mAxZWNTlZoD1Mn7UQyVV+iGxZB2JpS9omqha7gBN4xrAuTi/X9FqkzrmR+1V96A==} engines: {node: '>=16.0.0'} @@ -933,8 +955,8 @@ packages: tslib: 2.6.2 dev: false - /@capacitor/ios@5.7.2(@capacitor/core@5.7.4): - resolution: {integrity: sha512-msh+Kqjv/MyVCrSH0zVtwxptXnsgky4FENUq+Xdaa1pqEglmpHlUKod1Jf7qhfAhTLhHPyokOZMvaIyTtoSwCA==} + /@capacitor/ios@5.7.4(@capacitor/core@5.7.4): + resolution: {integrity: sha512-tQH24WMSYVKYr/Jl1gFImooQmu8OdXUFHoDaPV1WpZIiwbwxbTdwOXeLlGes5U8B8t7xuxTWMWMDt3IwRlDbhQ==} peerDependencies: '@capacitor/core': ^5.7.0 dependencies: