diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts index d75bceef97..d369e125af 100644 --- a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts @@ -21,6 +21,8 @@ import { OAuthApi, AccessTokenOptions } from '../../../definitions/auth'; import { OAuthRequestApi, AuthProvider } from '../../../definitions'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { StaticAuthSessionManager } from '../../../../lib/AuthSessionManager'; +import { BehaviorSubject } from '../../../../lib'; +import { Observable } from '../../../../types'; type CreateOptions = { // TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GithubAuth @@ -80,12 +82,31 @@ class GithubAuth implements OAuthApi { constructor(private readonly sessionManager: SessionManager) {} + private session: GithubSession | undefined; + private readonly subject = new BehaviorSubject( + undefined, + ); + + session$(): Observable { + return this.subject; + } + + getSession(): GithubSession | undefined { + return this.session; + } + + setSession(session?: GithubSession): void { + this.session = session; + this.subject.next(session); + } + async getAccessToken(scope?: string, options?: AccessTokenOptions) { const normalizedScopes = GithubAuth.normalizeScope(scope); const session = await this.sessionManager.getSession({ ...options, scopes: normalizedScopes, }); + this.setSession(session); if (session) { return session.accessToken; } @@ -94,6 +115,7 @@ class GithubAuth implements OAuthApi { async logout() { await this.sessionManager.removeSession(); + this.setSession(); } static normalizeScope(scope?: string): Set { diff --git a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts index d65a2c71ee..d310cdff57 100644 --- a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts @@ -29,6 +29,8 @@ import { import { OAuthRequestApi, AuthProvider } from '../../../definitions'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; +import { BehaviorSubject } from '../../../../lib'; +import { Observable } from '../../../../types'; type CreateOptions = { // TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GoogleAuth @@ -101,6 +103,24 @@ class GoogleAuth implements OAuthApi, OpenIdConnectApi, ProfileInfoApi { constructor(private readonly sessionManager: SessionManager) {} + private session: GoogleSession | undefined; + private readonly subject = new BehaviorSubject( + undefined, + ); + + session$(): Observable { + return this.subject; + } + + getSession(): GoogleSession | undefined { + return this.session; + } + + setSession(session?: GoogleSession): void { + this.session = session; + this.subject.next(session); + } + async getAccessToken( scope?: string | string[], options?: AccessTokenOptions, @@ -110,6 +130,7 @@ class GoogleAuth implements OAuthApi, OpenIdConnectApi, ProfileInfoApi { ...options, scopes: normalizedScopes, }); + this.setSession(session); if (session) { return session.accessToken; } @@ -118,6 +139,7 @@ class GoogleAuth implements OAuthApi, OpenIdConnectApi, ProfileInfoApi { async getIdToken(options: IdTokenOptions = {}) { const session = await this.sessionManager.getSession(options); + this.setSession(session); if (session) { return session.idToken; } @@ -126,6 +148,7 @@ class GoogleAuth implements OAuthApi, OpenIdConnectApi, ProfileInfoApi { async logout() { await this.sessionManager.removeSession(); + this.setSession(); } async getProfile(options: ProfileInfoOptions = {}) { diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx index 5179356cb8..28c4f100ae 100644 --- a/packages/core/src/layout/Sidebar/UserSettings.tsx +++ b/packages/core/src/layout/Sidebar/UserSettings.tsx @@ -60,23 +60,43 @@ const useProviders = () => { }, ]); - const setIsSignedIn = async () => { - const signInChecks = await Promise.all( - providers.map(provider => - provider.identity - ? provider.api.getIdToken({ optional: true }) - : provider.api.getAccessToken('', { optional: true }), - ), - ); + useEffect(() => { + // On page load we check the status of sign-in/sign-out for all the providers + // by making a optional getIdToken or getAccessToken request. + const setIsSignedIn = async () => { + const signInChecks = await Promise.all( + providers.map(provider => { + return provider.identity + ? provider.api.getIdToken({ optional: true }) + : provider.api.getAccessToken('', { optional: true }); + }), + ); - signInChecks.map((result, i) => { - providers[i].isSignedIn = !!result; + signInChecks.map((result, i) => { + providers[i].isSignedIn = !!result; + }); + + setProviders(providers); + }; + + setIsSignedIn(); + + // Any sign-in/sign-out activity on any provider is observed here + providers.map((provider, index) => { + provider.api.session$().subscribe((session: any) => { + if (session) { + const currentProvider = providers[index]; + currentProvider.isSignedIn = true; + setProviders([ + ...providers.slice(0, index), + currentProvider, + ...providers.slice(index + 1, providers.length), + ]); + } + }); }); - - setProviders(providers); - }; - - setIsSignedIn(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return providers; };