diff --git a/.changeset/fair-lines-warn.md b/.changeset/fair-lines-warn.md new file mode 100644 index 0000000000..ffd387efbf --- /dev/null +++ b/.changeset/fair-lines-warn.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-auth-backend-module-github-provider': minor +'@backstage/plugin-catalog-backend-module-github': minor +--- + +Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. diff --git a/plugins/auth-backend-module-github-provider/report.api.md b/plugins/auth-backend-module-github-provider/report.api.md index 6bad840d00..4581115679 100644 --- a/plugins/auth-backend-module-github-provider/report.api.md +++ b/plugins/auth-backend-module-github-provider/report.api.md @@ -17,13 +17,25 @@ export default authModuleGithubProvider; // @public (undocumented) export const githubAuthenticator: OAuthAuthenticator< PassportOAuthAuthenticatorHelper, - PassportProfile + GithubProfile >; +// @public (undocumented) +export type GithubProfile = PassportProfile & { + nodeId?: string; +}; + // @public export namespace githubSignInResolvers { const usernameMatchingUserEntityName: SignInResolverFactory< - OAuthAuthenticatorResult, + OAuthAuthenticatorResult, + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined + >; + const userIdMatchingUserEntityAnnotation: SignInResolverFactory< + OAuthAuthenticatorResult, | { dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; } diff --git a/plugins/auth-backend-module-github-provider/src/authenticator.ts b/plugins/auth-backend-module-github-provider/src/authenticator.ts index e9dc29e4a6..fa3e2b0987 100644 --- a/plugins/auth-backend-module-github-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-github-provider/src/authenticator.ts @@ -22,10 +22,18 @@ import { PassportProfile, } from '@backstage/plugin-auth-node'; +/** @public */ +export type GithubProfile = PassportProfile & { + nodeId?: string; +}; + const ACCESS_TOKEN_PREFIX = 'access-token-v2.'; /** @public */ -export const githubAuthenticator = createOAuthAuthenticator({ +export const githubAuthenticator = createOAuthAuthenticator< + PassportOAuthAuthenticatorHelper, + GithubProfile +>({ defaultProfileTransform: PassportOAuthAuthenticatorHelper.defaultProfileTransform, scopes: { @@ -90,7 +98,7 @@ export const githubAuthenticator = createOAuthAuthenticator({ if (!session.refreshToken && !session.expiresInSeconds) { session.refreshToken = ACCESS_TOKEN_PREFIX + session.accessToken; } - return { fullProfile, session }; + return { fullProfile: fullProfile as GithubProfile, session }; }, async refresh(input, helper) { @@ -115,7 +123,7 @@ export const githubAuthenticator = createOAuthAuthenticator({ }); return { - fullProfile, + fullProfile: fullProfile as GithubProfile, session: { accessToken, tokenType: 'bearer', diff --git a/plugins/auth-backend-module-github-provider/src/index.ts b/plugins/auth-backend-module-github-provider/src/index.ts index 646ba7c92a..678e188461 100644 --- a/plugins/auth-backend-module-github-provider/src/index.ts +++ b/plugins/auth-backend-module-github-provider/src/index.ts @@ -20,6 +20,6 @@ * @packageDocumentation */ -export { githubAuthenticator } from './authenticator'; +export { githubAuthenticator, type GithubProfile } from './authenticator'; export { authModuleGithubProvider as default } from './module'; export { githubSignInResolvers } from './resolvers'; diff --git a/plugins/auth-backend-module-github-provider/src/resolvers.ts b/plugins/auth-backend-module-github-provider/src/resolvers.ts index 5a6934439f..e065432826 100644 --- a/plugins/auth-backend-module-github-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-github-provider/src/resolvers.ts @@ -17,11 +17,12 @@ import { createSignInResolverFactory, OAuthAuthenticatorResult, - PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; import { z } from 'zod'; +import { GithubProfile } from './authenticator'; + /** * Available sign-in resolvers for the GitHub auth provider. * @@ -39,7 +40,7 @@ export namespace githubSignInResolvers { .optional(), create(options = {}) { return async ( - info: SignInInfo>, + info: SignInInfo>, ctx, ) => { const { fullProfile } = info.result; @@ -63,4 +64,44 @@ export namespace githubSignInResolvers { }; }, }); + + /** + * Looks up the user by matching their GitHub user ID to the github.com/user-id annotation. + */ + export const userIdMatchingUserEntityAnnotation = createSignInResolverFactory( + { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { fullProfile } = info.result; + + const userId = fullProfile.nodeId; + if (!userId) { + throw new Error(`GitHub user profile does not contain a user ID`); + } + + return ctx.signInWithCatalogUser( + { + annotations: { + 'github.com/user-id': userId, + }, + }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: userId } } + : undefined, + }, + ); + }; + }, + }, + ); } diff --git a/plugins/catalog-backend-module-github/report.api.md b/plugins/catalog-backend-module-github/report.api.md index aa275cfc9e..25fc58be79 100644 --- a/plugins/catalog-backend-module-github/report.api.md +++ b/plugins/catalog-backend-module-github/report.api.md @@ -307,6 +307,7 @@ export type GithubTeam = { // @public export type GithubUser = { login: string; + id?: string; bio?: string; avatarUrl?: string; email?: string; diff --git a/plugins/catalog-backend-module-github/src/lib/annotation.ts b/plugins/catalog-backend-module-github/src/lib/annotation.ts index 4db6ffb975..2d06388d65 100644 --- a/plugins/catalog-backend-module-github/src/lib/annotation.ts +++ b/plugins/catalog-backend-module-github/src/lib/annotation.ts @@ -35,3 +35,13 @@ export const ANNOTATION_GITHUB_USER_LOGIN = 'github.com/user-login'; * @public */ export const ANNOTATION_GITHUB_TEAM_SLUG = 'github.com/team-slug'; + +/** + * The value of this annotation is the unique identifier for a user on + * [GitHub](https://github.com) (either the public one, or a private GitHub + * Enterprise installation) that is related to this entity. This is a stable + * identifier that persists even if the user changes their login name. + * + * @public + */ +export const ANNOTATION_GITHUB_USER_ID = 'github.com/user-id'; diff --git a/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts b/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts index 6befd32056..4de9af89c8 100644 --- a/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts +++ b/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts @@ -19,6 +19,7 @@ import { graphql } from '@octokit/graphql'; import { ANNOTATION_GITHUB_TEAM_SLUG, ANNOTATION_GITHUB_USER_LOGIN, + ANNOTATION_GITHUB_USER_ID, } from './annotation'; import { GithubTeam, GithubUser } from './github'; @@ -69,6 +70,7 @@ export const defaultUserTransformer = async ( name: item.login, annotations: { [ANNOTATION_GITHUB_USER_LOGIN]: item.login, + ...(item.id && { [ANNOTATION_GITHUB_USER_ID]: item.id }), }, }, spec: { diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index bd77df4da7..0690ca125b 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -112,6 +112,7 @@ export type GithubOrg = { */ export type GithubUser = { login: string; + id?: string; bio?: string; avatarUrl?: string; email?: string; @@ -202,6 +203,7 @@ export async function getOrganizationUsers( avatarUrl, bio, email @include(if: $email), + id, login, name, ${suspendedAtField} @@ -268,6 +270,7 @@ export async function getOrganizationTeams( avatarUrl, bio, email, + id, login, name, organizationVerifiedDomainEmails(login: $org) @@ -362,6 +365,7 @@ export async function getOrganizationTeamsFromUsers( avatarUrl, bio, email, + id, login, name, organizationVerifiedDomainEmails(login: $org) diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index ac73430d33..48050e96bb 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -38,3 +38,8 @@ export { buildOrgHierarchy, } from './org'; export { parseGithubOrgUrl } from './util'; +export { + ANNOTATION_GITHUB_USER_LOGIN, + ANNOTATION_GITHUB_TEAM_SLUG, + ANNOTATION_GITHUB_USER_ID, +} from './annotation'; diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts index 1aa1af46d7..e80579ee36 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts @@ -94,6 +94,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -101,6 +102,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'x', + id: 'g', name: 'y', bio: 'z', email: 'w', @@ -128,7 +130,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'x' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'x', id: 'g' }, + ], }, }, ], @@ -156,6 +161,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'q', + id: 'h', name: 'r', bio: 's', email: 't', @@ -183,7 +189,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'q' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'q', id: 'h' }, + ], }, }, ], @@ -215,6 +224,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -241,6 +251,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/x', 'github.com/user-login': 'x', + 'github.com/user-id': 'g', }, description: 'z', name: 'x', @@ -267,6 +278,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/q', 'github.com/user-login': 'q', + 'github.com/user-id': 'h', }, description: 's', name: 'q', @@ -382,6 +394,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -389,6 +402,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'x', + id: 'g', name: 'y', bio: 'z', email: 'w', @@ -416,7 +430,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'x' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'x', id: 'g' }, + ], }, }, ], @@ -444,6 +461,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'q', + id: 'h', name: 'r', bio: 's', email: 't', @@ -471,7 +489,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'q' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'q', id: 'h' }, + ], }, }, ], @@ -517,6 +538,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -543,6 +565,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/x', 'github.com/user-login': 'x', + 'github.com/user-id': 'g', }, description: 'z', name: 'x', @@ -569,6 +592,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/q', 'github.com/user-login': 'q', + 'github.com/user-id': 'h', }, description: 's', name: 'q', @@ -656,6 +680,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -663,6 +688,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'x', + id: 'g', name: 'y', bio: 'z', email: 'w', @@ -690,7 +716,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'x' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'x', id: 'g' }, + ], }, }, ], @@ -718,6 +747,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'q', + id: 'h', name: 'r', bio: 's', email: 't', @@ -745,7 +775,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'q' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'q', id: 'h' }, + ], }, }, ], @@ -783,6 +816,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -809,6 +843,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/x', 'github.com/user-login': 'x', + 'github.com/user-id': 'g', }, description: 'z', name: 'x', @@ -835,6 +870,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/q', 'github.com/user-login': 'q', + 'github.com/user-id': 'h', }, description: 's', name: 'q', @@ -1099,6 +1135,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -1126,7 +1163,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }], + nodes: [{ login: 'a', id: 'f' }], }, }, ], @@ -1151,7 +1188,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }], + nodes: [{ login: 'a', id: 'f' }], }, }, ], @@ -1176,7 +1213,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }], + nodes: [{ login: 'a', id: 'f' }], }, }, ], @@ -1213,6 +1250,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -1300,7 +1338,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -1328,6 +1366,7 @@ describe('GithubMultiOrgEntityProvider', () => { membership: { user: { name: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', login: 'a', @@ -1351,6 +1390,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, @@ -1398,6 +1438,7 @@ describe('GithubMultiOrgEntityProvider', () => { membership: { user: { name: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', login: 'a', @@ -1422,6 +1463,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, @@ -1473,7 +1515,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -1493,6 +1535,7 @@ describe('GithubMultiOrgEntityProvider', () => { membership: { user: { name: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', login: 'a', @@ -1516,6 +1559,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, @@ -1729,7 +1773,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, }, @@ -1741,6 +1785,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -1748,6 +1793,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'w', + id: 'i', name: 'x', bio: 'y', email: 'z', @@ -1774,7 +1820,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -1798,7 +1844,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -1847,6 +1893,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', description: 'c', @@ -2110,7 +2157,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, }, @@ -2147,7 +2194,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -2171,7 +2218,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -2194,6 +2241,7 @@ describe('GithubMultiOrgEntityProvider', () => { member: { name: 'a', login: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', }, @@ -2245,6 +2293,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, @@ -2327,7 +2376,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -2350,6 +2399,7 @@ describe('GithubMultiOrgEntityProvider', () => { member: { name: 'a', login: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', }, @@ -2401,6 +2451,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index 173da84f58..28d598b5b1 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -531,7 +531,13 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const userTransformer = this.options.userTransformer || defaultUserTransformer; - const { name, avatar_url: avatarUrl, email, login } = event.membership.user; + const { + name, + avatar_url: avatarUrl, + email, + login, + node_id, + } = event.membership.user; const org = event.organization.login; const { headers } = await this.options.githubCredentialsProvider.getCredentials({ @@ -573,6 +579,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { avatarUrl, login, email: email ?? undefined, + id: node_id, }, { org, @@ -818,13 +825,14 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const userTransformer = this.options.userTransformer || defaultUserTransformer; - const { name, avatar_url: avatarUrl, email, login } = event.member; + const { name, avatar_url: avatarUrl, email, login, node_id } = event.member; const user = await userTransformer( { name, avatarUrl, login, email: email ?? undefined, + id: node_id, }, { org, diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 17fa00b137..9be5a3e161 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -89,6 +89,7 @@ describe('GithubOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -136,6 +137,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -311,6 +313,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, }, spec: { @@ -331,6 +334,7 @@ describe('GithubOrgEntityProvider', () => { user: { name: 'githubuser', login: 'githubuser', + node_id: 'githubuserId', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', }, @@ -398,6 +402,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, }, spec: { @@ -418,6 +423,7 @@ describe('GithubOrgEntityProvider', () => { user: { name: 'githubuser', login: 'githubuser', + node_id: 'githubuserId', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', }, @@ -778,6 +784,7 @@ describe('GithubOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -785,6 +792,7 @@ describe('GithubOrgEntityProvider', () => { }, { login: 'githubuser', + id: 'githubuserId', name: 'githubuser', bio: 'githubuser', email: 'd', @@ -865,6 +873,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', description: 'c', @@ -891,6 +900,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', @@ -1029,6 +1039,7 @@ describe('GithubOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -1036,6 +1047,7 @@ describe('GithubOrgEntityProvider', () => { }, { login: 'githubuser', + id: 'githubuserId', name: 'githubuser', bio: 'githubuser', email: 'd', @@ -1112,6 +1124,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', @@ -1168,6 +1181,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', @@ -1277,6 +1291,7 @@ describe('GithubOrgEntityProvider', () => { nodes: [ { login: 'githubuser', + id: 'githubuserId', name: 'githubuser', bio: 'githubuser', email: 'd', @@ -1336,6 +1351,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', @@ -1392,6 +1408,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index 35ec849e12..bb4b268e62 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -587,7 +587,13 @@ export class GithubOrgEntityProvider implements EntityProvider { const userTransformer = this.options.userTransformer || defaultUserTransformer; - const { name, avatar_url: avatarUrl, email, login } = event.membership.user; + const { + name, + avatar_url: avatarUrl, + email, + login, + node_id, + } = event.membership.user; const org = event.organization.login; const { headers } = await this.credentialsProvider.getCredentials({ url: this.options.orgUrl, @@ -603,6 +609,7 @@ export class GithubOrgEntityProvider implements EntityProvider { avatarUrl, login, email: email || undefined, + id: node_id, // we don't have this information in the event, so the refresh will handle that for us organizationVerifiedDomainEmails: [], },