Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ BASE_URL="http://localhost:7007"
GITHUB_CLIENT_ID="your-id"
GITHUB_CLIENT_SECRET="your-secret"

GOOGLE_CLIENT_ID= "google_client_id"
GOOGLE_CLIENT_SECRET= "google_client_secret"

GITHUB_TOKEN="your-token"

K8S_URL="k8s-url"
Expand All @@ -63,6 +66,12 @@ Keep it the same as it is right now, this is the url on which the application is
<br>
These environment variables are to setup correct [authentication](https://backstage.io/docs/getting-started/configuration#setting-up-authentication). Please follow [these](#github-auth) steps.

**`GOOGLE_CLIENT`:**
<br>
These environment variables are to allow google login with your code.berlin email.
( https://console.cloud.google.com/apis/credentials/oauthclient/1006240973223-fs36u8kllipl761732fn565l6suviroh.apps.googleusercontent.com?authuser=0&project=code-idp&pli=1 )
Use the link above and copy the client ID and secret.

**`GITHUB_TOKEN`:**
<br>
This environment variable is to configure the [GitHub integration](https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration),
Expand Down
4 changes: 4 additions & 0 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ auth:
development:
clientId: ${GITHUB_CLIENT_ID}
clientSecret: ${GITHUB_CLIENT_SECRET}
google:
development:
clientId: ${GOOGLE_CLIENT_ID}
clientSecret: ${GOOGLE_CLIENT_SECRET}

kubernetes:
serviceLocatorMethod:
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@
"@backstage/e2e-test-utils": "^0.1.0",
"@playwright/test": "^1.32.3",
"@spotify/prettier-config": "^12.0.0",
"canvas": "^2.11.2",
"concurrently": "^8.0.0",
"jest-canvas-mock": "^2.5.2",
"jsdom": "^24.0.0",
"lerna": "^7.3.0",
"node-gyp": "^10.0.1",
"prettier": "^2.3.2",
Expand All @@ -67,5 +70,4 @@
"prettier --write"
]
}

}
7 changes: 4 additions & 3 deletions packages/app/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import App from './App';
import 'jest-canvas-mock'



describe('App', () => {
it('should render', async () => {
Expand All @@ -19,9 +22,7 @@ describe('App', () => {
},
] as any,
};

const rendered = render(<App />);

const rendered = render(<App />);
await waitFor(() => {
expect(rendered.baseElement).toBeInTheDocument();
});
Expand Down
27 changes: 19 additions & 8 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,22 @@ import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
import { RequirePermission } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';

import { githubAuthApiRef } from '@backstage/core-plugin-api';
import { SignInPage } from '@backstage/core-components';
import { githubAuthApiRef, googleAuthApiRef } from '@backstage/core-plugin-api';
import { SignInPage, SignInProviderConfig } from '@backstage/core-components';

const githubProvider: SignInProviderConfig = {
id: 'github-auth-provider',
title: 'GitHub',
message: 'Sign in using GitHub',
apiRef: githubAuthApiRef,
};
const googleProvider: SignInProviderConfig = {
id: 'google-auth-provider',
title: 'Google',
message: 'Sign in using Google',
apiRef: googleAuthApiRef,
};


const app = createApp({
apis,
Expand All @@ -44,12 +58,9 @@ const app = createApp({
<SignInPage
{...props}
auto
provider={{
id: 'github-auth-provider',
title: 'GitHub',
message: 'Sign in using GitHub',
apiRef: githubAuthApiRef,
}}
providers={[
googleProvider
]}
/>
),
},
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
"@backstage/plugin-techdocs-backend": "^1.9.2",
"app": "link:../app",
"dockerode": "^3.3.1",
"dotenv": "^16.4.5",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"jest-canvas-mock": "^2.5.2",
"node-gyp": "^9.0.0",
"pg": "^8.11.3",
"winston": "^3.2.1"
Expand Down
61 changes: 61 additions & 0 deletions packages/backend/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,69 @@
import { PluginEnvironment } from './types';
import { resolverResult } from './plugins/plugins_helper/googleAuthResolver';
import { TokenParams } from '@backstage/plugin-auth-backend';
import 'jest-canvas-mock';

let mockProfile: any;
let mockSignInInfo: any;
let mockContext: any;

describe('test', () => {
it('unbreaks the test runner', () => {
const unbreaker = {} as PluginEnvironment;
expect(unbreaker).toBeTruthy();
});
});

describe('providers.google.create.signIn.resolver logic', () => {
beforeEach(() => {
mockProfile = {
displayName: 'John Doe',
picture: 'https://example.com/avatar.jpg',
};
mockSignInInfo = { profile: mockProfile, result: {} };
mockContext = {
issueToken: (params: TokenParams) => {
// Mock implementation for issueToken method
return { token: params.claims.sub + params.claims.ent };
},
findCatalogUser: jest.fn(),
signInWithCatalogUser: jest.fn(),
};
});

it('should throw an exception for empty email address', async () => {
mockProfile.email = '';
mockSignInInfo.profile = mockProfile;

await expect(resolverResult(mockSignInInfo, mockContext)).rejects.toThrow(
'Login failed, user profile does not contain a valid email',
);
});

it('should throw an exception for invalid non empty email address', async () => {
mockProfile.email = 'test.example.com';
mockSignInInfo.profile = mockProfile;

await expect(resolverResult(mockSignInInfo, mockContext)).rejects.toThrow(
'Login failed, user profile does not contain a valid email',
);
});

it('should throw an exception for valid email address with incorrect domain', async () => {
mockProfile.email = 'user@example.com';
mockSignInInfo.profile = mockProfile;

await expect(resolverResult(mockSignInInfo, mockContext)).rejects.toThrow(
`Login failed due to incorrect email domain.`,
);
});

it('should return a token for valid email address with correct domain', async () => {
mockProfile.email = 'john_doe@code.berlin';
mockSignInInfo.profile = mockProfile;

return expect(resolverResult(mockSignInInfo, mockContext)).resolves.toEqual(
{ token: 'user:default/john_doeuser:default/john_doe' },
);
});
});
6 changes: 6 additions & 0 deletions packages/backend/src/plugins/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from '@backstage/plugin-auth-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import { resolverResult } from './plugins_helper/googleAuthResolver';

export default async function createPlugin(
env: PluginEnvironment,
Expand Down Expand Up @@ -49,6 +50,11 @@ export default async function createPlugin(
// resolver: providers.github.resolvers.usernameMatchingUserEntityName(),
},
}),
google: providers.google.create({
signIn: {
resolver: resolverResult,
},
}),
},
});
}
41 changes: 41 additions & 0 deletions packages/backend/src/plugins/plugins_helper/googleAuthResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
stringifyEntityRef,
DEFAULT_NAMESPACE,
} from '@backstage/catalog-model';
import { OAuthResult } from '@backstage/plugin-auth-backend';
import { SignInInfo, AuthResolverContext } from '@backstage/plugin-auth-node';

export const resolverResult = async (
profile_input: SignInInfo<OAuthResult>,
ctx: AuthResolverContext,
) => {
const profile = profile_input.profile;
const regexp = new RegExp(
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
);

if (!profile.email || !regexp.test(profile.email)) {
throw new Error(
'Login failed, user profile does not contain a valid email',
);
}

const [localPart, domain] = profile.email.split('@');

if (domain !== 'code.berlin') {
throw new Error(`Login failed due to incorrect email domain.`);
}

const userEntityRef = stringifyEntityRef({
kind: 'User',
name: localPart,
namespace: DEFAULT_NAMESPACE,
});

return ctx.issueToken({
claims: {
sub: userEntityRef,
ent: [userEntityRef],
},
});
};
Loading