move components to separate files

This commit is contained in:
Raghunandan
2020-06-10 10:28:27 +02:00
parent 4c205d039d
commit 7accc4c0c0
15 changed files with 422 additions and 253 deletions
+11 -4
View File
@@ -168,8 +168,13 @@ export type ProfileInfo = {
picture?: string;
};
export type ObservableSession = {
session$(): Observable<boolean>;
export enum SessionState {
SignedIn = 'SignedIn',
SignedOut = 'SignedOut',
}
export type ObservableSessionStateApi = {
sessionState$(): Observable<SessionState>;
};
/**
* Provides authentication towards Google APIs and identities.
@@ -180,7 +185,7 @@ export type ObservableSession = {
* email and expiration information. Do not rely on any other fields, as they might not be present.
*/
export const googleAuthApiRef = createApiRef<
OAuthApi & OpenIdConnectApi & ProfileInfoApi & ObservableSession
OAuthApi & OpenIdConnectApi & ProfileInfoApi & ObservableSessionStateApi
>({
id: 'core.auth.google',
description: 'Provides authentication towards Google APIs and identities',
@@ -192,7 +197,9 @@ 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 & ObservableSession>({
export const githubAuthApiRef = createApiRef<
OAuthApi & ObservableSessionStateApi
>({
id: 'core.auth.github',
description: 'Provides authentication towards Github APIs',
});
@@ -0,0 +1,32 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed 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 { BehaviorSubject } from '../../../lib';
import { SessionState } from '../..';
export class SessionStateTracker {
private signedIn: boolean = false;
observable = new BehaviorSubject<SessionState>(SessionState.SignedOut);
setIsSignedId(isSignedIn: boolean) {
if (this.signedIn !== isSignedIn) {
this.signedIn = isSignedIn;
this.observable.next(
this.signedIn ? SessionState.SignedIn : SessionState.SignedOut,
);
}
}
}
@@ -20,13 +20,14 @@ import { GithubSession } from './types';
import {
OAuthApi,
AccessTokenOptions,
ObservableSession,
ObservableSessionStateApi,
SessionState,
} 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';
import { SessionStateTracker } from '../SessionStateTracker';
type CreateOptions = {
// TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GithubAuth
@@ -52,7 +53,7 @@ const DEFAULT_PROVIDER = {
icon: GithubIcon,
};
class GithubAuth implements OAuthApi, ObservableSession {
class GithubAuth implements OAuthApi, ObservableSessionStateApi {
static create({
apiOrigin,
basePath,
@@ -84,12 +85,10 @@ class GithubAuth implements OAuthApi, ObservableSession {
return new GithubAuth(sessionManager);
}
private readonly subject = new BehaviorSubject<boolean | undefined>(
undefined,
);
private readonly sessionStateTracker = new SessionStateTracker();
session$(): Observable<boolean | undefined> {
return this.subject;
sessionState$(): Observable<SessionState> {
return this.sessionStateTracker.observable;
}
constructor(private readonly sessionManager: SessionManager<GithubSession>) {}
@@ -100,7 +99,7 @@ class GithubAuth implements OAuthApi, ObservableSession {
...options,
scopes: normalizedScopes,
});
this.subject.next(!!session);
this.sessionStateTracker.setIsSignedId(!!session);
if (session) {
return session.accessToken;
}
@@ -109,7 +108,7 @@ class GithubAuth implements OAuthApi, ObservableSession {
async logout() {
await this.sessionManager.removeSession();
this.subject.next(false);
this.sessionStateTracker.setIsSignedId(false);
}
static normalizeScope(scope?: string): Set<string> {
@@ -25,13 +25,14 @@ import {
ProfileInfoApi,
ProfileInfoOptions,
ProfileInfo,
ObservableSession,
ObservableSessionStateApi,
SessionState,
} from '../../../definitions/auth';
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager';
import { BehaviorSubject } from '../../../../lib';
import { Observable } from '../../../../types';
import { SessionStateTracker } from '../SessionStateTracker';
type CreateOptions = {
// TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GoogleAuth
@@ -61,7 +62,11 @@ const DEFAULT_PROVIDER = {
const SCOPE_PREFIX = 'https://www.googleapis.com/auth/';
class GoogleAuth
implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, ObservableSession {
implements
OAuthApi,
OpenIdConnectApi,
ProfileInfoApi,
ObservableSessionStateApi {
static create({
apiOrigin,
basePath,
@@ -103,12 +108,10 @@ class GoogleAuth
return new GoogleAuth(sessionManager);
}
private readonly subject = new BehaviorSubject<boolean | undefined>(
undefined,
);
private readonly sessionStateTracker = new SessionStateTracker();
session$(): Observable<boolean | undefined> {
return this.subject;
sessionState$(): Observable<SessionState> {
return this.sessionStateTracker.observable;
}
constructor(private readonly sessionManager: SessionManager<GoogleSession>) {}
@@ -122,7 +125,7 @@ class GoogleAuth
...options,
scopes: normalizedScopes,
});
this.subject.next(!!session);
this.sessionStateTracker.setIsSignedId(!!session);
if (session) {
return session.accessToken;
}
@@ -131,7 +134,7 @@ class GoogleAuth
async getIdToken(options: IdTokenOptions = {}) {
const session = await this.sessionManager.getSession(options);
this.subject.next(!!session);
this.sessionStateTracker.setIsSignedId(!!session);
if (session) {
return session.idToken;
}
@@ -140,7 +143,7 @@ class GoogleAuth
async logout() {
await this.sessionManager.removeSession();
this.subject.next(false);
this.sessionStateTracker.setIsSignedId(false);
}
async getProfile(options: ProfileInfoOptions = {}) {
@@ -131,15 +131,6 @@ describe('RefreshingAuthSessionManager', () => {
});
it('should remove session and reload', async () => {
// This is a workaround that is used by Facebook and the Jest core team
// It is a limitation with the newest versions of JSDOM, and newer browser standards
// where window.location and all of its properties are read-only. So we re-construct it!
// See https://github.com/facebook/jest/issues/890#issuecomment-209698782
const location = { ...window.location };
delete window.location;
window.location = location;
jest.spyOn(window.location, 'reload').mockImplementation();
const removeSession = jest.fn();
const manager = new RefreshingAuthSessionManager({
connector: { removeSession },
@@ -147,7 +138,7 @@ describe('RefreshingAuthSessionManager', () => {
} as any);
await manager.removeSession();
expect(window.location.reload).toHaveBeenCalled();
expect(removeSession).toHaveBeenCalled();
expect(await manager.getSession({ optional: true })).toBe(undefined);
});
});
@@ -84,11 +84,6 @@ describe('StaticAuthSessionManager', () => {
});
it('should remove session and reload', async () => {
const location = { ...window.location };
delete window.location;
window.location = location;
jest.spyOn(window.location, 'reload').mockImplementation();
const removeSession = jest.fn();
const manager = new StaticAuthSessionManager({
connector: { removeSession },
@@ -96,7 +91,7 @@ describe('StaticAuthSessionManager', () => {
} as any);
await manager.removeSession();
expect(window.location.reload).toHaveBeenCalled();
expect(removeSession).toHaveBeenCalled();
expect(await manager.getSession({ optional: true })).toBe(undefined);
});
});
+4 -1
View File
@@ -58,8 +58,11 @@ const useStyles = makeStyles<Theme>(theme => {
// XXX (@koroeskohr): I can't seem to achieve the desired font-weight from the designs
fontWeight: 'bold',
whiteSpace: 'nowrap',
lineHeight: 1.0,
lineHeight: 'auto',
flex: '3 1 auto',
width: '110px',
overflow: 'hidden',
'text-overflow': 'ellipsis',
},
iconContainer: {
boxSizing: 'border-box',
@@ -0,0 +1,73 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed 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 {
ApiRef,
OAuthApi,
ObservableSessionStateApi,
useApi,
Subscription,
IconComponent,
SessionState,
} from '@backstage/core-api';
import React, { FC, useState, useEffect } from 'react';
import { ProviderSettingsItem } from './ProviderSettingsItem';
type OAuthProviderSidebarProps = {
title: string;
icon: IconComponent;
apiRef: ApiRef<OAuthApi & ObservableSessionStateApi>;
};
export const OAuthProviderSettings: FC<OAuthProviderSidebarProps> = ({
title,
icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
const checkSession = async () => {
const session = await api.getAccessToken('', { optional: true });
setSignedIn(!!session);
};
let subscription: Subscription;
const observeSession = () => {
subscription = api
.sessionState$()
.subscribe((sessionState: SessionState) => {
setSignedIn(sessionState === SessionState.SignedIn);
});
};
checkSession();
observeSession();
return () => {
subscription.unsubscribe();
};
}, [api]);
return (
<ProviderSettingsItem
title={title}
icon={icon}
signedIn={signedIn}
api={api}
signInHandler={() => api.getAccessToken()}
/>
);
};
@@ -0,0 +1,74 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed 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 {
ApiRef,
OpenIdConnectApi,
ObservableSessionStateApi,
useApi,
Subscription,
IconComponent,
SessionState,
} from '@backstage/core-api';
import React, { FC, useState, useEffect } from 'react';
import { ProviderSettingsItem } from './ProviderSettingsItem';
export type OIDCProviderSidebarProps = {
title: string;
icon: IconComponent;
apiRef: ApiRef<OpenIdConnectApi & ObservableSessionStateApi>;
};
export const OIDCProviderSettings: 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);
};
let subscription: Subscription;
const observeSession = () => {
subscription = api
.sessionState$()
.subscribe((sessionState: SessionState) => {
setSignedIn(sessionState === SessionState.SignedIn);
});
};
checkSession();
observeSession();
return () => {
subscription.unsubscribe();
};
}, [api]);
return (
<ProviderSettingsItem
title={title}
icon={icon}
signedIn={signedIn}
api={api}
signInHandler={() => api.getIdToken()}
/>
);
};
@@ -0,0 +1,49 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed 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 React, { FC } from 'react';
import { OAuthApi, OpenIdConnectApi, IconComponent } from '@backstage/core-api';
import { SidebarItem } from '../Items';
import { IconButton, Tooltip } from '@material-ui/core';
import StarBorder from '@material-ui/icons/StarBorder';
import PowerButton from '@material-ui/icons/PowerSettingsNew';
export const ProviderSettingsItem: FC<{
title: string;
icon: IconComponent;
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>
);
};
@@ -0,0 +1,112 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed 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 React, { FC, useState, useRef, useEffect } from 'react';
import { makeStyles, Avatar, Divider } from '@material-ui/core';
import {
ProfileInfo,
useApi,
googleAuthApiRef,
Subscription,
SessionState,
} from '@backstage/core-api';
import { SidebarItem } from '../Items';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
const useStyles = makeStyles({
avatar: {
width: 24,
height: 24,
},
});
export const UserProfile: FC<{ open: boolean; setOpen: Function }> = ({
open,
setOpen,
}) => {
const [profile, setProfile] = useState<ProfileInfo>();
const ref = useRef<Element>(); // for scrolling down when collapse item opens
const googleAuth = useApi(googleAuthApiRef);
const classes = useStyles();
const handleClick = () => {
setOpen(!open);
setTimeout(() => ref.current?.scrollIntoView({ behavior: 'smooth' }), 300);
};
useEffect(() => {
const fetchProfile = async () => {
await googleAuth
.getProfile({ optional: true })
.then((userProfile?: ProfileInfo) => {
setProfile(userProfile);
});
};
let subscription: Subscription;
const observeSession = () => {
subscription = googleAuth
.sessionState$()
.subscribe(async (sessionState: SessionState) => {
if (sessionState === SessionState.SignedIn) {
await fetchProfile();
} else {
setProfile(undefined);
}
});
};
fetchProfile();
observeSession();
return () => {
subscription.unsubscribe();
};
}, [googleAuth]);
// Handle main auth info that is shown on the collapsible SidebarItem
let avatar;
let displayName = 'Guest';
if (profile) {
const email = profile.email;
const name = profile.name;
const imageUrl = profile.picture;
const emailTrimmed = email.split('@')[0];
const displayEmail =
emailTrimmed.charAt(0).toUpperCase() + emailTrimmed.slice(1);
displayName = name ?? displayEmail;
avatar = imageUrl
? () => (
<Avatar alt={displayName} src={imageUrl} className={classes.avatar} />
)
: () => <Avatar alt={displayName} className={classes.avatar} />;
}
return (
<>
<Divider innerRef={ref} />
<SidebarItem
text={displayName}
onClick={handleClick}
icon={avatar || AccountCircleIcon}
disableSelected
>
{open ? <ExpandLess /> : <ExpandMore />}
</SidebarItem>
</>
);
};
@@ -0,0 +1,20 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed 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 { ProviderSettingsItem } from './ProviderSettingsItem';
export { OAuthProviderSettings } from './OAuthProviderSettings';
export { OIDCProviderSettings } from './OIDCProviderSettings';
export { UserProfile } from './UserProfile';
+10 -211
View File
@@ -14,217 +14,16 @@
* limitations under the License.
*/
import React, { useState, useContext, useEffect, useRef, FC } from 'react';
import React, { useContext, useEffect } from 'react';
import Collapse from '@material-ui/core/Collapse';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import StarBorder from '@material-ui/icons/StarBorder';
import Star from '@material-ui/icons/Star';
import { SidebarContext } from './config';
import { SidebarItem } from './Items';
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
import Divider from '@material-ui/core/Divider';
import { googleAuthApiRef, githubAuthApiRef } from '@backstage/core-api';
import {
useApi,
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 Provider = {
title: string;
icon: any;
};
type OAuthProviderSidebarProps = {
title: string;
icon: any;
apiRef: ApiRef<OAuthApi & ObservableSession>;
};
type OIDCProviderSidebarProps = {
title: string;
icon: any;
apiRef: ApiRef<OpenIdConnectApi & ObservableSession>;
};
const OAuthProviderSidebarComponent: FC<OAuthProviderSidebarProps> = ({
title,
icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
const checkSession = async () => {
const session = await api.getAccessToken('', { 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.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({
avatar: {
width: 24,
height: 24,
},
});
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();
const handleClick = () => {
setOpen(!open);
setTimeout(() => ref.current?.scrollIntoView({ behavior: 'smooth' }), 300);
};
useEffect(() => {
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;
let displayName = 'Guest';
if (profile) {
const email = profile.email;
const name = profile.name;
const imageUrl = profile.picture;
const emailTrimmed = email.split('@')[0];
const displayEmail =
emailTrimmed.charAt(0).toUpperCase() + emailTrimmed.slice(1);
displayName = name ?? displayEmail;
avatar = imageUrl
? () => (
<Avatar alt={displayName} src={imageUrl} className={classes.avatar} />
)
: () => <Avatar alt={displayName} className={classes.avatar} />;
}
return (
<>
<Divider innerRef={ref} />
<SidebarItem
text={displayName}
onClick={handleClick}
icon={avatar || AccountCircleIcon}
disableSelected
>
{open ? <ExpandLess /> : <ExpandMore />}
</SidebarItem>
</>
);
};
OAuthProviderSettings,
OIDCProviderSettings,
UserProfile as SidebarUserProfile,
} from './Settings';
export function SidebarUserSettings() {
const { isOpen: sidebarOpen } = useContext(SidebarContext);
@@ -237,14 +36,14 @@ export function SidebarUserSettings() {
return (
<>
<SidebarUserProfile setOpen={setOpen} />
<Collapse in={open} timeout="auto" unmountOnExit>
<OIDCProviderSidebarComponent
<SidebarUserProfile open={open} setOpen={setOpen} />
<Collapse in={open} timeout="auto">
<OIDCProviderSettings
title="Google"
apiRef={googleAuthApiRef}
icon={Star}
/>
<OAuthProviderSidebarComponent
<OAuthProviderSettings
title="Github"
apiRef={githubAuthApiRef}
icon={Star}
@@ -34,3 +34,4 @@ export {
export type { SidebarContextType } from './config';
export { SidebarThemeToggle } from './SidebarThemeToggle';
export { SidebarUserSettings } from './UserSettings';
export * from './Settings';
+11
View File
@@ -5,10 +5,12 @@ import {
oauthRequestApiRef,
OAuthRequestManager,
googleAuthApiRef,
githubAuthApiRef,
AlertApiForwarder,
ErrorApiForwarder,
ErrorAlerter,
GoogleAuth,
GithubAuth,
} from '@backstage/core';
const builder = ApiRegistry.builder();
@@ -31,4 +33,13 @@ builder.add(
}),
);
builder.add(
githubAuthApiRef,
GithubAuth.create({
apiOrigin: 'http://localhost:7000',
basePath: '/auth/',
oauthRequestApi,
}),
);
export const apis = builder.build();