Skip to content

Commit

Permalink
Implemented authentication on Android.
Browse files Browse the repository at this point in the history
  • Loading branch information
ashleydavis committed Apr 12, 2024
1 parent b4a57a9 commit e967d33
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 26 deletions.
2 changes: 2 additions & 0 deletions mobile/android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')

}
Expand Down
7 changes: 7 additions & 0 deletions mobile/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/custom_url_scheme" />
</intent-filter>

</activity>

<provider
Expand Down
8 changes: 7 additions & 1 deletion mobile/android/capacitor.settings.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
project(':capacitor-android').projectDir = new File('../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/android/capacitor')

include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/app/android')

include ':capacitor-browser'
project(':capacitor-browser').projectDir = new File('../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/browser/android')

include ':capacitor-splash-screen'
project(':capacitor-splash-screen').projectDir = new File('../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/splash-screen/android')
45 changes: 42 additions & 3 deletions mobile/frontend/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import React from "react";
import React, { useEffect } from "react";
import { HashRouter } from "react-router-dom";
import { ApiContextProvider, AuthContextProvider, CloudGallerySourceContextProvider, GalleryContextProvider, Main, SearchContextProvider, UploadContextProvider } from "user-interface";
import { ComputerPage } from "./pages/computer";
import { ComputerGallerySourceContextProvider } from "./context/source/computer-gallery-source-context";
import { ScanContextProvider } from "./context/scan-context";
import { Auth0Provider } from "@auth0/auth0-react";
import { Auth0Provider, useAuth0 } from "@auth0/auth0-react";
import { App as CapacitorApp } from "@capacitor/app";
import { Browser } from "@capacitor/browser";

export function App() {
return (
<HashRouter>
<Auth0Provider
domain={process.env.AUTH0_DOMAIN as string}
clientId={process.env.AUTH0_CLIENT_ID as string}
useRefreshTokens={true}
useRefreshTokensFallback={false}
authorizationParams={{
audience: process.env.AUTH0_AUDIENCE as string,
redirect_uri: `${process.env.AUTH0_ORIGIN}/on_login`,
}}
>
<AuthContextProvider>
<AuthContextProvider
openUrl={async (url: string) => {
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"
});
}}
>
<HandleAuthCallback />
<ApiContextProvider>
<SearchContextProvider>
<CloudGallerySourceContextProvider>
Expand All @@ -38,3 +55,25 @@ export function App() {
</HashRouter>
);
}

//
// 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 <></>;
}
8 changes: 5 additions & 3 deletions mobile/ios/App/Podfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
require_relative '../../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/ios/scripts/pods_helpers'

platform :ios, '13.0'
use_frameworks!
Expand All @@ -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/@[email protected]_@[email protected]/node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/ios'
pod 'CapacitorApp', :path => '../../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/app'
pod 'CapacitorBrowser', :path => '../../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/browser'
pod 'CapacitorSplashScreen', :path => '../../../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@capacitor/splash-screen'
end

Expand Down
2 changes: 2 additions & 0 deletions mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
69 changes: 60 additions & 9 deletions packages/user-interface/src/context/auth-context.tsx
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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.
//
Expand All @@ -33,15 +43,21 @@ export interface IAuthContext {
//
logout(): Promise<void>;


}

const AuthContext = createContext<IAuthContext | undefined>(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> | void;

children: ReactNode | ReactNode[];
}

export function AuthContextProvider({ children }: IProps) {
export function AuthContextProvider({ openUrl, children }: IAuthContextProviderProps) {

const {
isLoading,
Expand All @@ -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.
//
Expand All @@ -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;
Expand All @@ -87,7 +137,9 @@ export function AuthContextProvider({ children }: IProps) {
// Logs in.
//
async function login(): Promise<void> {
await loginWithRedirect({});
await loginWithRedirect({
openUrl,
});
}

//
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/user-interface/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export function Main({ computerPage }: IMainProps) {
onClick={login}
>
<i className="w-5 fa-solid fa-right-to-bracket"></i>
<span className="ml-2">Log in</span>
<span className="hidden sm:block ml-2">Log in</span>
</button>
</div>
)}
Expand All @@ -192,7 +192,7 @@ export function Main({ computerPage }: IMainProps) {
onClick={logout}
>
<i className="w-5 fa-solid fa-right-from-bracket"></i>
<span className="ml-1">Log out</span>
<span className="hidden sm:block ml-1">Log out</span>
</button>
</div>
)}
Expand Down
38 changes: 30 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e967d33

Please sign in to comment.