remove frontend parts

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2021-12-30 11:39:23 +01:00
parent 761ff980f1
commit 88aad0803d
7 changed files with 0 additions and 380 deletions
-137
View File
@@ -1,137 +0,0 @@
---
id: provider
title: Google Identity-Aware Proxy Provider
sidebar_label: Google IAP
# prettier-ignore
description: Adding Google Identity-Aware Proxy as an authentication provider in Backstage
---
# Using Google Identity-Aware Proxy to authenticate requests
Backstage allows offloading the responsibility of authenticating users to the
Google HTTPS Load Balancer & [IAP](https://cloud.google.com/iap), leveraging the
authentication support on the latter.
This tutorial shows how to use authentication on an IAP sitting in front of
Backstage.
It is assumed an IAP is already serving traffic in front of a Backstage instance
configured to serve the frontend app from the backend.
## Frontend Changes
The Backstage app needs a `SignInPage` to be configured. When using IAP Proxy
authentication Backstage will only be loaded once the user has successfully
authenticated; we won't need to display a sign-in page as such, however we will
need to create a dummy `SignInPage` component that can fetch the token and other
information from the backend.
Create a sign-in page component in your app, for example in
`packages/app/src/components/SignInPage.tsx`.
```tsx
import { GcpIapIdentity } from '@backstage/core-app-api';
import { ErrorPanel, Progress } from '@backstage/core-components';
import {
discoveryApiRef,
fetchApiRef,
SignInPageProps,
useApi,
} from '@backstage/core-plugin-api';
import { ResponseError } from '@backstage/errors';
import React from 'react';
import { useAsync } from 'react-use';
export const GcpIapSignInPage = (props: SignInPageProps) => {
const discovery = useApi(discoveryApiRef);
const { fetch } = useApi(fetchApiRef);
const { loading, error } = useAsync(async () => {
const base = await discovery.getBaseUrl('auth');
const response = await fetch(`${base}/gcp-iap/refresh`, {
headers: { 'x-requested-with': 'XMLHttpRequest' },
credentials: 'include',
});
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
props.onSignInSuccess(GcpIapIdentity.fromResponse(await response.json()));
}, []);
if (loading) return <Progress />;
if (error) return <ErrorPanel error={error} />;
return null;
};
```
Pass this sign in page to your `createApp` function, in
`packages/app/src/App.tsx`.
```diff
+import { GcpIapSignInPage } from './components/SignInPage';
const app = createApp({
components: {
+ SignInPage: GcpIapSignInPage
```
## Backend Changes
- Add a `providerFactories` entry to the router in
`packages/backend/plugin/auth.ts`.
```ts
import { createGcpIapProvider } from '@backstage/plugin-auth-backend';
export default async function createPlugin({
logger,
database,
config,
discovery,
}: PluginEnvironment): Promise<Router> {
return await createRouter({
logger,
config,
database,
discovery,
providerFactories: {
'gcp-iap': createGcpIapProvider({
// Replace the auth handler if you want to customize the returned user
// profile info (can be left out; default implementation shown below
// which only returns the email.
async authHandler({ iapToken }) {
return { profile: { email: iapToken.email } };
},
signIn: {
// You need to supply an identity resolver, that takes the profile
// and the IAP token and produces the Backstage token with the
// relevant user info.
async resolver({ profile, result: { iapToken } }, ctx) {
// Somehow compute the Backstage token claims, just some dummy code
// shown here, but you may want to query your LDAP server, or
// GSuite or similar, based on the IAP token sub/email claims
const id = `user:default/${iapToken.email.split('@')[0]}`;
const fullEnt = ['group:default/team-name'];
const token = await ctx.tokenIssuer.issueToken({
claims: { sub: id, ent: fullEnt },
});
return { id, token };
},
},
}),
},
});
}
```
## Configuration
Use the following `auth` configuration:
```yaml
auth:
providers:
gcp-iap:
audience:
'/projects/<project number>/global/backendServices/<backend service id>'
```
-37
View File
@@ -21,9 +21,7 @@ import { AuthProviderInfo } from '@backstage/core-plugin-api';
import { AuthRequestOptions } from '@backstage/core-plugin-api';
import { BackstageIdentity } from '@backstage/core-plugin-api';
import { BackstageIdentityApi } from '@backstage/core-plugin-api';
import { BackstageIdentityResponse } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { BackstageUserIdentity } from '@backstage/core-plugin-api';
import { bitbucketAuthApiRef } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { Config } from '@backstage/config';
@@ -390,41 +388,6 @@ export type FlatRoutesProps = {
children: ReactNode;
};
// @public
export class GcpIapIdentity implements IdentityApi {
constructor(session: GcpIapSession);
static fromResponse(data: unknown): GcpIapIdentity;
// (undocumented)
getBackstageIdentity(): Promise<BackstageUserIdentity>;
// (undocumented)
getCredentials(): Promise<{
token?: string | undefined;
}>;
// (undocumented)
getIdToken(): Promise<string | undefined>;
// (undocumented)
getProfile(): ProfileInfo;
// (undocumented)
getProfileInfo(): Promise<ProfileInfo>;
// (undocumented)
getUserId(): string;
// (undocumented)
signOut(): Promise<void>;
}
// @public
export type GcpIapSession = {
providerInfo: {
iapToken: {
sub: string;
email: string;
[key: string]: unknown;
};
};
profile: ProfileInfo;
backstageIdentity: BackstageIdentityResponse;
};
// @public
export class GithubAuth implements OAuthApi, SessionApi {
// (undocumented)
@@ -1,74 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 {
BackstageUserIdentity,
IdentityApi,
ProfileInfo,
} from '@backstage/core-plugin-api';
import { GcpIapSession, gcpIapSessionSchema } from './types';
/**
* An identity API based on a Google Identity-Aware Proxy auth response.
*
* @public
*/
export class GcpIapIdentity implements IdentityApi {
/**
* Creates an identity instance based on the auth-backend API response.
*
* @param data - The raw JSON response from the auth-backend.
*/
static fromResponse(data: unknown): GcpIapIdentity {
const parsed = gcpIapSessionSchema.parse(data);
return new GcpIapIdentity(parsed);
}
constructor(private readonly session: GcpIapSession) {}
/** {@inheritdoc @backstage/core-plugin-api#IdentityApi.getUserId} */
getUserId(): string {
return this.session.backstageIdentity.id;
}
/** {@inheritdoc @backstage/core-plugin-api#IdentityApi.getIdToken} */
async getIdToken(): Promise<string | undefined> {
return this.session.backstageIdentity.token;
}
/** {@inheritdoc @backstage/core-plugin-api#IdentityApi.getProfile} */
getProfile(): ProfileInfo {
return this.session.profile;
}
/** {@inheritdoc @backstage/core-plugin-api#IdentityApi.getProfileInfo} */
async getProfileInfo(): Promise<ProfileInfo> {
return this.session.profile;
}
/** {@inheritdoc @backstage/core-plugin-api#IdentityApi.getBackstageIdentity} */
async getBackstageIdentity(): Promise<BackstageUserIdentity> {
return this.session.backstageIdentity.identity;
}
/** {@inheritdoc @backstage/core-plugin-api#IdentityApi.getCredentials} */
async getCredentials(): Promise<{ token?: string | undefined }> {
return { token: this.session.backstageIdentity.token };
}
/** {@inheritdoc @backstage/core-plugin-api#IdentityApi.signOut} */
async signOut(): Promise<void> {}
}
@@ -1,18 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { GcpIapIdentity } from './GcpIapIdentity';
export type { GcpIapSession } from './types';
@@ -1,50 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { TypeOf } from 'zod';
import { GcpIapSession, gcpIapSessionSchema } from './types';
describe('types', () => {
const responseData: GcpIapSession = {
providerInfo: {
iapToken: {
sub: 's',
email: 'e',
other: 7,
},
},
profile: {
email: 'e',
displayName: 'd',
picture: 'p',
},
backstageIdentity: {
id: 'i',
token: 't',
identity: {
type: 'user',
userEntityRef: 'ue',
ownershipEntityRefs: ['oe'],
},
},
};
it('has a compatible schema type', () => {
function f(_b: TypeOf<typeof gcpIapSessionSchema>) {}
f(responseData); // no tsc errors
expect(gcpIapSessionSchema.parse(responseData)).toEqual(responseData);
});
});
@@ -1,63 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 {
BackstageIdentityResponse,
ProfileInfo,
} from '@backstage/core-plugin-api';
import { z } from 'zod';
export const gcpIapSessionSchema = z.object({
providerInfo: z.object({
iapToken: z
.object({
sub: z.string(),
email: z.string(),
})
.catchall(z.unknown()),
}),
profile: z.object({
email: z.string().optional(),
displayName: z.string().optional(),
picture: z.string().optional(),
}),
backstageIdentity: z.object({
id: z.string(),
token: z.string(),
identity: z.object({
type: z.literal('user'),
userEntityRef: z.string(),
ownershipEntityRefs: z.array(z.string()),
}),
}),
});
/**
* Session information for Google Identity-Aware Proxy auth.
*
* @public
*/
export type GcpIapSession = {
providerInfo: {
iapToken: {
sub: string;
email: string;
[key: string]: unknown;
};
};
profile: ProfileInfo;
backstageIdentity: BackstageIdentityResponse;
};
@@ -25,5 +25,4 @@ export * from './microsoft';
export * from './onelogin';
export * from './bitbucket';
export * from './atlassian';
export * from './gcp-iap';
export type { OAuthApiCreateOptions, AuthApiCreateOptions } from './types';