Skip to content

Commit

Permalink
feat(web): add aws cognito support in auth (#449)
Browse files Browse the repository at this point in the history
  • Loading branch information
pyshx committed Jun 21, 2023
1 parent ff1daf5 commit 4144730
Show file tree
Hide file tree
Showing 15 changed files with 2,572 additions and 87 deletions.
1 change: 1 addition & 0 deletions web/e2e/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const config = {
authClientId: process.env["REEARTH_WEB_AUTH0_CLIENT_ID"],
authUrl: process.env["REEARTH_WEB_AUTH0_DOMAIN"],
signUpSecret: process.env["REEARTH_WEB_E2E_SIGNUP_SECRET"],
authProvider: process.env["REEARTH_WEB_AUTH_PROVIDER"],
developerMode: process.env["REEARTH_WEB_DEVELOPER_MODE"],
};

Expand Down
7 changes: 7 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@
</body>

</html>

<script>
// https://github.com/aws-amplify/amplify-js/issues/678
if (global === undefined) {
var global = window;
}
</script>
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"@ungap/event-target": "0.2.4",
"apollo-link-sentry": "3.2.3",
"apollo-upload-client": "17.0.0",
"aws-amplify": "5.2.2",
"array-move": "4.0.0",
"axios": "1.4.0",
"cesium": "1.105.2",
Expand Down
6 changes: 3 additions & 3 deletions web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import Loading from "@reearth/classic/components/atoms/Loading";
import NotificationBanner from "@reearth/classic/components/organisms/Notification";
import { Provider as I18nProvider } from "@reearth/services/i18n";

import { Provider as Auth0Provider } from "./services/auth";
import { AuthProvider } from "./services/auth";
import { Provider as GqlProvider } from "./services/gql";
import { AppRoutes } from "./services/routing";
import { Provider as ThemeProvider } from "./services/theme";

export default function App() {
return (
<Auth0Provider>
<AuthProvider>
<GqlProvider>
<ThemeProvider>
<I18nProvider>
Expand All @@ -22,6 +22,6 @@ export default function App() {
</I18nProvider>
</ThemeProvider>
</GqlProvider>
</Auth0Provider>
</AuthProvider>
);
}
1 change: 1 addition & 0 deletions web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ window.React = React;
window.ReactDOM = ReactDOM;

loadConfig().finally(async () => {
await import("./services/config/aws.config");
const element = document.getElementById("root");
if (!element) throw new Error("root element is not found");

Expand Down
8 changes: 8 additions & 0 deletions web/src/services/auth/authHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type AuthHook = {
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
getAccessToken: () => Promise<string>;
login: () => void;
logout: () => void;
};
28 changes: 28 additions & 0 deletions web/src/services/auth/authOAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useAuth0 } from "@auth0/auth0-react";

import { e2eAccessToken } from "@reearth/services/config";

import type { AuthHook } from "./authHook";

export const errorKey = "reeartherror";

export const useAuth0Auth = (): AuthHook => {
const { isAuthenticated, error, isLoading, loginWithRedirect, logout, getAccessTokenSilently } =
useAuth0();

return {
isAuthenticated: !!e2eAccessToken() || (isAuthenticated && !error),
isLoading,
error: error?.message ?? null,
getAccessToken: () => getAccessTokenSilently(),
login: () => loginWithRedirect(),
logout: () =>
logout({
logoutParams: {
returnTo: error
? `${window.location.origin}?${errorKey}=${encodeURIComponent(error?.message)}`
: window.location.origin,
},
}),
};
};
51 changes: 51 additions & 0 deletions web/src/services/auth/authProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Auth0Provider } from "@auth0/auth0-react";
import React, { createContext, ReactNode } from "react";

import type { AuthHook } from "./authHook";
import { useAuth0Auth } from "./authOAuth";
import { useCognitoAuth } from "./cognitoAuth";

export const AuthContext = createContext<AuthHook | null>(null);

const Auth0Wrapper = ({ children }: { children: ReactNode }) => {
const auth = useAuth0Auth();
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
};

const CognitoWrapper = ({ children }: { children: ReactNode }) => {
const auth = useCognitoAuth();
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
};

export const AuthProvider: React.FC<{ children?: ReactNode }> = ({ children }) => {
const authProvider = window.REEARTH_CONFIG?.authProvider;

if (authProvider === "auth0") {
const domain = window.REEARTH_CONFIG?.auth0Domain;
const clientId = window.REEARTH_CONFIG?.auth0ClientId;
const audience = window.REEARTH_CONFIG?.auth0Audience;

return domain && clientId ? (
<Auth0Provider
domain={domain}
clientId={clientId}
authorizationParams={{
audience: audience,
scope: "openid profile email offline_access",
redirect_uri: window.location.origin,
}}
useRefreshTokens
useRefreshTokensFallback
cacheLocation="localstorage">
<Auth0Wrapper>{children}</Auth0Wrapper>
</Auth0Provider>
) : null;
}

if (authProvider === "cognito") {
// No specific provider needed for Cognito/AWS Amplify
return <CognitoWrapper>{children}</CognitoWrapper>;
}

return <>{children}</>; // or some default fallback
};
52 changes: 52 additions & 0 deletions web/src/services/auth/cognitoAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Auth } from "@aws-amplify/auth";
import { useState, useEffect } from "react";

import type { AuthHook } from "./authHook";

export const useCognitoAuth = (): AuthHook => {
const [user, setUser] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);

useEffect(() => {
const checkUser = async () => {
try {
const cognitoUser = await Auth.currentAuthenticatedUser();
setUser(cognitoUser);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError(JSON.stringify(err));
}
}
setIsLoading(false);
};

checkUser();
}, []);

const getAccessToken = async () => {
const session = await Auth.currentSession();
return session.getIdToken().getJwtToken();
};

const login = () => {
Auth.federatedSignIn();
};

const logout = async () => {
try {
await Auth.signOut();
setUser(null);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError(JSON.stringify(err));
}
}
};

return { isAuthenticated: !!user, isLoading, error, getAccessToken, login, logout };
};
6 changes: 3 additions & 3 deletions web/src/services/auth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { ReactNode } from "react";

import GlobalModal from "@reearth/classic/components/organisms/GlobalModal";

import { useAuthenticationRequired } from "./hooks";
import { useAuthenticationRequired } from "./useAuth";

export { default as Provider } from "./provider";
export { default as useAuth, useCleanUrl, useAuthenticationRequired } from "./hooks";
export { AuthProvider } from "./authProvider";
export { useAuth, useCleanUrl, useAuthenticationRequired } from "./useAuth";

export { withAuthenticationRequired } from "@auth0/auth0-react";

Expand Down
29 changes: 0 additions & 29 deletions web/src/services/auth/provider.tsx

This file was deleted.

42 changes: 16 additions & 26 deletions web/src/services/auth/hooks.ts → web/src/services/auth/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
import { useAuth0 } from "@auth0/auth0-react";
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";

import { e2eAccessToken } from "@reearth/services/config";
import { useAuth0Auth } from "./authOAuth";
import { AuthContext } from "./authProvider";

export const errorKey = "reeartherror";

export default function useAuth() {
const { isAuthenticated, error, isLoading, loginWithRedirect, logout, getAccessTokenSilently } =
useAuth0();

return {
isAuthenticated: !!e2eAccessToken() || (isAuthenticated && !error),
isLoading,
error: error?.message,
getAccessToken: () => getAccessTokenSilently(),
login: () => loginWithRedirect(),
logout: () =>
logout({
logoutParams: {
returnTo: error
? `${window.location.origin}?${errorKey}=${encodeURIComponent(error?.message)}`
: window.location.origin,
},
}),
};
}
export const useAuth = () => {
let auth = useContext(AuthContext);

if (!auth) {
// eslint-disable-next-line react-hooks/rules-of-hooks
auth = useAuth0Auth();
}

return auth;
};

export function useCleanUrl(): [string | undefined, boolean] {
const { isLoading } = useAuth0();
const { isAuthenticated, isLoading } = useAuth();
const [error, setError] = useState<string>();
const [done, setDone] = useState(false);

useEffect(() => {
if (isLoading) return; // ensure that Auth0 can detect errors
if (isLoading) return;

const params = new URLSearchParams(window.location.search);

Expand All @@ -50,7 +40,7 @@ export function useCleanUrl(): [string | undefined, boolean] {

history.replaceState(null, document.title, url);
setDone(true);
}, [isLoading]);
}, [isAuthenticated, isLoading]);

return [error, done];
}
Expand Down
30 changes: 30 additions & 0 deletions web/src/services/config/aws.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Amplify } from "aws-amplify";

const authProvider = window.REEARTH_CONFIG?.authProvider;
if (authProvider === "cognito") {
const cognitoRegion = window.REEARTH_CONFIG?.cognitoRegion;
const cognitoUserPoolId = window.REEARTH_CONFIG?.cognitoUserPoolId;
const cognitoUserPoolWebClientId = window.REEARTH_CONFIG?.cognitoUserPoolWebClientId;
const cognitoOauthScope = window.REEARTH_CONFIG?.cognitoOauthScope?.split(", ");
const cognitoOauthDomain = window.REEARTH_CONFIG?.cognitoOauthDomain;
const cognitoOauthRedirectSignIn = window.REEARTH_CONFIG?.cognitoOauthRedirectSignIn;
const cognitoOauthRedirectSignOut = window.REEARTH_CONFIG?.cognitoOauthRedirectSignOut;
const cognitoOauthResponseType = window.REEARTH_CONFIG?.cognitoOauthResponseType;

const config = {
Auth: {
region: cognitoRegion,
userPoolId: cognitoUserPoolId,
userPoolWebClientId: cognitoUserPoolWebClientId,
oauth: {
scope: cognitoOauthScope,
domain: cognitoOauthDomain,
redirectSignIn: cognitoOauthRedirectSignIn,
redirectSignOut: cognitoOauthRedirectSignOut,
responseType: cognitoOauthResponseType,
},
},
};

Amplify.configure(config);
}
10 changes: 10 additions & 0 deletions web/src/services/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ export type Config = {
auth0ClientId?: string;
auth0Domain?: string;
auth0Audience?: string;
authProvider?: string;
cognitoRegion?: string;
cognitoUserPoolId?: string;
cognitoUserPoolWebClientId?: string;
cognitoOauthScope?: string;
cognitoOauthDomain?: string;
cognitoOauthRedirectSignIn?: string;
cognitoOauthRedirectSignOut?: string;
cognitoOauthResponseType?: string;
googleApiKey?: string;
googleClientId?: string;
sentryDsn?: string;
Expand Down Expand Up @@ -64,6 +73,7 @@ export const defaultConfig: Config = {
auth0Audience: "http://localhost:8080",
auth0Domain: "http://localhost:8080",
auth0ClientId: "reearth-authsrv-client-default",
authProvider: "auth0",
policy: {
modalTitle: {
en: "Re:Earth Cloud",
Expand Down
Loading

0 comments on commit 4144730

Please sign in to comment.