Skip to content

Commit 04b64ae

Browse files
alkaline-0TsangingtonBMG93
authored
ADD: tests to google oauth (#22)
* ADD: GoogleAuth * ADD: Google Login Without Catalog For Testing Purposes * ADD: Custom Domain Check for code.berlin emails * ADD: details to readme about google client env variables * DEL: Github SignIn Provider In Frontend * DEL: Github SignIn Provider In Frontend * UPD: Refactored google oauth to make the function testable * ADD: added unit tests for index.test.ts to verify the logic provided to the google oauth resolver * ADD: install jest canvas mock * UPD: canvas issue * UPD: removed env variable email domain as it was breaking the workflow --------- Co-authored-by: tsangington <[email protected]> Co-authored-by: Brenden Gammill <[email protected]>
1 parent fe310eb commit 04b64ae

File tree

10 files changed

+403
-34
lines changed

10 files changed

+403
-34
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ BASE_URL="http://localhost:7007"
3939
GITHUB_CLIENT_ID="your-id"
4040
GITHUB_CLIENT_SECRET="your-secret"
4141
42+
GOOGLE_CLIENT_ID= "google_client_id"
43+
GOOGLE_CLIENT_SECRET= "google_client_secret"
44+
4245
GITHUB_TOKEN="your-token"
4346
4447
K8S_URL="k8s-url"
@@ -63,6 +66,12 @@ Keep it the same as it is right now, this is the url on which the application is
6366
<br>
6467
These environment variables are to setup correct [authentication](https://backstage.io/docs/getting-started/configuration#setting-up-authentication). Please follow [these](#github-auth) steps.
6568

69+
**`GOOGLE_CLIENT`:**
70+
<br>
71+
These environment variables are to allow google login with your code.berlin email.
72+
( https://console.cloud.google.com/apis/credentials/oauthclient/1006240973223-fs36u8kllipl761732fn565l6suviroh.apps.googleusercontent.com?authuser=0&project=code-idp&pli=1 )
73+
Use the link above and copy the client ID and secret.
74+
6675
**`GITHUB_TOKEN`:**
6776
<br>
6877
This environment variable is to configure the [GitHub integration](https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration),

app-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ auth:
7676
development:
7777
clientId: ${GITHUB_CLIENT_ID}
7878
clientSecret: ${GITHUB_CLIENT_SECRET}
79+
google:
80+
development:
81+
clientId: ${GOOGLE_CLIENT_ID}
82+
clientSecret: ${GOOGLE_CLIENT_SECRET}
7983

8084
kubernetes:
8185
serviceLocatorMethod:

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@
4747
"@backstage/e2e-test-utils": "^0.1.0",
4848
"@playwright/test": "^1.32.3",
4949
"@spotify/prettier-config": "^12.0.0",
50+
"canvas": "^2.11.2",
5051
"concurrently": "^8.0.0",
52+
"jest-canvas-mock": "^2.5.2",
53+
"jsdom": "^24.0.0",
5154
"lerna": "^7.3.0",
5255
"node-gyp": "^10.0.1",
5356
"prettier": "^2.3.2",
@@ -67,5 +70,4 @@
6770
"prettier --write"
6871
]
6972
}
70-
7173
}

packages/app/src/App.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React from 'react';
22
import { render, waitFor } from '@testing-library/react';
33
import App from './App';
4+
import 'jest-canvas-mock'
5+
6+
47

58
describe('App', () => {
69
it('should render', async () => {
@@ -19,9 +22,7 @@ describe('App', () => {
1922
},
2023
] as any,
2124
};
22-
23-
const rendered = render(<App />);
24-
25+
const rendered = render(<App />);
2526
await waitFor(() => {
2627
expect(rendered.baseElement).toBeInTheDocument();
2728
});

packages/app/src/App.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,22 @@ import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
3434
import { RequirePermission } from '@backstage/plugin-permission-react';
3535
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
3636

37-
import { githubAuthApiRef } from '@backstage/core-plugin-api';
38-
import { SignInPage } from '@backstage/core-components';
37+
import { githubAuthApiRef, googleAuthApiRef } from '@backstage/core-plugin-api';
38+
import { SignInPage, SignInProviderConfig } from '@backstage/core-components';
39+
40+
const githubProvider: SignInProviderConfig = {
41+
id: 'github-auth-provider',
42+
title: 'GitHub',
43+
message: 'Sign in using GitHub',
44+
apiRef: githubAuthApiRef,
45+
};
46+
const googleProvider: SignInProviderConfig = {
47+
id: 'google-auth-provider',
48+
title: 'Google',
49+
message: 'Sign in using Google',
50+
apiRef: googleAuthApiRef,
51+
};
52+
3953

4054
const app = createApp({
4155
apis,
@@ -44,12 +58,9 @@ const app = createApp({
4458
<SignInPage
4559
{...props}
4660
auto
47-
provider={{
48-
id: 'github-auth-provider',
49-
title: 'GitHub',
50-
message: 'Sign in using GitHub',
51-
apiRef: githubAuthApiRef,
52-
}}
61+
providers={[
62+
googleProvider
63+
]}
5364
/>
5465
),
5566
},

packages/backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@
4040
"@backstage/plugin-techdocs-backend": "^1.9.2",
4141
"app": "link:../app",
4242
"dockerode": "^3.3.1",
43+
"dotenv": "^16.4.5",
4344
"express": "^4.17.1",
4445
"express-promise-router": "^4.1.0",
46+
"jest-canvas-mock": "^2.5.2",
4547
"node-gyp": "^9.0.0",
4648
"pg": "^8.11.3",
4749
"winston": "^3.2.1"

packages/backend/src/index.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,69 @@
11
import { PluginEnvironment } from './types';
2+
import { resolverResult } from './plugins/plugins_helper/googleAuthResolver';
3+
import { TokenParams } from '@backstage/plugin-auth-backend';
4+
import 'jest-canvas-mock';
5+
6+
let mockProfile: any;
7+
let mockSignInInfo: any;
8+
let mockContext: any;
29

310
describe('test', () => {
411
it('unbreaks the test runner', () => {
512
const unbreaker = {} as PluginEnvironment;
613
expect(unbreaker).toBeTruthy();
714
});
815
});
16+
17+
describe('providers.google.create.signIn.resolver logic', () => {
18+
beforeEach(() => {
19+
mockProfile = {
20+
displayName: 'John Doe',
21+
picture: 'https://example.com/avatar.jpg',
22+
};
23+
mockSignInInfo = { profile: mockProfile, result: {} };
24+
mockContext = {
25+
issueToken: (params: TokenParams) => {
26+
// Mock implementation for issueToken method
27+
return { token: params.claims.sub + params.claims.ent };
28+
},
29+
findCatalogUser: jest.fn(),
30+
signInWithCatalogUser: jest.fn(),
31+
};
32+
});
33+
34+
it('should throw an exception for empty email address', async () => {
35+
mockProfile.email = '';
36+
mockSignInInfo.profile = mockProfile;
37+
38+
await expect(resolverResult(mockSignInInfo, mockContext)).rejects.toThrow(
39+
'Login failed, user profile does not contain a valid email',
40+
);
41+
});
42+
43+
it('should throw an exception for invalid non empty email address', async () => {
44+
mockProfile.email = 'test.example.com';
45+
mockSignInInfo.profile = mockProfile;
46+
47+
await expect(resolverResult(mockSignInInfo, mockContext)).rejects.toThrow(
48+
'Login failed, user profile does not contain a valid email',
49+
);
50+
});
51+
52+
it('should throw an exception for valid email address with incorrect domain', async () => {
53+
mockProfile.email = '[email protected]';
54+
mockSignInInfo.profile = mockProfile;
55+
56+
await expect(resolverResult(mockSignInInfo, mockContext)).rejects.toThrow(
57+
`Login failed due to incorrect email domain.`,
58+
);
59+
});
60+
61+
it('should return a token for valid email address with correct domain', async () => {
62+
mockProfile.email = '[email protected]';
63+
mockSignInInfo.profile = mockProfile;
64+
65+
return expect(resolverResult(mockSignInInfo, mockContext)).resolves.toEqual(
66+
{ token: 'user:default/john_doeuser:default/john_doe' },
67+
);
68+
});
69+
});

packages/backend/src/plugins/auth.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@backstage/plugin-auth-backend';
66
import { Router } from 'express';
77
import { PluginEnvironment } from '../types';
8+
import { resolverResult } from './plugins_helper/googleAuthResolver';
89

910
export default async function createPlugin(
1011
env: PluginEnvironment,
@@ -49,6 +50,11 @@ export default async function createPlugin(
4950
// resolver: providers.github.resolvers.usernameMatchingUserEntityName(),
5051
},
5152
}),
53+
google: providers.google.create({
54+
signIn: {
55+
resolver: resolverResult,
56+
},
57+
}),
5258
},
5359
});
5460
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
stringifyEntityRef,
3+
DEFAULT_NAMESPACE,
4+
} from '@backstage/catalog-model';
5+
import { OAuthResult } from '@backstage/plugin-auth-backend';
6+
import { SignInInfo, AuthResolverContext } from '@backstage/plugin-auth-node';
7+
8+
export const resolverResult = async (
9+
profile_input: SignInInfo<OAuthResult>,
10+
ctx: AuthResolverContext,
11+
) => {
12+
const profile = profile_input.profile;
13+
const regexp = new RegExp(
14+
/^(([^<>()\[\]\\.,;:\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,}))$/,
15+
);
16+
17+
if (!profile.email || !regexp.test(profile.email)) {
18+
throw new Error(
19+
'Login failed, user profile does not contain a valid email',
20+
);
21+
}
22+
23+
const [localPart, domain] = profile.email.split('@');
24+
25+
if (domain !== 'code.berlin') {
26+
throw new Error(`Login failed due to incorrect email domain.`);
27+
}
28+
29+
const userEntityRef = stringifyEntityRef({
30+
kind: 'User',
31+
name: localPart,
32+
namespace: DEFAULT_NAMESPACE,
33+
});
34+
35+
return ctx.issueToken({
36+
claims: {
37+
sub: userEntityRef,
38+
ent: [userEntityRef],
39+
},
40+
});
41+
};

0 commit comments

Comments
 (0)