diff --git a/tdrive/frontend/src/app/components/modal/modal-component.tsx b/tdrive/frontend/src/app/components/modal/modal-component.tsx index bc69bec46..c4f4f6801 100755 --- a/tdrive/frontend/src/app/components/modal/modal-component.tsx +++ b/tdrive/frontend/src/app/components/modal/modal-component.tsx @@ -40,7 +40,7 @@ export default () => { centered closable={false} title={null} - visible={open} + open={open} footer={null} destroyOnClose={true} width={ModalManager.getPosition()?.size?.width || '700px'} diff --git a/tdrive/frontend/src/app/features/auth/auth-service.ts b/tdrive/frontend/src/app/features/auth/auth-service.ts index 058dd4614..04d0ac8cd 100644 --- a/tdrive/frontend/src/app/features/auth/auth-service.ts +++ b/tdrive/frontend/src/app/features/auth/auth-service.ts @@ -50,6 +50,7 @@ class AuthService { const config = InitService.server_infos?.configuration?.accounts[accountType]; + console.log(`Use "${Globals.environment.env_dev_auth}" account type for authorization`) if (Globals.environment.env_dev_auth) accountType = Globals.environment.env_dev_auth; if (accountType === 'remote') { @@ -104,7 +105,6 @@ class AuthService { onSessionExpired: () => this.onSessionExpired(), onNewToken: async token => { this.onNewToken(token); - // TODO: Change the basic auth to return this new token on init if (this.initState === 'initializing') { const user = await this.comleteInit(); @@ -118,6 +118,8 @@ class AuthService { resolve(null); }, }); + this.logger.info("Init completed") + resolve(null); }); } diff --git a/tdrive/frontend/src/app/features/auth/jwt-storage-service.ts b/tdrive/frontend/src/app/features/auth/jwt-storage-service.ts index d06d45db4..e2ec87e9f 100755 --- a/tdrive/frontend/src/app/features/auth/jwt-storage-service.ts +++ b/tdrive/frontend/src/app/features/auth/jwt-storage-service.ts @@ -84,7 +84,7 @@ class JWTStorage { this.timeDelta = new Date().getTime() / 1000 - jwtData.time; this.jwtData.expiration += this.timeDelta - 5 * 60; //Force reduce expiration by 5 minutes this.jwtData.refresh_expiration += this.timeDelta - 5 * 60; //Force reduce expiration by 5 minutes - + this.logger.info("Update jwt token in local storage") LocalStorage.setItem('jwt', this.jwtData); } } @@ -147,7 +147,7 @@ class JWTStorage { } async renew(): Promise { - const token = await ConsoleAPIClient.getNewAccessToken(); + const token = await ConsoleAPIClient.renewAccessToken(); if (!token) { throw new Error('Can not get a new access token'); diff --git a/tdrive/frontend/src/app/features/auth/login-service.ts b/tdrive/frontend/src/app/features/auth/login-service.ts index 111d7bac8..4499ed29a 100755 --- a/tdrive/frontend/src/app/features/auth/login-service.ts +++ b/tdrive/frontend/src/app/features/auth/login-service.ts @@ -11,8 +11,13 @@ import Application from '../applications/services/application-service'; import { UserType } from '@features/users/types/user'; import { Cookies } from 'react-cookie'; import InitService from '../global/services/init-service'; +import { useRecoilState } from "recoil"; +import { CurrentUserState } from "features/users/state/atoms/current-user"; class Login extends Observable { + + private static logInOngoing = false; + // Promise resolved when user is defined userIsSet!: Promise; resolveUser!: (userId: string) => void; @@ -80,12 +85,14 @@ class Login extends Observable { } if (!AuthService.isInitialized()) { + this.logger.log("Auth service is not initialized, init ...") this.reset(); await AuthService.init(); + this.logger.info("Auth service initialized"); const redirectUrl = this.cookies.get('pending-redirect'); if (redirectUrl) { - console.log('Got pending redirect to', redirectUrl); + this.logger.info('Got pending redirect to', redirectUrl); this.cookies.remove('pending-redirect'); setTimeout(() => { document.location.href = redirectUrl; @@ -104,6 +111,8 @@ class Login extends Observable { } async updateUser(callback?: (err: Error | null, user?: UserType) => void): Promise { + this.logger.info("LoginService:: Try to update user info ") + if (Globals.store_public_access_get_data) { this.firstInit = true; this.state = 'logged_out'; @@ -114,7 +123,7 @@ class Login extends Observable { AuthService.updateUser(async user => { this.logger.debug('User update result', user); if (!user) { - if (!this.pingServer()) { + if (!(await this.pingServer())) { //We are disconnected console.log('We are disconnected, we will get user again in 10 seconds'); setTimeout(() => { @@ -167,15 +176,17 @@ class Login extends Observable { }); } - login(params: any, hide_load = false) { - if (!hide_load) { - this.login_loading = true; - } - this.login_error = false; - this.notify(); + async login(params: any, hide_load = false) { + if (!Login.logInOngoing) { + this.logger.debug("Try to login"); + if (!hide_load) { + this.login_loading = true; + } + this.login_error = false; + this.notify(); - AuthService.login(params) - .then(async result => { + try { + const result = await AuthService.login(params); this.login_loading = false; if (!result) { this.login_error = true; @@ -183,11 +194,15 @@ class Login extends Observable { return; } await this.updateUser(); - }) - .catch(err => { + } catch (err) { this.logger.error('Can not login', err); - // TODO display a modal message - }); + } finally { + this.logger.debug('Login process finished'); + Login.logInOngoing = false; + } + } else { + this.logger.debug("Login is already in process ..."); + } } async logout(reload = false) { diff --git a/tdrive/frontend/src/app/features/auth/provider/oidc/oidc-auth-provider-service.ts b/tdrive/frontend/src/app/features/auth/provider/oidc/oidc-auth-provider-service.ts index af688ef79..2339729e7 100644 --- a/tdrive/frontend/src/app/features/auth/provider/oidc/oidc-auth-provider-service.ts +++ b/tdrive/frontend/src/app/features/auth/provider/oidc/oidc-auth-provider-service.ts @@ -8,9 +8,10 @@ import { getAsFrontUrl } from '@features/global/utils/URLUtils'; import { TdriveService } from '../../../global/framework/registry-decorator-service'; import EnvironmentService from '../../../global/framework/environment-service'; import { AuthProvider, InitParameters } from '../auth-provider'; -import ConsoleService from '@features/console/services/console-service'; import jwtStorageService, { JWTDataType } from '@features/auth/jwt-storage-service'; import LocalStorage from '@features/global/framework/local-storage-service'; +import ConsoleApiClient from '@features/console/api/console-api-client'; +import JwtStorageService from "@features/auth/jwt-storage-service"; const OIDC_CALLBACK_URL = '/oidccallback'; const OIDC_SIGNOUT_URL = '/signout'; @@ -55,11 +56,11 @@ export default class OIDCAuthProviderService scope: 'openid profile email address phone offline_access', post_logout_redirect_uri: getAsFrontUrl(OIDC_SIGNOUT_URL), silent_redirect_uri: getAsFrontUrl(OIDC_SILENT_URL), - automaticSilentRenew: true, + automaticSilentRenew: false, loadUserInfo: true, accessTokenExpiringNotificationTime: 10, filterProtocolClaims: true, - monitorSession: false, + monitorSession: true, }); // For logout if signout or logout endpoint called @@ -69,6 +70,18 @@ export default class OIDCAuthProviderService this.signOut(); } + this.userManager.events.addUserSessionChanged((... args) => { + this.logger.debug('User Session changed:', args); + }); + + this.userManager.events.addSilentRenewError((... args) => { + this.logger.debug('Silent Renew Error:', args); + }); + + this.userManager.events.addUserUnloaded((... args) => { + this.logger.debug('User unloaded:', args); + }); + this.userManager.events.addUserLoaded((user: any, ...args) => { this.logger.debug('New User Loaded:', user, args); this.logger.debug('Acess_token: ', user.access_token); @@ -83,12 +96,8 @@ export default class OIDCAuthProviderService await this.userManager?.removeUser(); await this.signIn(); }); - - this.userManager.events.addSilentRenewError((...args) => { - console.error('Silent Renew Error:', args); - }); } - + this.logger.info("Init completed") return this; } @@ -96,7 +105,7 @@ export default class OIDCAuthProviderService this.logger.info('Signin'); try { - await this.userManager!.signinRedirectCallback(); + await this.userManager?.signinRedirectCallback(); } catch (e) { console.log('Not connected, connect through SSO'); } @@ -104,24 +113,23 @@ export default class OIDCAuthProviderService const user = await this.userManager?.getUser(); if (user) { - await this.getJWTFromOidcToken(user, (err, jwt) => { - if (err) { - this.logger.error( - 'OIDC user loaded listener, error while getting the JWT from OIDC token', - err, - ); - this.signinRedirect(); - } - + try { + const jwt = await this.getJWTFromOidcToken(user); if (!this.initialized) { this.onInitialized(); this.initialized = true; - } else { - jwt && this.params!.onNewToken(jwt); } - }); + this.logger.info("Setting new access token"); + await this.params?.onNewToken(jwt); + } catch (err) { + this.logger.error( + 'OIDC user loaded listener, error while getting the JWT from OIDC token', + err, + ); + await this.signinRedirect(); + } } else { - this.userManager?.signinRedirect(); + await this.signinRedirect(); } } @@ -154,14 +162,10 @@ export default class OIDCAuthProviderService * Try to get a new JWT token from the OIDC one: * Call the backend with the OIDC token, it will use it to get a new token from console */ - private async getJWTFromOidcToken( - user: Oidc.User, - callback: (err?: Error, accessToken?: JWTDataType) => void, - ): Promise { + private async getJWTFromOidcToken(user: Oidc.User): Promise { if (!user) { this.logger.info('getJWTFromOidcToken, Cannot getJWTFromOidcToken with a null user'); - callback(new Error('Cannot getJWTFromOidcToken with a null user')); - return; + throw new Error('Cannot getJWTFromOidcToken with a null user'); } if (user.expired) { @@ -169,13 +173,16 @@ export default class OIDCAuthProviderService this.logger.info('getJWTFromOidcToken, user expired'); } - ConsoleService.getNewAccessToken( + const jwt = await ConsoleApiClient.getNewAccessToken( { id_token: user.id_token, access_token: user.access_token }, - callback, ); + + JwtStorageService.updateJWT(jwt) + + return jwt; } - signinRedirect() { + async signinRedirect() { if (document.location.href.indexOf('/login') === -1) { //Save requested URL for after redirect / sign-in LocalStorage.setItem('requested_url', { @@ -186,7 +193,9 @@ export default class OIDCAuthProviderService jwtStorageService.clear(); - if (this.userManager) this.userManager.signinRedirect(); + if (this.userManager) { + await this.userManager.signinRedirect(); + } } onInitialized() { diff --git a/tdrive/frontend/src/app/features/console/api/console-api-client.ts b/tdrive/frontend/src/app/features/console/api/console-api-client.ts index ded75b0f0..de95b9c3d 100644 --- a/tdrive/frontend/src/app/features/console/api/console-api-client.ts +++ b/tdrive/frontend/src/app/features/console/api/console-api-client.ts @@ -1,6 +1,7 @@ import Api from '@features/global/framework/api-service'; import { TdriveService } from '@features/global/framework/registry-decorator-service'; import JWTStorage, { JWTDataType } from '@features/auth/jwt-storage-service'; +import Logger from "features/global/framework/logger-service"; type LoginParams = { email: string; @@ -16,8 +17,20 @@ type SignupParams = { username: string; }; +type AccessTokenResponse = { + statusCode: string; + access_token: JWTDataType; +} + +type AccessTokenRequest = { + oidc_id_token: string; +} + @TdriveService('ConsoleAPIClientService') class ConsoleAPIClient { + + logger = Logger.getLogger('ConsoleAPIClient'); + login(params: LoginParams, disableJWTAuthentication = false): Promise { return Api.post( '/internal/services/console/v1/login', @@ -39,7 +52,29 @@ class ConsoleAPIClient { return res; } - getNewAccessToken(): Promise { + public async getNewAccessToken( + currentToken: { access_token: string; id_token: string } + ): Promise { + this.logger.debug( + `getNewAccessToken, get new token from current token ${JSON.stringify(currentToken)}`, + ); + const response = await Api.post( + '/internal/services/console/v1/login', + { oidc_id_token: currentToken.id_token }); + + if (response.statusCode && !response.access_token) { + this.logger.error( + 'getNewAccessToken, Can not retrieve access_token from console. Response was', + response, + ); + throw new Error('Can not retrieve access_token from console'); + } + // the input access_token is potentially expired and so the response contains an error. + // we should be able to refresh the token or renew it in some way... + return response.access_token; + } + + renewAccessToken(): Promise { if (JWTStorage.isRefreshExpired() && JWTStorage.isAccessExpired()) { throw new Error('Can not get access token as both access and refresh token are expired'); } diff --git a/tdrive/frontend/src/app/features/console/services/console-service.ts b/tdrive/frontend/src/app/features/console/services/console-service.ts index 263fa7ee4..af47d1bd9 100644 --- a/tdrive/frontend/src/app/features/console/services/console-service.ts +++ b/tdrive/frontend/src/app/features/console/services/console-service.ts @@ -103,42 +103,6 @@ class ConsoleService { return res; } - /** - * @deprecated use ConsoleServiceAPIClient.getNewAccessToken - * @param currentToken - * @param callback - */ - public getNewAccessToken( - currentToken: { access_token: string; id_token: string }, - callback: (err?: Error, access_token?: JWTDataType) => void, - ): void { - this.logger.debug( - `getNewAccessToken, get new token from current token ${JSON.stringify(currentToken)}`, - ); - Api.post( - '/internal/services/console/v1/login', - { oidc_id_token: currentToken.id_token }, - (response: { - access_token: JWTDataType; - message: string; - error: string; - statusCode: number; - }) => { - if (response.statusCode && !response.access_token) { - this.logger.error( - 'getNewAccessToken, Can not retrieve access_token from console. Response was', - response, - ); - callback(new Error('Can not retrieve access_token from console')); - return; - } - // the input access_token is potentially expired and so the response contains an error. - // we should be able to refresh the token or renew it in some way... - - callback(undefined, response.access_token); - }, - ); - } } export default new ConsoleService(); diff --git a/tdrive/frontend/src/app/features/global/hooks/use-realtime.ts b/tdrive/frontend/src/app/features/global/hooks/use-realtime.ts index f7d7b59c7..6c12759a1 100644 --- a/tdrive/frontend/src/app/features/global/hooks/use-realtime.ts +++ b/tdrive/frontend/src/app/features/global/hooks/use-realtime.ts @@ -22,7 +22,7 @@ export type RealtimeRoomService = { * * Note: It will subscribe only once, even if the component using it re renders. If you need to unsubscribe and subscribe again, call unsubscribe on the returned object. * - * @param roomName + * @param roomConf * @param tagName * @param onEvent * @returns diff --git a/tdrive/frontend/src/app/features/users/hooks/use-current-user.ts b/tdrive/frontend/src/app/features/users/hooks/use-current-user.ts index c37437059..16a9af38c 100644 --- a/tdrive/frontend/src/app/features/users/hooks/use-current-user.ts +++ b/tdrive/frontend/src/app/features/users/hooks/use-current-user.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef } from "react"; import LoginService from '@features/auth/login-service'; import UserAPIClient from '@features/users/api/user-api-client'; import { useRecoilState } from 'recoil'; @@ -8,19 +8,29 @@ import { useRealtimeRoom } from '@features/global/hooks/use-realtime'; import Languages from '@features/global/services/languages-service'; import { useSetUserList } from './use-user-list'; import { getPublicLinkToken } from 'app/features/drive/api-client/api-client'; +import Logger from '../../../features/global/framework/logger-service'; export const useCurrentUser = () => { const [user, setUser] = useRecoilState(CurrentUserState); const { set: setUserList } = useSetUserList('useCurrentUser'); + const logger = Logger.getLogger('useCurrentUser'); + //Depreciated way to get use update from LoginService LoginService.recoilUpdateUser = setUser; + useEffect(() => { if (!user && !getPublicLinkToken()) { - LoginService.init(); - LoginService.login({}); + logger.debug("Init LoggerService ..."); + LoginService.init(true) + .then(() => logger.debug("Init LoggerService completed")) + .then(() => LoginService.login({})) + .then(() => logger.debug("Login process completed")) + .then(() => {if (user) setUserList([user])}) + .catch(err => logger.error("Error during auth: ", err)) + } else { + if (user) setUserList([user]); } - if (user) setUserList([user]); }, [user]); //Update app language diff --git a/tdrive/frontend/src/app/features/users/hooks/use-online-user.ts b/tdrive/frontend/src/app/features/users/hooks/use-online-user.ts index 89643bbb8..ea22e8b15 100644 --- a/tdrive/frontend/src/app/features/users/hooks/use-online-user.ts +++ b/tdrive/frontend/src/app/features/users/hooks/use-online-user.ts @@ -2,32 +2,35 @@ import { useEffect } from 'react'; import { useRecoilCallback, useRecoilValue } from 'recoil'; import { OnlineUserStateFamily, OnlineUserType } from '../state/atoms/online-users'; -import { OnlineUserRealtimeAPI } from '../api/online-user-realtime-api-client'; -import WebSocketFactory from '../../global/services/websocket-factory-service'; +//TODO refactor it in the notification feature + +// import { OnlineUserRealtimeAPI } from '../api/online-user-realtime-api-client'; +// import WebSocketFactory from '../../global/services/websocket-factory-service'; export const useOnlineUser = (id: string): OnlineUserType => { - const OnlineAPI = OnlineUserRealtimeAPI(WebSocketFactory.get()); - - const updateUser = useRecoilCallback( - ({ set, snapshot }) => - (status: { id: string; connected: boolean }) => { - const current = snapshot.getLoadable(OnlineUserStateFamily(status.id)).contents; - set(OnlineUserStateFamily(status.id), { - ...status, - lastSeen: status.connected ? Date.now() : current.lastSeen, - initialized: true, - }); - }, - [], - ); + + // const OnlineAPI = OnlineUserRealtimeAPI(WebSocketFactory.get()); + + // const updateUser = useRecoilCallback( + // ({ set, snapshot }) => + // (status: { id: string; connected: boolean }) => { + // const current = snapshot.getLoadable(OnlineUserStateFamily(status.id)).contents; + // set(OnlineUserStateFamily(status.id), { + // ...status, + // lastSeen: status.connected ? Date.now() : current.lastSeen, + // initialized: true, + // }); + // }, + // [], + // ); const state = useRecoilValue(OnlineUserStateFamily(id)); useEffect(() => { if (state && !state.initialized) { - OnlineAPI.getUserStatus(id).then(status => { - updateUser({ id: status[0], connected: status[1] }); - }); + // OnlineAPI.getUserStatus(id).then(status => { + // updateUser({ id: status[0], connected: status[1] }); + // }); } }, [state, id]);