refactoring to have side item components based on provider type

This commit is contained in:
Raghunandan
2020-06-09 23:30:39 +02:00
parent 76b8e1310d
commit 4c205d039d
4 changed files with 173 additions and 115 deletions
@@ -15,6 +15,7 @@
*/
import { createApiRef } from '../ApiRef';
import { Observable } from '../..';
/**
* This file contains declarations for common interfaces of auth-related APIs.
@@ -167,6 +168,9 @@ export type ProfileInfo = {
picture?: string;
};
export type ObservableSession = {
session$(): Observable<boolean>;
};
/**
* Provides authentication towards Google APIs and identities.
*
@@ -176,7 +180,7 @@ export type ProfileInfo = {
* email and expiration information. Do not rely on any other fields, as they might not be present.
*/
export const googleAuthApiRef = createApiRef<
OAuthApi & OpenIdConnectApi & ProfileInfoApi
OAuthApi & OpenIdConnectApi & ProfileInfoApi & ObservableSession
>({
id: 'core.auth.google',
description: 'Provides authentication towards Google APIs and identities',
@@ -188,7 +192,7 @@ export const googleAuthApiRef = createApiRef<
* See https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
* for a full list of supported scopes.
*/
export const githubAuthApiRef = createApiRef<OAuthApi>({
export const githubAuthApiRef = createApiRef<OAuthApi & ObservableSession>({
id: 'core.auth.github',
description: 'Provides authentication towards Github APIs',
});
@@ -17,7 +17,11 @@
import GithubIcon from '@material-ui/icons/AcUnit';
import { DefaultAuthConnector } from '../../../../lib/AuthConnector';
import { GithubSession } from './types';
import { OAuthApi, AccessTokenOptions } from '../../../definitions/auth';
import {
OAuthApi,
AccessTokenOptions,
ObservableSession,
} from '../../../definitions/auth';
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import { StaticAuthSessionManager } from '../../../../lib/AuthSessionManager';
@@ -48,7 +52,7 @@ const DEFAULT_PROVIDER = {
icon: GithubIcon,
};
class GithubAuth implements OAuthApi {
class GithubAuth implements OAuthApi, ObservableSession {
static create({
apiOrigin,
basePath,
@@ -25,6 +25,7 @@ import {
ProfileInfoApi,
ProfileInfoOptions,
ProfileInfo,
ObservableSession,
} from '../../../definitions/auth';
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
@@ -59,7 +60,8 @@ const DEFAULT_PROVIDER = {
const SCOPE_PREFIX = 'https://www.googleapis.com/auth/';
class GoogleAuth implements OAuthApi, OpenIdConnectApi, ProfileInfoApi {
class GoogleAuth
implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, ObservableSession {
static create({
apiOrigin,
basePath,
+158 -110
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { useState, useContext, useEffect, useRef } from 'react';
import React, { useState, useContext, useEffect, useRef, FC } from 'react';
import Collapse from '@material-ui/core/Collapse';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
@@ -29,76 +29,131 @@ import {
googleAuthApiRef,
githubAuthApiRef,
ProfileInfo,
OAuthApi,
OpenIdConnectApi,
ProfileInfoApi,
ApiRef,
ObservableSession,
} from '@backstage/core-api';
import { Avatar, IconButton, makeStyles, Tooltip } from '@material-ui/core';
import PowerButton from '@material-ui/icons/PowerSettingsNew';
type AppAuthProviders = Provider[];
type Provider = {
title: string;
api: any;
identity?: boolean;
isSignedIn: boolean;
icon: any;
};
const useProviders = () => {
const googleAuth = useApi(googleAuthApiRef);
const githubAuth = useApi(githubAuthApiRef);
// TODO(soapraj): List all the providers supported by the app
const [providers, setProviders] = useState<AppAuthProviders>([
{
title: 'Google',
api: googleAuth,
identity: true,
isSignedIn: false,
icon: Star,
},
{
title: 'Github',
api: githubAuth,
isSignedIn: false,
icon: StarBorder,
},
]);
type OAuthProviderSidebarProps = {
title: string;
icon: any;
apiRef: ApiRef<OAuthApi & ObservableSession>;
};
// 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 });
}),
);
type OIDCProviderSidebarProps = {
title: string;
icon: any;
apiRef: ApiRef<OpenIdConnectApi & ObservableSession>;
};
signInChecks.map((result, i) => {
providers[i].isSignedIn = !!result;
});
setProviders(providers);
};
// Any sign-in/sign-out activity on any provider is observed here
const observeProviderState = () => {
providers.map((provider, index) => {
provider.api.session$().subscribe((signedInState: boolean) => {
const mutatedProvider = providers[index];
mutatedProvider.isSignedIn = signedInState;
providers[index] = mutatedProvider;
setProviders(providers.slice());
});
});
};
const OAuthProviderSidebarComponent: FC<OAuthProviderSidebarProps> = ({
title,
icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
setIsSignedIn();
observeProviderState();
}, [setIsSignedIn, observeProviderState]);
const checkSession = async () => {
const session = await api.getAccessToken('', { optional: true });
setSignedIn(!!session);
};
return providers;
const observeSession = () => {
api.session$().subscribe((signedInState: boolean) => {
if (signedIn !== signedInState) {
setSignedIn(signedInState);
}
});
};
checkSession();
observeSession();
}, []);
return (
<ProviderSidebarItemComponent
title={title}
icon={icon}
signedIn={signedIn}
api={api}
signInHandler={api.getAccessToken}
/>
);
};
const OIDCProviderSidebarComponent: FC<OIDCProviderSidebarProps> = ({
title,
icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
const checkSession = async () => {
const session = await api.getIdToken({ optional: true });
setSignedIn(!!session);
};
const observeSession = () => {
api.session$().subscribe((signedInState: boolean) => {
if (signedIn !== signedInState) {
setSignedIn(signedInState);
}
});
};
checkSession();
observeSession();
}, []);
return (
<ProviderSidebarItemComponent
title={title}
icon={icon}
signedIn={signedIn}
api={api}
signInHandler={api.getIdToken}
/>
);
};
const ProviderSidebarItemComponent: FC<{
title: string;
icon: any;
signedIn: boolean;
api: OAuthApi | OpenIdConnectApi;
signInHandler: Function;
}> = ({ title, icon, signedIn, api, signInHandler }) => {
return (
<SidebarItem
key={title}
text={title}
icon={icon ?? StarBorder}
disableSelected
>
<IconButton onClick={() => (signedIn ? api.logout() : signInHandler())}>
<Tooltip
placement="top"
arrow
title={signedIn ? `Sign out from ${title}` : `Sign in to ${title}`}
>
<PowerButton color={signedIn ? 'secondary' : 'primary'} />
</Tooltip>
</IconButton>
</SidebarItem>
);
};
const useStyles = makeStyles({
@@ -108,38 +163,35 @@ const useStyles = makeStyles({
},
});
export function SidebarUserSettings() {
const { isOpen: sidebarOpen } = useContext(SidebarContext);
const [open, setOpen] = React.useState(false);
const ref = useRef<Element>(); // for scrolling down when collapse item opens
const providers = useProviders();
export const SidebarUserProfile: FC<{ setOpen: Function }> = ({ setOpen }) => {
const [profile, setProfile] = useState<ProfileInfo>();
const ref = useRef<Element>(); // for scrolling down when collapse item opens
const googleAuth = useApi(googleAuthApiRef);
const classes = useStyles();
useEffect(() => {
const identityProvider = providers.find(
(provider: Provider) => provider.identity,
);
if (identityProvider?.isSignedIn) {
identityProvider?.api
.getProfile({ optional: true })
.then((userProfile: ProfileInfo) => {
setProfile(userProfile);
});
} else {
setProfile(undefined);
}
}, [providers, open]);
const handleClick = () => {
setOpen(!open);
setTimeout(() => ref.current?.scrollIntoView({ behavior: 'smooth' }), 300);
};
// Close the provider list when sidebar collapse
useEffect(() => {
if (!sidebarOpen && open) setOpen(false);
}, [open, sidebarOpen]);
const fetchProfile = async () => {
await googleAuth
.getProfile({ optional: true })
.then((userProfile?: ProfileInfo) => {
setProfile(userProfile);
});
};
const observeSession = () => {
googleAuth.session$().subscribe(() => {
fetchProfile();
});
};
fetchProfile();
observeSession();
}, [open]);
// Handle main auth info that is shown on the collapsible SidebarItem
let avatar;
@@ -170,37 +222,33 @@ export function SidebarUserSettings() {
>
{open ? <ExpandLess /> : <ExpandMore />}
</SidebarItem>
</>
);
};
export function SidebarUserSettings() {
const { isOpen: sidebarOpen } = useContext(SidebarContext);
const [open, setOpen] = React.useState(false);
// Close the provider list when sidebar collapse
useEffect(() => {
if (!sidebarOpen && open) setOpen(false);
}, [open, sidebarOpen]);
return (
<>
<SidebarUserProfile setOpen={setOpen} />
<Collapse in={open} timeout="auto" unmountOnExit>
{providers.map((provider: Provider) => (
<SidebarItem
key={provider.title}
text={provider.title}
icon={provider.icon ?? StarBorder}
disableSelected
>
<IconButton
onClick={() =>
provider.isSignedIn
? provider.api.logout()
: provider.api.getAccessToken()
}
>
<Tooltip
placement="top"
arrow
title={
provider.isSignedIn
? `Sign out from ${provider.title}`
: `Sign in to ${provider.title}`
}
>
<PowerButton
color={provider.isSignedIn ? 'secondary' : 'primary'}
/>
</Tooltip>
</IconButton>
</SidebarItem>
))}
<OIDCProviderSidebarComponent
title="Google"
apiRef={googleAuthApiRef}
icon={Star}
/>
<OAuthProviderSidebarComponent
title="Github"
apiRef={githubAuthApiRef}
icon={Star}
/>
</Collapse>
</>
);