From cd5932bc667d7b770093999d118a72b0ca3e5528 Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Mon, 19 Jul 2021 16:35:00 +0200 Subject: [PATCH 01/13] feat: setup gitlab auth provider factory * add username to ProfileInfo * expose gitlab provider * add signIn handler * refactor resultHandler usage Signed-off-by: Tom Opdebeeck --- .../lib/passport/PassportStrategyHelper.ts | 7 +- .../src/providers/gitlab/provider.ts | 178 ++++++++++++++---- plugins/auth-backend/src/providers/index.ts | 1 + plugins/auth-backend/src/providers/types.ts | 4 + 4 files changed, 156 insertions(+), 34 deletions(-) diff --git a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts index 313b4a79bd..e6ef47c2c0 100644 --- a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts +++ b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts @@ -30,7 +30,7 @@ export const makeProfileInfo = ( profile: passport.Profile, idToken?: string, ): ProfileInfo => { - let { displayName } = profile; + let { displayName, username } = profile; let email: string | undefined = undefined; if (profile.emails && profile.emails.length > 0) { @@ -56,6 +56,10 @@ export const makeProfileInfo = ( if (!displayName && decoded.name) { displayName = decoded.name; } + + if (!username && decoded.username) { + username = decoded.username; + } } catch (e) { throw new Error(`Failed to parse id token and get profile info, ${e}`); } @@ -65,6 +69,7 @@ export const makeProfileInfo = ( email, picture, displayName, + username, }; }; diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 606d2c76a6..0fa99252b7 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -16,6 +16,8 @@ import express from 'express'; import { Strategy as GitlabStrategy } from 'passport-gitlab2'; +import { Logger } from 'winston'; + import { executeRedirectStrategy, executeFrameHandlerStrategy, @@ -24,7 +26,12 @@ import { makeProfileInfo, PassportDoneCallback, } from '../../lib/passport'; -import { RedirectInfo, AuthProviderFactory } from '../types'; +import { + RedirectInfo, + AuthProviderFactory, + SignInResolver, + AuthHandler, +} from '../types'; import { OAuthAdapter, OAuthProviderOptions, @@ -36,6 +43,8 @@ import { encodeState, OAuthResult, } from '../../lib/oauth'; +import { TokenIssuer } from '../../identity'; +import { CatalogIdentityClient } from '../../lib/catalog'; type FullProfile = OAuthResult['fullProfile'] & { avatarUrl?: string; @@ -47,29 +56,72 @@ type PrivateInfo = { export type GitlabAuthProviderOptions = OAuthProviderOptions & { baseUrl: string; + signInResolver?: SignInResolver; + authHandler: AuthHandler; + tokenIssuer: TokenIssuer; + catalogIdentityClient: CatalogIdentityClient; + logger: Logger; }; -function transformProfile(fullProfile: FullProfile) { +function transformResult(result: OAuthResult): OAuthResult { + const { fullProfile, ...authResult } = result; + const profile = makeProfileInfo({ ...fullProfile, photos: [ ...(fullProfile.photos ?? []), - ...(fullProfile.avatarUrl ? [{ value: fullProfile.avatarUrl }] : []), + ...((fullProfile as FullProfile).avatarUrl + ? [{ value: (fullProfile as FullProfile).avatarUrl as string }] + : []), ], }); let id = fullProfile.id; + if (profile.email) { id = profile.email.split('@')[0]; } - return { id, profile }; + return { + ...authResult, + fullProfile, + }; } +export const gitlabDefaultSignInResolver: SignInResolver = async ( + info, + ctx, +) => { + const { profile } = info; + + if (!profile.email) { + throw new Error('Profile contained no email'); + } + + const userId = profile.email.split('@')[0]; + + const token = await ctx.tokenIssuer.issueToken({ + claims: { sub: userId, ent: [`user:default/${userId}`] }, + }); + + return { id: userId, token }; +}; + export class GitlabAuthProvider implements OAuthHandlers { private readonly _strategy: GitlabStrategy; + private readonly signInResolver?: SignInResolver; + private readonly authHandler: AuthHandler; + private readonly tokenIssuer: TokenIssuer; + private readonly catalogIdentityClient: CatalogIdentityClient; + private readonly logger: Logger; constructor(options: GitlabAuthProviderOptions) { + this.signInResolver = options.signInResolver; + this.authHandler = options.authHandler; + this.tokenIssuer = options.tokenIssuer; + this.logger = options.logger; + this.catalogIdentityClient = options.catalogIdentityClient; + this._strategy = new GitlabStrategy( { clientID: options.clientId, @@ -109,23 +161,9 @@ export class GitlabAuthProvider implements OAuthHandlers { OAuthResult, PrivateInfo >(req, this._strategy); - const { accessToken, params } = result; - - const { id, profile } = transformProfile(result.fullProfile); return { - response: { - profile, - providerInfo: { - accessToken, - scope: params.scope, - expiresInSeconds: params.expires_in, - idToken: params.id_token, - }, - backstageIdentity: { - id, - }, - }, + response: await this.handleResult(result), refreshToken: privateInfo.refreshToken, }; } @@ -145,30 +183,78 @@ export class GitlabAuthProvider implements OAuthHandlers { this._strategy, accessToken, ); - const { id, profile } = transformProfile(fullProfile); - return { - profile, + return this.handleResult({ + fullProfile, + params, + accessToken, + refreshToken: newRefreshToken, + }); + } + + private async handleResult(result: OAuthResult): Promise { + const { profile } = await this.authHandler(transformResult(result)); + + const response: OAuthResponse = { providerInfo: { - accessToken, - refreshToken: newRefreshToken, // GitLab expires the old refresh token when used - idToken: params.id_token, - expiresInSeconds: params.expires_in, - scope: params.scope, - }, - backstageIdentity: { - id, + idToken: result.params.id_token, + accessToken: result.accessToken, + scope: result.params.scope, + expiresInSeconds: result.params.expires_in, }, + profile, }; + + if (this.signInResolver) { + response.backstageIdentity = await this.signInResolver( + { + result, + profile, + }, + { + tokenIssuer: this.tokenIssuer, + catalogIdentityClient: this.catalogIdentityClient, + logger: this.logger, + }, + ); + } + + return response; } } -export type GitlabProviderOptions = {}; +export type GitlabProviderOptions = { + /** + * The profile transformation function used to verify and convert the auth response + * into the profile that will be presented to the user. + */ + authHandler?: AuthHandler; + + /** + * Configure sign-in for this provider, without it the provider can not be used to sign users in. + */ + /** + * Maps an auth result to a Backstage identity for the user. + * + * Set to `'email'` to use the default email-based sign in resolver, which will search + * the catalog for a single user entity that has a matching `microsoft.com/email` annotation. + */ + signIn?: { + resolver?: SignInResolver; + }; +}; export const createGitlabProvider = ( - _options?: GitlabProviderOptions, + options?: GitlabProviderOptions, ): AuthProviderFactory => { - return ({ providerId, globalConfig, config, tokenIssuer }) => + return ({ + providerId, + globalConfig, + config, + tokenIssuer, + catalogApi, + logger, + }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); @@ -176,11 +262,37 @@ export const createGitlabProvider = ( const baseUrl = audience || 'https://gitlab.com'; const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const catalogIdentityClient = new CatalogIdentityClient({ + catalogApi, + tokenIssuer, + }); + + const authHandler: AuthHandler = options?.authHandler + ? options.authHandler + : async ({ fullProfile, params }) => ({ + profile: makeProfileInfo(fullProfile, params.id_token), + }); + + const signInResolverFn = + options?.signIn?.resolver ?? gitlabDefaultSignInResolver; + + const signInResolver: SignInResolver = info => + signInResolverFn(info, { + catalogIdentityClient, + tokenIssuer, + logger, + }); + const provider = new GitlabAuthProvider({ clientId, clientSecret, callbackUrl, baseUrl, + authHandler, + signInResolver, + catalogIdentityClient, + logger, + tokenIssuer, }); return OAuthAdapter.fromConfig(globalConfig, provider, { diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 6b262dcf68..bf5615a72e 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export * from './gitlab'; export * from './google'; export * from './microsoft'; export { factories as defaultAuthProviderFactories } from './factories'; diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index c838005c00..a8b087156d 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -196,6 +196,10 @@ export type ProfileInfo = { * signed in user. */ picture?: string; + /** + * Username of the signed in user. + */ + username?: string; }; export type SignInInfo = { From aae88bc3fa1b000a254326ea3f475f43da012307 Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Mon, 19 Jul 2021 16:45:52 +0200 Subject: [PATCH 02/13] fix: cleanup username/email check Signed-off-by: Tom Opdebeeck --- .../src/providers/gitlab/provider.ts | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 0fa99252b7..25440dab75 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -31,6 +31,7 @@ import { AuthProviderFactory, SignInResolver, AuthHandler, + ProfileInfo, } from '../types'; import { OAuthAdapter, @@ -63,25 +64,28 @@ export type GitlabAuthProviderOptions = OAuthProviderOptions & { logger: Logger; }; +const extractUserId = (profile: ProfileInfo): string => { + return profile.username || (profile.email?.split('@')[0] as string); +}; + function transformResult(result: OAuthResult): OAuthResult { const { fullProfile, ...authResult } = result; - const profile = makeProfileInfo({ - ...fullProfile, - photos: [ - ...(fullProfile.photos ?? []), - ...((fullProfile as FullProfile).avatarUrl - ? [{ value: (fullProfile as FullProfile).avatarUrl as string }] - : []), - ], - }); + fullProfile.photos = [ + ...(fullProfile.photos ?? []), + ...((fullProfile as FullProfile).avatarUrl + ? [{ value: (fullProfile as FullProfile).avatarUrl as string }] + : []), + ]; - let id = fullProfile.id; + const profile = makeProfileInfo(fullProfile); - if (profile.email) { - id = profile.email.split('@')[0]; + if (!profile.username && !profile.email) { + throw new Error('Profile contained no username or email'); } + fullProfile.id = extractUserId(profile); + return { ...authResult, fullProfile, @@ -94,17 +98,17 @@ export const gitlabDefaultSignInResolver: SignInResolver = async ( ) => { const { profile } = info; - if (!profile.email) { - throw new Error('Profile contained no email'); + if (!profile.username && !profile.email) { + throw new Error('Profile contained no username or email'); } - const userId = profile.email.split('@')[0]; + const id = extractUserId(profile); const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: userId, ent: [`user:default/${userId}`] }, + claims: { sub: id, ent: [`user:default/${id}`] }, }); - return { id: userId, token }; + return { id, token }; }; export class GitlabAuthProvider implements OAuthHandlers { From 9b55010a460ca812aed44dae678458030a1a50ab Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Mon, 19 Jul 2021 17:16:17 +0200 Subject: [PATCH 03/13] fix: cleanup provider methods Signed-off-by: Tom Opdebeeck --- .../src/providers/gitlab/provider.ts | 108 ++++++++++-------- 1 file changed, 58 insertions(+), 50 deletions(-) diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 25440dab75..d229165562 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -58,40 +58,16 @@ type PrivateInfo = { export type GitlabAuthProviderOptions = OAuthProviderOptions & { baseUrl: string; signInResolver?: SignInResolver; - authHandler: AuthHandler; + authHandler?: AuthHandler; tokenIssuer: TokenIssuer; catalogIdentityClient: CatalogIdentityClient; logger: Logger; }; -const extractUserId = (profile: ProfileInfo): string => { +export const extractGitLabUserId = (profile: ProfileInfo): string => { return profile.username || (profile.email?.split('@')[0] as string); }; -function transformResult(result: OAuthResult): OAuthResult { - const { fullProfile, ...authResult } = result; - - fullProfile.photos = [ - ...(fullProfile.photos ?? []), - ...((fullProfile as FullProfile).avatarUrl - ? [{ value: (fullProfile as FullProfile).avatarUrl as string }] - : []), - ]; - - const profile = makeProfileInfo(fullProfile); - - if (!profile.username && !profile.email) { - throw new Error('Profile contained no username or email'); - } - - fullProfile.id = extractUserId(profile); - - return { - ...authResult, - fullProfile, - }; -} - export const gitlabDefaultSignInResolver: SignInResolver = async ( info, ctx, @@ -102,7 +78,7 @@ export const gitlabDefaultSignInResolver: SignInResolver = async ( throw new Error('Profile contained no username or email'); } - const id = extractUserId(profile); + const id = extractGitLabUserId(profile); const token = await ctx.tokenIssuer.issueToken({ claims: { sub: id, ent: [`user:default/${id}`] }, @@ -111,6 +87,13 @@ export const gitlabDefaultSignInResolver: SignInResolver = async ( return { id, token }; }; +export const gitlabDefaultAuthHandler: AuthHandler = async ({ + fullProfile, + params, +}) => ({ + profile: makeProfileInfo(fullProfile, params.id_token), +}); + export class GitlabAuthProvider implements OAuthHandlers { private readonly _strategy: GitlabStrategy; private readonly signInResolver?: SignInResolver; @@ -120,11 +103,11 @@ export class GitlabAuthProvider implements OAuthHandlers { private readonly logger: Logger; constructor(options: GitlabAuthProviderOptions) { - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - this.tokenIssuer = options.tokenIssuer; - this.logger = options.logger; this.catalogIdentityClient = options.catalogIdentityClient; + this.logger = options.logger; + this.tokenIssuer = options.tokenIssuer; + this.authHandler = options.authHandler || gitlabDefaultAuthHandler; + this.signInResolver = this.createSignInResolverFn(options); this._strategy = new GitlabStrategy( { @@ -197,7 +180,8 @@ export class GitlabAuthProvider implements OAuthHandlers { } private async handleResult(result: OAuthResult): Promise { - const { profile } = await this.authHandler(transformResult(result)); + const transformedResult = this.transformResult(result); + const { profile } = await this.authHandler(transformedResult); const response: OAuthResponse = { providerInfo: { @@ -225,6 +209,46 @@ export class GitlabAuthProvider implements OAuthHandlers { return response; } + + private createSignInResolverFn({ + signInResolver, + catalogIdentityClient, + tokenIssuer, + logger, + }: GitlabAuthProviderOptions): SignInResolver { + const resolver = signInResolver || gitlabDefaultSignInResolver; + + return info => + resolver(info, { + catalogIdentityClient, + tokenIssuer, + logger, + }); + } + + private transformResult(result: OAuthResult): OAuthResult { + const { fullProfile, ...authResult } = result; + + fullProfile.photos = [ + ...(fullProfile.photos ?? []), + ...((fullProfile as FullProfile).avatarUrl + ? [{ value: (fullProfile as FullProfile).avatarUrl as string }] + : []), + ]; + + const profile = makeProfileInfo(fullProfile); + + if (!profile.username && !profile.email) { + throw new Error('Profile contained no username or email'); + } + + fullProfile.id = extractGitLabUserId(profile); + + return { + ...authResult, + fullProfile, + }; + } } export type GitlabProviderOptions = { @@ -271,29 +295,13 @@ export const createGitlabProvider = ( tokenIssuer, }); - const authHandler: AuthHandler = options?.authHandler - ? options.authHandler - : async ({ fullProfile, params }) => ({ - profile: makeProfileInfo(fullProfile, params.id_token), - }); - - const signInResolverFn = - options?.signIn?.resolver ?? gitlabDefaultSignInResolver; - - const signInResolver: SignInResolver = info => - signInResolverFn(info, { - catalogIdentityClient, - tokenIssuer, - logger, - }); - const provider = new GitlabAuthProvider({ clientId, clientSecret, callbackUrl, baseUrl, - authHandler, - signInResolver, + authHandler: options?.authHandler, + signInResolver: options?.signIn?.resolver, catalogIdentityClient, logger, tokenIssuer, From db469242bec05d59cd4206a3255fa0ddcd719351 Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Mon, 19 Jul 2021 17:16:26 +0200 Subject: [PATCH 04/13] fix: tests Signed-off-by: Tom Opdebeeck --- .../src/providers/github/provider.test.ts | 4 ++++ .../src/providers/gitlab/provider.test.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/plugins/auth-backend/src/providers/github/provider.test.ts b/plugins/auth-backend/src/providers/github/provider.test.ts index 4e53ce4026..5faa508f88 100644 --- a/plugins/auth-backend/src/providers/github/provider.test.ts +++ b/plugins/auth-backend/src/providers/github/provider.test.ts @@ -72,6 +72,7 @@ describe('GithubAuthProvider', () => { }, profile: { email: 'jimmymarkum@gmail.com', + username: 'jimmymarkum', displayName: 'Jimmy Markum', picture: 'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg', @@ -117,6 +118,7 @@ describe('GithubAuthProvider', () => { }, profile: { displayName: 'Jimmy Markum', + username: 'jimmymarkum', picture: 'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg', }, @@ -160,6 +162,7 @@ describe('GithubAuthProvider', () => { }, profile: { displayName: 'jimmymarkum', + username: 'jimmymarkum', picture: 'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg', }, @@ -205,6 +208,7 @@ describe('GithubAuthProvider', () => { profile: { displayName: 'Dave Boyle', email: 'daveboyle@gitlab.org', + username: 'daveboyle', }, }; diff --git a/plugins/auth-backend/src/providers/gitlab/provider.test.ts b/plugins/auth-backend/src/providers/gitlab/provider.test.ts index 2277515cc9..814a060e90 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.test.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.test.ts @@ -17,6 +17,9 @@ import { GitlabAuthProvider } from './provider'; import * as helpers from '../../lib/passport/PassportStrategyHelper'; import { OAuthResult } from '../../lib/oauth'; +import { getVoidLogger } from '../../../../../packages/backend-common/src'; +import { TokenIssuer } from '../../identity'; +import { CatalogIdentityClient } from '../../lib/catalog'; const mockFrameHandler = (jest.spyOn( helpers, @@ -64,6 +67,7 @@ describe('GitlabAuthProvider', () => { profile: { email: 'jimmymarkum@gmail.com', displayName: 'Jimmy Markum', + username: 'jimmymarkum', picture: 'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg', }, @@ -107,16 +111,28 @@ describe('GitlabAuthProvider', () => { profile: { displayName: 'Dave Boyle', email: 'daveboyle@gitlab.org', + username: 'daveboyle', }, }, }, ]; + const tokenIssuer = { + issueToken: jest.fn(), + listPublicKeys: jest.fn(), + }; + const catalogIdentityClient = { + findUser: jest.fn(), + }; + const provider = new GitlabAuthProvider({ clientId: 'mock', clientSecret: 'mock', callbackUrl: 'mock', baseUrl: 'mock', + catalogIdentityClient: (catalogIdentityClient as unknown) as CatalogIdentityClient, + tokenIssuer: (tokenIssuer as unknown) as TokenIssuer, + logger: getVoidLogger(), }); for (const test of tests) { mockFrameHandler.mockResolvedValueOnce(test.input); From 39fc3d7f807f790e178e8d5ed4383944efd73da5 Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Mon, 19 Jul 2021 17:23:30 +0200 Subject: [PATCH 05/13] chore: add changeset Signed-off-by: Tom Opdebeeck --- .changeset/strong-swans-wave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-swans-wave.md diff --git a/.changeset/strong-swans-wave.md b/.changeset/strong-swans-wave.md new file mode 100644 index 0000000000..c24aceb8df --- /dev/null +++ b/.changeset/strong-swans-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Add Sign In and Handler resolver for GitLab provider From fcc4e3c428aa70cdf467a653a07eb84897534cdb Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Fri, 13 Aug 2021 11:26:04 +0200 Subject: [PATCH 06/13] fix(auth-backend): revert username added on profie Signed-off-by: Tom Opdebeeck --- .../src/lib/passport/PassportStrategyHelper.ts | 7 +------ .../auth-backend/src/providers/github/provider.test.ts | 4 ---- plugins/auth-backend/src/providers/github/provider.ts | 4 ++-- plugins/auth-backend/src/providers/gitlab/provider.ts | 10 +++++----- plugins/auth-backend/src/providers/types.ts | 4 ---- 5 files changed, 8 insertions(+), 21 deletions(-) diff --git a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts index e6ef47c2c0..313b4a79bd 100644 --- a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts +++ b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts @@ -30,7 +30,7 @@ export const makeProfileInfo = ( profile: passport.Profile, idToken?: string, ): ProfileInfo => { - let { displayName, username } = profile; + let { displayName } = profile; let email: string | undefined = undefined; if (profile.emails && profile.emails.length > 0) { @@ -56,10 +56,6 @@ export const makeProfileInfo = ( if (!displayName && decoded.name) { displayName = decoded.name; } - - if (!username && decoded.username) { - username = decoded.username; - } } catch (e) { throw new Error(`Failed to parse id token and get profile info, ${e}`); } @@ -69,7 +65,6 @@ export const makeProfileInfo = ( email, picture, displayName, - username, }; }; diff --git a/plugins/auth-backend/src/providers/github/provider.test.ts b/plugins/auth-backend/src/providers/github/provider.test.ts index 5faa508f88..4e53ce4026 100644 --- a/plugins/auth-backend/src/providers/github/provider.test.ts +++ b/plugins/auth-backend/src/providers/github/provider.test.ts @@ -72,7 +72,6 @@ describe('GithubAuthProvider', () => { }, profile: { email: 'jimmymarkum@gmail.com', - username: 'jimmymarkum', displayName: 'Jimmy Markum', picture: 'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg', @@ -118,7 +117,6 @@ describe('GithubAuthProvider', () => { }, profile: { displayName: 'Jimmy Markum', - username: 'jimmymarkum', picture: 'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg', }, @@ -162,7 +160,6 @@ describe('GithubAuthProvider', () => { }, profile: { displayName: 'jimmymarkum', - username: 'jimmymarkum', picture: 'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg', }, @@ -208,7 +205,6 @@ describe('GithubAuthProvider', () => { profile: { displayName: 'Dave Boyle', email: 'daveboyle@gitlab.org', - username: 'daveboyle', }, }; diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index 62f2534601..a550cd1fdc 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -79,7 +79,7 @@ export class GithubAuthProvider implements OAuthHandlers { const profile = makeProfileInfo( { ...fullProfile, - id: fullProfile.username || fullProfile.id, + id: fullProfile.id, displayName: fullProfile.displayName || fullProfile.username || fullProfile.id, }, @@ -95,7 +95,7 @@ export class GithubAuthProvider implements OAuthHandlers { expiresInSeconds: params.expires_in, }, backstageIdentity: { - id: fullProfile.username || fullProfile.id, + id: fullProfile.id, }, }, }; diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index d229165562..63d1260983 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -65,7 +65,7 @@ export type GitlabAuthProviderOptions = OAuthProviderOptions & { }; export const extractGitLabUserId = (profile: ProfileInfo): string => { - return profile.username || (profile.email?.split('@')[0] as string); + return profile.email?.split('@')[0] as string; }; export const gitlabDefaultSignInResolver: SignInResolver = async ( @@ -74,8 +74,8 @@ export const gitlabDefaultSignInResolver: SignInResolver = async ( ) => { const { profile } = info; - if (!profile.username && !profile.email) { - throw new Error('Profile contained no username or email'); + if (!profile.email) { + throw new Error('Profile contained no email'); } const id = extractGitLabUserId(profile); @@ -238,8 +238,8 @@ export class GitlabAuthProvider implements OAuthHandlers { const profile = makeProfileInfo(fullProfile); - if (!profile.username && !profile.email) { - throw new Error('Profile contained no username or email'); + if (!profile.email) { + throw new Error('Profile contained no email'); } fullProfile.id = extractGitLabUserId(profile); diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index a8b087156d..c838005c00 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -196,10 +196,6 @@ export type ProfileInfo = { * signed in user. */ picture?: string; - /** - * Username of the signed in user. - */ - username?: string; }; export type SignInInfo = { From ec8930cb5be1013e9f3040137616a8ae8a8e6c30 Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Fri, 13 Aug 2021 11:26:39 +0200 Subject: [PATCH 07/13] fix(auth-backend): update authhandler & signinresolver Signed-off-by: Tom Opdebeeck --- .../src/providers/gitlab/provider.test.ts | 7 ++++ .../src/providers/gitlab/provider.ts | 39 +++++++++---------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/plugins/auth-backend/src/providers/gitlab/provider.test.ts b/plugins/auth-backend/src/providers/gitlab/provider.test.ts index 814a060e90..e4112e1fff 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.test.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.test.ts @@ -132,6 +132,13 @@ describe('GitlabAuthProvider', () => { baseUrl: 'mock', catalogIdentityClient: (catalogIdentityClient as unknown) as CatalogIdentityClient, tokenIssuer: (tokenIssuer as unknown) as TokenIssuer, + authHandler: async ({ fullProfile }) => ({ + profile: { + email: fullProfile.emails![0]!.value, + displayName: fullProfile.displayName, + picture: 'http://gitlab.com/lols', + }, + }), logger: getVoidLogger(), }); for (const test of tests) { diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 63d1260983..b07b8d72cf 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -58,7 +58,7 @@ type PrivateInfo = { export type GitlabAuthProviderOptions = OAuthProviderOptions & { baseUrl: string; signInResolver?: SignInResolver; - authHandler?: AuthHandler; + authHandler: AuthHandler; tokenIssuer: TokenIssuer; catalogIdentityClient: CatalogIdentityClient; logger: Logger; @@ -106,8 +106,8 @@ export class GitlabAuthProvider implements OAuthHandlers { this.catalogIdentityClient = options.catalogIdentityClient; this.logger = options.logger; this.tokenIssuer = options.tokenIssuer; - this.authHandler = options.authHandler || gitlabDefaultAuthHandler; - this.signInResolver = this.createSignInResolverFn(options); + this.authHandler = options.authHandler; + this.signInResolver = options.signInResolver; this._strategy = new GitlabStrategy( { @@ -210,22 +210,6 @@ export class GitlabAuthProvider implements OAuthHandlers { return response; } - private createSignInResolverFn({ - signInResolver, - catalogIdentityClient, - tokenIssuer, - logger, - }: GitlabAuthProviderOptions): SignInResolver { - const resolver = signInResolver || gitlabDefaultSignInResolver; - - return info => - resolver(info, { - catalogIdentityClient, - tokenIssuer, - logger, - }); - } - private transformResult(result: OAuthResult): OAuthResult { const { fullProfile, ...authResult } = result; @@ -295,13 +279,26 @@ export const createGitlabProvider = ( tokenIssuer, }); + const authHandler: AuthHandler = + options?.authHandler ?? gitlabDefaultAuthHandler; + + const signInResolverFn = + options?.signIn?.resolver ?? gitlabDefaultSignInResolver; + + const signInResolver: SignInResolver = info => + signInResolverFn(info, { + catalogIdentityClient, + tokenIssuer, + logger, + }); + const provider = new GitlabAuthProvider({ clientId, clientSecret, callbackUrl, baseUrl, - authHandler: options?.authHandler, - signInResolver: options?.signIn?.resolver, + authHandler, + signInResolver, catalogIdentityClient, logger, tokenIssuer, From c097e8893beb95f4837d6974a8da099b30c7e43b Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Fri, 13 Aug 2021 11:35:30 +0200 Subject: [PATCH 08/13] fix: revert changes in github provider Signed-off-by: Tom Opdebeeck --- plugins/auth-backend/src/providers/github/provider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index a550cd1fdc..62f2534601 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -79,7 +79,7 @@ export class GithubAuthProvider implements OAuthHandlers { const profile = makeProfileInfo( { ...fullProfile, - id: fullProfile.id, + id: fullProfile.username || fullProfile.id, displayName: fullProfile.displayName || fullProfile.username || fullProfile.id, }, @@ -95,7 +95,7 @@ export class GithubAuthProvider implements OAuthHandlers { expiresInSeconds: params.expires_in, }, backstageIdentity: { - id: fullProfile.id, + id: fullProfile.username || fullProfile.id, }, }, }; From 278f1fe23e337c5a68d94290f13262c5873a7ebf Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Fri, 13 Aug 2021 11:53:05 +0200 Subject: [PATCH 09/13] fix(auth-backend): cleanup profile transform * make more consistent with github flow Signed-off-by: Tom Opdebeeck --- .../src/providers/gitlab/provider.ts | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index b07b8d72cf..b4715aefb6 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -72,13 +72,9 @@ export const gitlabDefaultSignInResolver: SignInResolver = async ( info, ctx, ) => { - const { profile } = info; + const { result } = info; - if (!profile.email) { - throw new Error('Profile contained no email'); - } - - const id = extractGitLabUserId(profile); + const id = result.fullProfile.username || result.fullProfile.id; const token = await ctx.tokenIssuer.issueToken({ claims: { sub: id, ent: [`user:default/${id}`] }, @@ -90,9 +86,18 @@ export const gitlabDefaultSignInResolver: SignInResolver = async ( export const gitlabDefaultAuthHandler: AuthHandler = async ({ fullProfile, params, -}) => ({ - profile: makeProfileInfo(fullProfile, params.id_token), -}); +}) => { + fullProfile.photos = [ + ...(fullProfile.photos ?? []), + ...((fullProfile as FullProfile).avatarUrl + ? [{ value: (fullProfile as FullProfile).avatarUrl as string }] + : []), + ]; + + return { + profile: makeProfileInfo(fullProfile, params.id_token), + }; +}; export class GitlabAuthProvider implements OAuthHandlers { private readonly _strategy: GitlabStrategy; @@ -180,8 +185,7 @@ export class GitlabAuthProvider implements OAuthHandlers { } private async handleResult(result: OAuthResult): Promise { - const transformedResult = this.transformResult(result); - const { profile } = await this.authHandler(transformedResult); + const { profile } = await this.authHandler(result); const response: OAuthResponse = { providerInfo: { @@ -209,30 +213,6 @@ export class GitlabAuthProvider implements OAuthHandlers { return response; } - - private transformResult(result: OAuthResult): OAuthResult { - const { fullProfile, ...authResult } = result; - - fullProfile.photos = [ - ...(fullProfile.photos ?? []), - ...((fullProfile as FullProfile).avatarUrl - ? [{ value: (fullProfile as FullProfile).avatarUrl as string }] - : []), - ]; - - const profile = makeProfileInfo(fullProfile); - - if (!profile.email) { - throw new Error('Profile contained no email'); - } - - fullProfile.id = extractGitLabUserId(profile); - - return { - ...authResult, - fullProfile, - }; - } } export type GitlabProviderOptions = { From 7ddb49a05c83af17d028209ddded7b00c7278cf6 Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Mon, 16 Aug 2021 12:17:27 +0200 Subject: [PATCH 10/13] fix(auth-backend): gitlab provider tests Signed-off-by: Tom Opdebeeck --- .../src/providers/gitlab/provider.test.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plugins/auth-backend/src/providers/gitlab/provider.test.ts b/plugins/auth-backend/src/providers/gitlab/provider.test.ts index 7c35ab780c..bb3297b043 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.test.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { GitlabAuthProvider } from './provider'; +import { GitlabAuthProvider, gitlabDefaultSignInResolver } from './provider'; import * as helpers from '../../lib/passport/PassportStrategyHelper'; import { OAuthResult } from '../../lib/oauth'; import { getVoidLogger } from '../../../../../packages/backend-common/src'; @@ -63,13 +63,12 @@ describe('GitlabAuthProvider', () => { accessToken: '19xasczxcm9n7gacn9jdgm19me', expiresInSeconds: 100, scope: 'user_read write_repository', + idToken: undefined, }, profile: { email: 'jimmymarkum@gmail.com', displayName: 'Jimmy Markum', - username: 'jimmymarkum', - picture: - 'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg', + picture: 'http://gitlab.com/lols', }, }, }, @@ -106,12 +105,13 @@ describe('GitlabAuthProvider', () => { accessToken: 'ajakljsdoiahoawxbrouawucmbawe.awkxjemaneasdxwe.sodijxqeqwexeqwxe', expiresInSeconds: 200, + idToken: undefined, scope: 'read_repository', }, profile: { displayName: 'Dave Boyle', email: 'daveboyle@gitlab.org', - username: 'daveboyle', + picture: 'http://gitlab.com/lols', }, }, }, @@ -130,8 +130,9 @@ describe('GitlabAuthProvider', () => { clientSecret: 'mock', callbackUrl: 'mock', baseUrl: 'mock', - catalogIdentityClient: (catalogIdentityClient as unknown) as CatalogIdentityClient, - tokenIssuer: (tokenIssuer as unknown) as TokenIssuer, + catalogIdentityClient: + catalogIdentityClient as unknown as CatalogIdentityClient, + tokenIssuer: tokenIssuer as unknown as TokenIssuer, authHandler: async ({ fullProfile }) => ({ profile: { email: fullProfile.emails![0]!.value, @@ -139,6 +140,7 @@ describe('GitlabAuthProvider', () => { picture: 'http://gitlab.com/lols', }, }), + signInResolver: gitlabDefaultSignInResolver, logger: getVoidLogger(), }); for (const test of tests) { From 4804c7257adc4f0d8eeb050d782a97365e202c4c Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Tue, 17 Aug 2021 14:43:45 +0200 Subject: [PATCH 11/13] fix(auth-backend): cleanup * remove unused method "extractGitlabUserId" * consistent profile id * move avatarUrl check to makeProfileInfo (extend passport type) Signed-off-by: Tom Opdebeeck --- .../lib/passport/PassportStrategyHelper.ts | 14 +++++--- .../auth-backend/src/lib/passport/types.ts | 20 ++++++++++++ .../src/providers/gitlab/provider.ts | 32 ++++++------------- 3 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 plugins/auth-backend/src/lib/passport/types.ts diff --git a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts index a0c07943c1..fe11edf376 100644 --- a/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts +++ b/plugins/auth-backend/src/lib/passport/PassportStrategyHelper.ts @@ -17,9 +17,11 @@ import express from 'express'; import passport from 'passport'; import jwtDecoder from 'jwt-decode'; -import { ProfileInfo, RedirectInfo } from '../../providers/types'; import { InternalOAuthError } from 'passport-oauth2'; +import { PassportProfile } from './types'; +import { ProfileInfo, RedirectInfo } from '../../providers/types'; + export type PassportDoneCallback = ( err?: Error, response?: Res, @@ -27,7 +29,7 @@ export type PassportDoneCallback = ( ) => void; export const makeProfileInfo = ( - profile: passport.Profile, + profile: PassportProfile, idToken?: string, ): ProfileInfo => { let { displayName } = profile; @@ -39,7 +41,9 @@ export const makeProfileInfo = ( } let picture: string | undefined = undefined; - if (profile.photos && profile.photos.length > 0) { + if (profile.avatarUrl) { + picture = profile.avatarUrl; + } else if (profile.photos && profile.photos.length > 0) { const [firstPhoto] = profile.photos; picture = firstPhoto.value; } @@ -193,12 +197,12 @@ type ProviderStrategy = { export const executeFetchUserProfileStrategy = async ( providerStrategy: passport.Strategy, accessToken: string, -): Promise => { +): Promise => { return new Promise((resolve, reject) => { const anyStrategy = providerStrategy as unknown as ProviderStrategy; anyStrategy.userProfile( accessToken, - (error: Error, rawProfile: passport.Profile) => { + (error: Error, rawProfile: PassportProfile) => { if (error) { reject(error); } else { diff --git a/plugins/auth-backend/src/lib/passport/types.ts b/plugins/auth-backend/src/lib/passport/types.ts new file mode 100644 index 0000000000..55fe4543fc --- /dev/null +++ b/plugins/auth-backend/src/lib/passport/types.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 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 passport from 'passport'; + +export type PassportProfile = passport.Profile & { + avatarUrl?: string; +}; diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index b4715aefb6..419f00a346 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -31,7 +31,6 @@ import { AuthProviderFactory, SignInResolver, AuthHandler, - ProfileInfo, } from '../types'; import { OAuthAdapter, @@ -47,10 +46,6 @@ import { import { TokenIssuer } from '../../identity'; import { CatalogIdentityClient } from '../../lib/catalog'; -type FullProfile = OAuthResult['fullProfile'] & { - avatarUrl?: string; -}; - type PrivateInfo = { refreshToken: string; }; @@ -64,17 +59,17 @@ export type GitlabAuthProviderOptions = OAuthProviderOptions & { logger: Logger; }; -export const extractGitLabUserId = (profile: ProfileInfo): string => { - return profile.email?.split('@')[0] as string; -}; - export const gitlabDefaultSignInResolver: SignInResolver = async ( info, ctx, ) => { - const { result } = info; + const { profile, result } = info; - const id = result.fullProfile.username || result.fullProfile.id; + let id = result.fullProfile.id; + + if (profile.email) { + id = profile.email.split('@')[0]; + } const token = await ctx.tokenIssuer.issueToken({ claims: { sub: id, ent: [`user:default/${id}`] }, @@ -86,18 +81,9 @@ export const gitlabDefaultSignInResolver: SignInResolver = async ( export const gitlabDefaultAuthHandler: AuthHandler = async ({ fullProfile, params, -}) => { - fullProfile.photos = [ - ...(fullProfile.photos ?? []), - ...((fullProfile as FullProfile).avatarUrl - ? [{ value: (fullProfile as FullProfile).avatarUrl as string }] - : []), - ]; - - return { - profile: makeProfileInfo(fullProfile, params.id_token), - }; -}; +}) => ({ + profile: makeProfileInfo(fullProfile, params.id_token), +}); export class GitlabAuthProvider implements OAuthHandlers { private readonly _strategy: GitlabStrategy; From c72c73f1adc2205124c2320797c482516c26779a Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Thu, 19 Aug 2021 10:46:32 +0200 Subject: [PATCH 12/13] fix: minor -> patch Signed-off-by: Tom Opdebeeck --- .changeset/strong-swans-wave.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/strong-swans-wave.md b/.changeset/strong-swans-wave.md index c24aceb8df..2a7f409022 100644 --- a/.changeset/strong-swans-wave.md +++ b/.changeset/strong-swans-wave.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-auth-backend': minor +'@backstage/plugin-auth-backend': patch --- Add Sign In and Handler resolver for GitLab provider From 5ea93f7246e9c33879315246b19e2749d8003c3b Mon Sep 17 00:00:00 2001 From: Tom Opdebeeck Date: Fri, 20 Aug 2021 10:04:27 +0200 Subject: [PATCH 13/13] chore(auth-backend): update api report Signed-off-by: Tom Opdebeeck --- plugins/auth-backend/api-report.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index af4375f5d7..a66584ba85 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -84,6 +84,13 @@ export type BackstageIdentity = { entity?: Entity; }; +// Warning: (ae-missing-release-tag) "createGitlabProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const createGitlabProvider: ( + options?: GitlabProviderOptions | undefined, +) => AuthProviderFactory; + // Warning: (ae-missing-release-tag) "createGoogleProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -133,7 +140,16 @@ export const encodeState: (state: OAuthState) => string; // @public (undocumented) export const ensuresXRequestedWith: (req: express.Request) => boolean; -// Warning: (ae-forgotten-export) The symbol "SignInResolver" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "GitlabProviderOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type GitlabProviderOptions = { + authHandler?: AuthHandler; + signIn?: { + resolver?: SignInResolver; + }; +}; + // Warning: (ae-missing-release-tag) "googleEmailSignInResolver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -398,7 +414,8 @@ export type WebMessageResponse = // // src/identity/types.d.ts:25:5 - (ae-forgotten-export) The symbol "TokenParams" needs to be exported by the entry point index.d.ts // src/identity/types.d.ts:31:9 - (ae-forgotten-export) The symbol "AnyJWK" needs to be exported by the entry point index.d.ts -// src/providers/google/provider.d.ts:36:5 - (ae-forgotten-export) The symbol "AuthHandler" needs to be exported by the entry point index.d.ts +// src/providers/gitlab/provider.d.ts:38:5 - (ae-forgotten-export) The symbol "AuthHandler" needs to be exported by the entry point index.d.ts +// src/providers/gitlab/provider.d.ts:49:9 - (ae-forgotten-export) The symbol "SignInResolver" needs to be exported by the entry point index.d.ts // src/providers/types.d.ts:105:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts // src/providers/types.d.ts:111:5 - (ae-forgotten-export) The symbol "ExperimentalIdentityResolver" needs to be exported by the entry point index.d.ts // src/providers/types.d.ts:128:8 - (tsdoc-missing-deprecation-message) The @deprecated block must include a deprecation message, e.g. describing the recommended alternative