Skip to content

Commit 7ddb4d5

Browse files
Feat/login UI (unitycatalog#450)
**PR Checklist** - [x ] A description of the changes is added to the description of this PR. - [x ] If there is a related issue, make sure it is linked to this PR. - [ ] If you've fixed a bug or added code that should be tested, add tests! - [ ] If you've added or modified a feature, documentation in `docs` is updated **Description of changes** Allows users to login via google auth once auth configuration is in place and users have been added to users table. <img width="1064" alt="Screenshot 2024-09-11 at 12 37 00 PM" src="https://github.com/user-attachments/assets/7f5db283-2604-4e6a-a179-f17d6c5537d1"> <img width="323" alt="Screenshot 2024-09-11 at 12 38 38 PM" src="https://github.com/user-attachments/assets/1824e68a-6a76-4f11-87f4-d49522a54a16"> Closes unitycatalog#430
1 parent 19c8dd5 commit 7ddb4d5

File tree

6 files changed

+51
-35
lines changed

6 files changed

+51
-35
lines changed

server/src/main/java/io/unitycatalog/server/persist/utils/ServerPropertiesUtils.java

+2
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public Map<String, ADLSStorageConfig> getAdlsConfigurations() {
126126

127127
// Get a property value by key with a default value
128128
public String getProperty(String key, String defaultValue) {
129+
if (System.getProperty(key) != null) return System.getProperty(key);
130+
if (System.getenv().containsKey(key)) return System.getenv(key);
129131
return properties.getProperty(key, defaultValue);
130132
}
131133
}

server/src/main/java/io/unitycatalog/server/service/AuthService.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.unitycatalog.server.exception.GlobalExceptionHandler;
1717
import io.unitycatalog.server.exception.OAuthInvalidRequestException;
1818
import io.unitycatalog.server.persist.UserRepository;
19+
import io.unitycatalog.server.persist.utils.ServerPropertiesUtils;
1920
import io.unitycatalog.server.security.SecurityContext;
2021
import io.unitycatalog.server.utils.JwksOperations;
2122
import lombok.Builder;
@@ -126,6 +127,13 @@ public HttpResponse grantToken(OAuthTokenExchangeRequest request) {
126127
ErrorCode.INVALID_ARGUMENT, "Actor tokens not currently supported");
127128
}
128129

130+
String authorization =
131+
ServerPropertiesUtils.getInstance().getProperty("server.authorization", "disable");
132+
if (!authorization.equals("enable")) {
133+
throw new OAuthInvalidRequestException(
134+
ErrorCode.INVALID_ARGUMENT, "Authorization is disabled");
135+
}
136+
129137
DecodedJWT decodedJWT = JWT.decode(request.getSubjectToken());
130138
String issuer = decodedJWT.getClaim("iss").asString();
131139
String keyId = decodedJWT.getHeaderClaim("kid").asString();
@@ -162,7 +170,7 @@ private static void verifyPrincipal(DecodedJWT decodedJWT) {
162170

163171
try {
164172
User user = USER_REPOSITORY.getUserByEmail(subject);
165-
if (user != null && user.getState() != User.StateEnum.ENABLED) {
173+
if (user != null && user.getState() != User.StateEnum.DISABLED) {
166174
return;
167175
}
168176
} catch (Exception e) {

ui/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ You will also see any lint errors in the console.
3030

3131
OSS Unity Catalog supports Sign in with Google. You can authenticate with Google by clicking the "Sign in with Google" button on the login page, once OAuth has been configured. To configure this, follow the steps to obtain a [Google API Client ID](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid) and configure your OAuth consent screen.
3232

33+
NOTE: The google client ID should match what is configured in the server.properties file on the server side. See README in root directory. In order for login to work, authentication must be enabled on server side AND UI side and users must be added to users table.
34+
3335
Once you have the client ID, add it to the `.env` file after `REACT_APP_GOOGLE_CLIENT_ID=` and change the `REACT_APP_GOOGLE_AUTH_ENABLED` flag from false to true. Restart yarn.

ui/src/App.tsx

+26-24
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ const router = createBrowserRouter([
6161
]);
6262

6363
function AppProvider() {
64-
const { accessToken, logout } = useAuth();
64+
const { accessToken, logout, currentUser } = useAuth();
6565
const navigate = useNavigate();
66+
const authEnabled = process.env.REACT_APP_GOOGLE_AUTH_ENABLED === 'true';
6667
const loggedIn = accessToken !== '';
6768

6869
const profileMenuItems = useMemo(
@@ -77,8 +78,8 @@ function AppProvider() {
7778
cursor: 'default',
7879
}}
7980
>
80-
<Typography.Text>User name here</Typography.Text>
81-
<Typography.Text>[email protected]</Typography.Text>
81+
<Typography.Text>{currentUser?.displayName}</Typography.Text>
82+
<Typography.Text>{currentUser?.emails[0]?.value}</Typography.Text>
8283
</div>
8384
),
8485
},
@@ -91,14 +92,13 @@ function AppProvider() {
9192
onClick: () => logout().then(() => navigate('/')),
9293
},
9394
],
94-
[],
95+
[currentUser],
9596
);
9697

9798
// commenting login UI for now until repositories are merged
98-
// return !loggedIn ? (
99-
// <Login />
100-
// ) : (
101-
return (
99+
return authEnabled && !loggedIn ? (
100+
<Login />
101+
) : (
102102
<ConfigProvider
103103
theme={{
104104
components: {
@@ -143,22 +143,24 @@ function AppProvider() {
143143
style={{ flex: 1, minWidth: 0 }}
144144
/>
145145
</div>
146-
{/*<div>*/}
147-
{/* <Dropdown*/}
148-
{/* menu={{ items: profileMenuItems }}*/}
149-
{/* trigger={['click']}*/}
150-
{/* placement={'bottomRight'}*/}
151-
{/* >*/}
152-
{/* <Avatar*/}
153-
{/* icon={<UserOutlined />}*/}
154-
{/* style={{*/}
155-
{/* backgroundColor: 'white',*/}
156-
{/* color: 'black',*/}
157-
{/* cursor: 'pointer',*/}
158-
{/* }}*/}
159-
{/* />*/}
160-
{/* </Dropdown>*/}
161-
{/*</div>*/}
146+
{authEnabled && (
147+
<div>
148+
<Dropdown
149+
menu={{ items: profileMenuItems }}
150+
trigger={['click']}
151+
placement={'bottomRight'}
152+
>
153+
<Avatar
154+
icon={<UserOutlined />}
155+
style={{
156+
backgroundColor: 'white',
157+
color: 'black',
158+
cursor: 'pointer',
159+
}}
160+
/>
161+
</Dropdown>
162+
</div>
163+
)}
162164
</Layout.Header>
163165
{/* Content */}
164166
<Layout.Content

ui/src/context/auth-context.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useCallback, useEffect, useMemo, useState } from 'react';
22
import { useGetCurrentUser, useLoginWithToken } from '../hooks/user';
33
import apiClient from './client';
4+
import { useNotification } from '../utils/NotificationContext';
45

56
interface AuthContextProps {
67
accessToken: any;
@@ -19,15 +20,22 @@ AuthContext.displayName = 'AuthContext';
1920

2021
function AuthProvider(props: any) {
2122
const [accessToken, setAccessToken] = useState<string>('');
22-
// const { data: currentUser, refetch } = useGetCurrentUser(accessToken);
23+
const { data: currentUser, refetch } = useGetCurrentUser(accessToken);
2324
const loginWithTokenMutation = useLoginWithToken();
25+
const { setNotification } = useNotification();
2426

2527
const loginWithToken = useCallback(
2628
async (idToken: string) => {
2729
return loginWithTokenMutation.mutate(idToken, {
2830
onSuccess: (response) => {
2931
setAccessToken(response.access_token);
3032
},
33+
onError: (error) => {
34+
setNotification(
35+
'Login failed. Please contact your system administrator.',
36+
'error',
37+
);
38+
},
3139
});
3240
},
3341
[loginWithTokenMutation],
@@ -70,7 +78,7 @@ function AuthProvider(props: any) {
7078
accessToken,
7179
loginWithToken,
7280
logout,
73-
// currentUser,
81+
currentUser,
7482
}),
7583
[accessToken, loginWithToken, logout],
7684
);

ui/src/pages/Login.tsx

+2-8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@ import GoogleAuthButton from '../components/login/GoogleAuthButton';
44
import OktaAuthButton from '../components/login/OktaAuthButton';
55
import { useAuth } from '../context/auth-context';
66
import KeycloakAuthButton from '../components/login/KeycloakAuthButton';
7-
import { useNotification } from '../utils/NotificationContext';
87
import { useNavigate, useLocation } from 'react-router-dom';
98

109
export default function () {
11-
const { setNotification } = useNotification();
12-
const { loginWithToken, logout } = useAuth();
10+
const { loginWithToken } = useAuth();
1311
const navigate = useNavigate();
1412
const location = useLocation();
1513
const from = location.state?.from || '/';
@@ -19,11 +17,7 @@ export default function () {
1917
process.env.REACT_APP_KEYCLOAK_AUTH_ENABLED === 'true';
2018

2119
const handleGoogleSignIn = async (idToken: string) => {
22-
await loginWithToken(idToken)
23-
.then(() => navigate(from, { replace: true }))
24-
.catch((e: any) => {
25-
setNotification(e.message, 'error');
26-
});
20+
await loginWithToken(idToken).then(() => navigate(from, { replace: true }));
2721
};
2822

2923
return (

0 commit comments

Comments
 (0)