This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new b10dea750 feat: account panel for oauth2_proxy (#5409)
b10dea750 is described below

commit b10dea750360bfa19cf0e93e761b4b4fba595008
Author: Klesh Wong <[email protected]>
AuthorDate: Fri Jun 9 09:17:25 2023 +0800

    feat: account panel for oauth2_proxy (#5409)
---
 backend/server/api/api.go           |   7 ++
 config-ui/src/App.tsx               |   3 -
 config-ui/src/layouts/base/api.ts   |   7 ++
 config-ui/src/layouts/base/base.tsx |  19 +++---
 config-ui/src/pages/index.ts        |   1 -
 config-ui/src/pages/login/api.ts    |  34 ----------
 config-ui/src/pages/login/index.ts  |  19 ------
 config-ui/src/pages/login/login.tsx | 129 ------------------------------------
 config-ui/src/pages/login/styld.ts  |  39 -----------
 config-ui/src/utils/request.ts      |  46 ++-----------
 docker-compose-dev.yml              |  24 +++++++
 11 files changed, 51 insertions(+), 277 deletions(-)

diff --git a/backend/server/api/api.go b/backend/server/api/api.go
index 24a017129..b0fa46290 100644
--- a/backend/server/api/api.go
+++ b/backend/server/api/api.go
@@ -67,6 +67,13 @@ func CreateApiService() {
        // For both protected and unprotected routes
        router.GET("/ping", ping.Get)
        router.GET("/version", version.Get)
+       router.GET("/userinfo", func(ctx *gin.Context) {
+               shared.ApiOutputSuccess(ctx, gin.H{
+                       "user":      ctx.Request.Header.Get("X-Forwarded-User"),
+                       "email":     
ctx.Request.Header.Get("X-Forwarded-Email"),
+                       "logoutURI": config.GetConfig().GetString("LOGOUT_URI"),
+               }, http.StatusOK)
+       })
 
        // Endpoint to proceed database migration
        router.GET("/proceed-db-migration", func(ctx *gin.Context) {
diff --git a/config-ui/src/App.tsx b/config-ui/src/App.tsx
index 1e723a1e6..8bb42ca66 100644
--- a/config-ui/src/App.tsx
+++ b/config-ui/src/App.tsx
@@ -19,7 +19,6 @@
 import { Switch, Route, Redirect, Router } from 'react-router-dom';
 import { ErrorLayout, BaseLayout } from '@/layouts';
 import {
-  LoginPage,
   OfflinePage,
   DBMigratePage,
   ConnectionHomePage,
@@ -36,8 +35,6 @@ function App() {
   return (
     <Router history={history}>
       <Switch>
-        <Route exact path="/login" component={() => <LoginPage />} />
-
         <Route
           exact
           path="/offline"
diff --git a/config-ui/src/layouts/base/api.ts 
b/config-ui/src/layouts/base/api.ts
index d5bc56b01..c3be4bbac 100644
--- a/config-ui/src/layouts/base/api.ts
+++ b/config-ui/src/layouts/base/api.ts
@@ -18,4 +18,11 @@
 
 import { request } from '@/utils';
 
+export type UserInfo = {
+  user: string;
+  email: string;
+  logoutURI: string;
+};
+
 export const getVersion = () => request('/version');
+export const getUserInfo = (): Promise<UserInfo> => request('/userinfo');
diff --git a/config-ui/src/layouts/base/base.tsx 
b/config-ui/src/layouts/base/base.tsx
index 104163b73..520112bb4 100644
--- a/config-ui/src/layouts/base/base.tsx
+++ b/config-ui/src/layouts/base/base.tsx
@@ -16,6 +16,7 @@
  *
  */
 
+import { useEffect, useState } from 'react';
 import { useLocation } from 'react-router-dom';
 import { Menu, MenuItem, Tag, Navbar, Intent, Alignment, Button } from 
'@blueprintjs/core';
 
@@ -43,7 +44,10 @@ export const BaseLayout = ({ children }: Props) => {
   const { tips } = useTips();
   const { ready, data } = useRefreshData<{ version: string }>(() => 
API.getVersion(), []);
 
-  const token = window.localStorage.getItem('accessToken');
+  const [userInfo, setUserInfo] = useState<API.UserInfo | null>(null);
+  useEffect(() => {
+    API.getUserInfo().then(setUserInfo);
+  }, []);
 
   const handlePushPath = (it: MenuItemType) => {
     if (!it.target) {
@@ -53,11 +57,6 @@ export const BaseLayout = ({ children }: Props) => {
     }
   };
 
-  const handleSignOut = () => {
-    localStorage.removeItem(`accessToken`);
-    history.push('/login');
-  };
-
   const getGrafanaUrl = () => {
     const suffix = '/d/lCO8w-pVk/homepage?orgId=1';
     const { protocol, hostname } = window.location;
@@ -149,12 +148,12 @@ export const BaseLayout = ({ children }: Props) => {
               <img src={SlackIcon} alt="slack" />
               <span>Slack</span>
             </a>
-            {token && (
+            {userInfo && userInfo.logoutURI && (
               <>
                 <Navbar.Divider />
-                <Button small intent={Intent.NONE} onClick={handleSignOut}>
-                  Sign Out
-                </Button>
+                <span>{userInfo.email}</span>
+                <Navbar.Divider />
+                <a href={userInfo.logoutURI}>Sign Out</a>
               </>
             )}
           </Navbar.Group>
diff --git a/config-ui/src/pages/index.ts b/config-ui/src/pages/index.ts
index 557b2860e..371eed61d 100644
--- a/config-ui/src/pages/index.ts
+++ b/config-ui/src/pages/index.ts
@@ -19,7 +19,6 @@
 export * from './blueprint';
 export * from './connection';
 export * from './db-migrate';
-export * from './login';
 export * from './offline';
 export * from './pipeline';
 export * from './project';
diff --git a/config-ui/src/pages/login/api.ts b/config-ui/src/pages/login/api.ts
deleted file mode 100644
index 1e36a0a46..000000000
--- a/config-ui/src/pages/login/api.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { request } from '@/utils';
-
-type LoginPayload = {
-  username: string;
-  password: string;
-};
-
-type NewPasswordPayload = {
-  username: string;
-  newPassword: string;
-  session: string;
-};
-
-export const login = (payload: LoginPayload) => request(`/login`, { method: 
'post', data: payload });
-export const newPassword = (payload: NewPasswordPayload) =>
-  request(`/login/newpassword`, { method: 'post', data: payload });
diff --git a/config-ui/src/pages/login/index.ts 
b/config-ui/src/pages/login/index.ts
deleted file mode 100644
index a365b1ed2..000000000
--- a/config-ui/src/pages/login/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-export * from './login';
diff --git a/config-ui/src/pages/login/login.tsx 
b/config-ui/src/pages/login/login.tsx
deleted file mode 100644
index 69105f317..000000000
--- a/config-ui/src/pages/login/login.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import { useState } from 'react';
-import { useHistory } from 'react-router-dom';
-import { FormGroup, InputGroup, Button, Intent } from '@blueprintjs/core';
-
-import { operator } from '@/utils';
-
-import * as API from './api';
-import * as S from './styld';
-
-const NEW_PASSWORD_REQUIRED = 'NEW_PASSWORD_REQUIRED';
-
-export const LoginPage = () => {
-  const [username, setUsername] = useState(localStorage.getItem('username') || 
'');
-  const [password, setPassword] = useState('');
-  const [newPassword, setNewPassword] = useState('');
-  const [confirmNewPassword, setConfirmNewPassword] = useState('');
-  const [challenge, setChallenge] = useState('');
-  const [session, setSession] = useState('');
-  const loginDisabled =
-    !username ||
-    !password ||
-    (challenge === 'NEW_PASSWORD_REQUIRED' &&
-      (!newPassword || !confirmNewPassword || newPassword !== 
confirmNewPassword));
-
-  const history = useHistory();
-
-  // () =>
-  const handleSubmit = async () => {
-    var request: () => Promise<any>;
-
-    switch (challenge) {
-      case NEW_PASSWORD_REQUIRED:
-        request = () => API.newPassword({ username, session, newPassword });
-        break;
-      default:
-        request = () => API.login({ username, password });
-        break;
-    }
-
-    const [success, res] = await operator(request, {
-      formatReason: (error) => {
-        const e = error as any;
-        return e?.response?.data?.causes[0];
-      },
-    });
-    localStorage.setItem('username', username);
-    if (success) {
-      if (res.challengeName) {
-        setChallenge(res.challengeName);
-        setSession(res.session);
-      } else {
-        localStorage.setItem('accessToken', 
res.authenticationResult.accessToken);
-        localStorage.setItem('refreshToken', 
res.authenticationResult.refreshToken);
-        document.cookie = 'access_token=' + 
res.authenticationResult.accessToken + '; path=/';
-        setUsername('');
-        setPassword('');
-        setChallenge('');
-        setSession('');
-        history.push('/');
-      }
-    }
-  };
-
-  return (
-    <S.Wrapper>
-      <S.Inner>
-        <h2>DevLake Login</h2>
-        <FormGroup label="Username">
-          <InputGroup
-            placeholder="Username"
-            value={username}
-            disabled={challenge !== ''}
-            onChange={(e) => setUsername((e.target as HTMLInputElement).value)}
-          />
-        </FormGroup>
-        <FormGroup label="Password">
-          <InputGroup
-            type="password"
-            placeholder="Password"
-            value={password}
-            disabled={challenge !== ''}
-            onChange={(e) => setPassword((e.target as HTMLInputElement).value)}
-          />
-        </FormGroup>
-        {challenge === 'NEW_PASSWORD_REQUIRED' && (
-          <>
-            <FormGroup label="Set New Password">
-              <InputGroup
-                type="password"
-                placeholder="Please set a new Password for your account"
-                value={newPassword}
-                onChange={(e) => setNewPassword((e.target as 
HTMLInputElement).value)}
-              />
-            </FormGroup>
-            <FormGroup label="Confirm New Password">
-              <InputGroup
-                type="password"
-                placeholder="Please repeat your New Password"
-                value={confirmNewPassword}
-                onChange={(e) => setConfirmNewPassword((e.target as 
HTMLInputElement).value)}
-              />
-            </FormGroup>
-          </>
-        )}
-        <Button intent={Intent.PRIMARY} onClick={handleSubmit} 
disabled={loginDisabled}>
-          Login
-        </Button>
-      </S.Inner>
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/login/styld.ts 
b/config-ui/src/pages/login/styld.ts
deleted file mode 100644
index e12cfadc0..000000000
--- a/config-ui/src/pages/login/styld.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-import styled from 'styled-components';
-
-export const Wrapper = styled.div`
-  height: 100vh;
-  background-color: #f5f5f5;
-  overflow: hidden;
-`;
-
-export const Inner = styled.div`
-  margin: 200px auto;
-  padding: 16px 24px;
-  width: 400px;
-  background-color: #fff;
-  border-raidus: 4px;
-  box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 
0, 0, 0.07);
-
-  h2 {
-    margin-bottom: 16px;
-    text-align: center;
-  }
-`;
diff --git a/config-ui/src/utils/request.ts b/config-ui/src/utils/request.ts
index b345293e1..d08301836 100644
--- a/config-ui/src/utils/request.ts
+++ b/config-ui/src/utils/request.ts
@@ -27,51 +27,11 @@ const instance = axios.create({
   baseURL: DEVLAKE_ENDPOINT,
 });
 
-var refreshingToken: Promise<any> | null = null;
-
 instance.interceptors.response.use(
   (response) => response,
   (error) => {
     const status = error.response?.status;
 
-    if (status === 401) {
-      toast.error('Please login first');
-      history.push('/login');
-    }
-
-    if (status === 403) {
-      var refreshToken = localStorage.getItem('refreshToken');
-      if (refreshToken) {
-        refreshingToken =
-          refreshingToken ||
-          request('/login/refreshtoken', {
-            method: 'POST',
-            data: {
-              refreshToken: refreshToken,
-            },
-          }).then(
-            (resp) => {
-              localStorage.setItem('accessToken', 
resp.authenticationResult.accessToken);
-              refreshingToken = null;
-              return resp;
-            },
-            (err) => {
-              refreshingToken = null;
-              toast.error('Please login first');
-              history.push('/login');
-              return Promise.reject(err);
-            },
-          );
-        return refreshingToken.then(() => {
-          const originalRequest = error.config;
-          originalRequest._retry = true;
-          return Promise.resolve(request(originalRequest.url, 
originalRequest));
-        });
-      } else {
-        history.push('/login');
-      }
-    }
-
     if (status === 428) {
       history.push('/db-migrate');
     }
@@ -84,7 +44,8 @@ instance.interceptors.response.use(
   },
 );
 
-export type ReuqestConfig = {
+export type RequestConfig = {
+  baseURL?: string;
   method?: AxiosRequestConfig['method'];
   data?: unknown;
   timeout?: number;
@@ -92,7 +53,7 @@ export type ReuqestConfig = {
   headers?: Record<string, string>;
 };
 
-export const request = (path: string, config?: ReuqestConfig) => {
+export const request = (path: string, config?: RequestConfig) => {
   const { method = 'get', data, timeout, headers, signal } = config || {};
 
   const cancelTokenSource = axios.CancelToken.source();
@@ -103,6 +64,7 @@ export const request = (path: string, config?: 
ReuqestConfig) => {
   }
 
   const params: any = {
+    baseURL: config?.baseURL,
     url: path,
     method,
     timeout,
diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml
index beae68982..33235184a 100644
--- a/docker-compose-dev.yml
+++ b/docker-compose-dev.yml
@@ -79,6 +79,7 @@ services:
       - ./logs:/app/logs
     environment:
       LOGGING_DIR: /app/logs
+      # LOGOUT_URI: 
https://xxx.amazoncognito.com/logout?client_id=yyy&logout_uri=http%3A%2F%2Flocalhost%3A4180%2Foauth2%2Fsign_out
     depends_on:
       - mysql
 
@@ -98,6 +99,29 @@ services:
     depends_on:
       - devlake
 
+  authproxy:
+    image: quay.io/oauth2-proxy/oauth2-proxy:v7.4.0-amd64
+    network_mode: "host"
+    ports:
+      - "4180:4180"
+    env_file:
+      - ./.env
+    # environment:
+    #   OAUTH2_PROXY_PROVIDER: oidc
+    #   OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: my provider
+    #   OAUTH2_PROXY_COOKIE_SECRET: 
+    #   OAUTH2_PROXY_COOKIE_DOMAINS: localhost:4180
+    #   OAUTH2_PROXY_COOKIE_SECURE: 'false'
+    #   OAUTH2_PROXY_EMAIL_DOMAINS: *
+    #   OAUTH2_PROXY_OIDC_ISSUER_URL: 
+    #   OAUTH2_PROXY_OIDC_JWKS_URL: 
+    #   OAUTH2_PROXY_CLIENT_ID: 
+    #   OAUTH2_PROXY_CLIENT_SECRET: 
+    #   OAUTH2_PROXY_UPSTREAMS: http://localhost:4000
+    #   OAUTH2_PROXY_HTTP_ADDRESS: http://0.0.0.0:4180
+    #   OAUTH2_PROXY_REVERSE_PROXY: 'true'
+    #   OAUTH2_PROXY_SKIP_AUTH_ROUTES: ^/grafana.*
+
 volumes:
   mysql-storage:
   grafana-storage:

Reply via email to