diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index 8d76c75d91..d6658c891c 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -25,9 +25,11 @@ import { featureFlagsApiRef, FeatureFlags, GoogleAuth, + GithubAuth, oauthRequestApiRef, OAuthRequestManager, googleAuthApiRef, + githubAuthApiRef, } from '@backstage/core'; import { @@ -63,6 +65,15 @@ builder.add( }), ); +builder.add( + githubAuthApiRef, + GithubAuth.create({ + apiOrigin: 'http://localhost:7000', + basePath: '/auth/', + oauthRequestApi, + }), +); + builder.add( techRadarApiRef, new TechRadar({ diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts new file mode 100644 index 0000000000..3d3da266fc --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts @@ -0,0 +1,31 @@ +/* + * 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 GithubAuth from './GithubAuth'; + +const theFuture = new Date(Date.now() + 3600000); + +describe('GithubAuth', () => { + it('should get refreshed access token', async () => { + const getSession = jest + .fn() + .mockResolvedValue({ accessToken: 'access-token', expiresAt: theFuture }); + const githubAuth = new GithubAuth({ getSession } as any); + + expect(await githubAuth.getAccessToken()).toBe('access-token'); + expect(getSession).toBeCalledTimes(1); + }); +}); diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts new file mode 100644 index 0000000000..d20eaa54cb --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts @@ -0,0 +1,118 @@ +/* + * 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 GithubIcon from '@material-ui/icons/AcUnit'; +import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; +import { GithubSession } from './types'; +import { OAuthApi, AccessTokenOptions } from '../../../definitions/auth'; +import { OAuthRequestApi, AuthProvider } from '../../../definitions'; +import { SessionManager } from '../../../../lib/AuthSessionManager/types'; +import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; + +type CreateOptions = { + // TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GithubAuth + apiOrigin: string; + basePath: string; + + oauthRequestApi: OAuthRequestApi; + + environment?: string; + provider?: AuthProvider & { id: string }; +}; + +export type GithubAuthResponse = { + accessToken: string; + idToken: string; + scope: string; + expiresInSeconds: number; +}; + +const DEFAULT_PROVIDER = { + id: 'github', + title: 'Github', + icon: GithubIcon, +}; + +class GithubAuth implements OAuthApi { + static create({ + apiOrigin, + basePath, + environment = 'dev', + provider = DEFAULT_PROVIDER, + oauthRequestApi, + }: CreateOptions) { + const connector = new DefaultAuthConnector({ + apiOrigin, + basePath, + environment, + provider, + oauthRequestApi: oauthRequestApi, + sessionTransform(res: GithubAuthResponse): GithubSession { + return { + accessToken: res.accessToken, + scopes: GithubAuth.normalizeScopes(res.scope), + expiresAt: new Date(Date.now() + res.expiresInSeconds * 1000), + }; + }, + }); + + const sessionManager = new RefreshingAuthSessionManager({ + connector, + defaultScopes: new Set(['user']), + sessionScopes: session => session.scopes, + sessionShouldRefresh: session => { + const expiresInSec = (session.expiresAt.getTime() - Date.now()) / 1000; + return expiresInSec < 60 * 5; + }, + }); + + return new GithubAuth(sessionManager); + } + + constructor(private readonly sessionManager: SessionManager) {} + + async getAccessToken( + scope?: string | string[], + options?: AccessTokenOptions, + ) { + const normalizedScopes = GithubAuth.normalizeScopes(scope); + const session = await this.sessionManager.getSession({ + ...options, + scopes: normalizedScopes, + }); + if (session) { + return session.accessToken; + } + return ''; + } + + async logout() { + await this.sessionManager.removeSession(); + } + + static normalizeScopes(scopes?: string | string[]): Set { + if (!scopes) { + return new Set(); + } + + const scopeList = Array.isArray(scopes) + ? scopes + : scopes.split(/[\s]/).filter(Boolean); + + return new Set(scopeList); + } +} +export default GithubAuth; diff --git a/packages/core-api/src/apis/implementations/auth/github/index.ts b/packages/core-api/src/apis/implementations/auth/github/index.ts new file mode 100644 index 0000000000..9e1722f4a4 --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/github/index.ts @@ -0,0 +1,18 @@ +/* + * 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 * from './types'; +export { default as GithubAuth } from './GithubAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/github/types.ts b/packages/core-api/src/apis/implementations/auth/github/types.ts new file mode 100644 index 0000000000..282017b80d --- /dev/null +++ b/packages/core-api/src/apis/implementations/auth/github/types.ts @@ -0,0 +1,21 @@ +/* + * 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 type GithubSession = { + accessToken: string; + scopes: Set; + expiresAt: Date; +}; diff --git a/packages/core-api/src/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts index 5fa6644b2a..f13368b5c4 100644 --- a/packages/core-api/src/apis/implementations/auth/index.ts +++ b/packages/core-api/src/apis/implementations/auth/index.ts @@ -15,3 +15,4 @@ */ export * from './google'; +export * from './github';