diff --git a/.changeset/fast-cheetahs-grow.md b/.changeset/fast-cheetahs-grow.md new file mode 100644 index 0000000000..5567436b5e --- /dev/null +++ b/.changeset/fast-cheetahs-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-node': minor +--- + +**BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`. diff --git a/.changeset/khaki-pears-march.md b/.changeset/khaki-pears-march.md new file mode 100644 index 0000000000..5626bf3ea5 --- /dev/null +++ b/.changeset/khaki-pears-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +**BREAKING**: All sign-in resolvers must now return a `token` in their sign-in result. Returning an `id` is no longer supported. diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts index 07c8196dd2..87e6b96745 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts @@ -50,7 +50,6 @@ describe('oauth helpers', () => { email: 'foo@bar.com', }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', @@ -110,7 +109,6 @@ describe('oauth helpers', () => { email: 'foo@bar.com', }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', @@ -157,7 +155,6 @@ describe('oauth helpers', () => { displayName: "Adam l'Hôpital", }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index c1130b4270..0ce9dff66c 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -17,7 +17,7 @@ import express from 'express'; import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter'; import { encodeState } from './helpers'; -import { OAuthHandlers, OAuthResponse, OAuthState } from './types'; +import { OAuthHandlers, OAuthState } from './types'; const mockResponseData = { providerInfo: { @@ -30,18 +30,11 @@ const mockResponseData = { email: 'foo@bar.com', }, backstageIdentity: { - id: 'foo', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', }, }; -function mkTokenBody(payload: unknown): string { - return Buffer.from(JSON.stringify(payload), 'utf8') - .toString('base64') - .replace(/=/g, ''); -} - describe('OAuthAdapter', () => { class MyAuthProvider implements OAuthHandlers { async start() { @@ -324,13 +317,11 @@ describe('OAuthAdapter', () => { expect(mockResponse.json).toHaveBeenCalledWith({ ...mockResponseData, backstageIdentity: { - id: mockResponseData.backstageIdentity.id, token: mockResponseData.backstageIdentity.token, - idToken: mockResponseData.backstageIdentity.token, identity: { - ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', userEntityRef: 'user:default/jimmymarkum', + ownershipEntityRefs: ['user:default/jimmymarkum'], }, }, }); @@ -356,95 +347,6 @@ describe('OAuthAdapter', () => { ); }); - it('correctly populates incomplete identities', async () => { - const mockRefresh = jest.fn< - Promise<{ response: OAuthResponse }>, - [express.Request] - >(); - - const oauthProvider = new OAuthAdapter( - { - refresh: mockRefresh, - start: jest.fn(), - handler: jest.fn(), - } as OAuthHandlers, - { - ...oAuthProviderOptions, - tokenIssuer: { - issueToken: async ({ claims }) => `a.${mkTokenBody(claims)}.a`, - listPublicKeys: async () => ({ keys: [] }), - }, - disableRefresh: false, - isOriginAllowed: () => false, - }, - ); - - const mockRequest = { - header: () => 'XMLHttpRequest', - cookies: { - 'test-provider-refresh-token': 'token', - }, - query: {}, - } as unknown as express.Request; - - const mockResponse = { - json: jest.fn().mockReturnThis(), - status: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - // Without a token - mockRefresh.mockResolvedValueOnce({ - response: { - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: '', - }, - }, - }); - await oauthProvider.refresh(mockRequest, mockResponse); - expect(mockResponse.json).toHaveBeenCalledTimes(1); - expect(mockResponse.json).toHaveBeenLastCalledWith({ - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`, - idToken: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`, - identity: { - type: 'user', - userEntityRef: 'user:default/foo', - ownershipEntityRefs: [], - }, - }, - }); - - // With a token - mockRefresh.mockResolvedValueOnce({ - response: { - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - }, - }, - }); - await oauthProvider.refresh(mockRequest, mockResponse); - expect(mockResponse.json).toHaveBeenCalledTimes(2); - expect(mockResponse.json).toHaveBeenLastCalledWith({ - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - idToken: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - identity: { - type: 'user', - userEntityRef: 'user:my-ns/foo', - ownershipEntityRefs: [], - }, - }, - }); - }); - it('sets the correct cookie configuration using a callbackUrl', async () => { const config = { baseUrl: 'http://domain.org/auth', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 07611ddb9c..c895b82ede 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -17,11 +17,6 @@ import express, { CookieOptions } from 'express'; import crypto from 'crypto'; import { URL } from 'url'; -import { - DEFAULT_NAMESPACE, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { BackstageIdentityResponse, BackstageSignInResult, @@ -263,22 +258,11 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { if (!identity) { return undefined; } - - if (identity.token) { - return prepareBackstageIdentityResponse(identity); + if (!identity.token) { + throw new InputError(`Identity response must return a token`); } - const userEntityRef = stringifyEntityRef( - parseEntityRef(identity.id, { - defaultKind: 'user', - defaultNamespace: DEFAULT_NAMESPACE, - }), - ); - const token = await this.options.tokenIssuer.issueToken({ - claims: { sub: userEntityRef }, - }); - - return prepareBackstageIdentityResponse({ ...identity, token }); + return prepareBackstageIdentityResponse(identity); } private setNonceCookie = (res: express.Response, nonce: string) => { diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts index 37b5bc1f68..eb801fdd46 100644 --- a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts +++ b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts @@ -123,9 +123,8 @@ describe('AwsAlbAuthProvider', () => { }), signInResolver: async () => { return { - id: 'user.name', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', }; }, }); @@ -136,11 +135,8 @@ describe('AwsAlbAuthProvider', () => { expect(mockResponse.json).toHaveBeenCalledWith({ backstageIdentity: { - id: 'user.name', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', - idToken: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', identity: { ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', diff --git a/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts b/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts index aab0103140..eb2f5c478d 100644 --- a/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts +++ b/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts @@ -45,7 +45,7 @@ describe('GcpIapProvider', () => { const iapToken = { sub: 's', email: 'e@mail.com' }; authHandler.mockResolvedValueOnce({ email: 'e@mail.com' }); - signInResolver.mockResolvedValueOnce({ id: 'i', token: backstageToken }); + signInResolver.mockResolvedValueOnce({ token: backstageToken }); tokenValidator.mockResolvedValueOnce(iapToken); const app = express(); @@ -61,8 +61,6 @@ describe('GcpIapProvider', () => { ); expect(response.body).toEqual({ backstageIdentity: { - id: 'i', - idToken: backstageToken, token: backstageToken, identity: { type: 'user', diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts index 048a3b9344..61f08bc535 100644 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts +++ b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts @@ -43,7 +43,7 @@ import { describe('Oauth2ProxyAuthProvider', () => { const mockToken = - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob'; + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob'; let provider: Oauth2ProxyAuthProvider; let logger: jest.Mocked; @@ -122,7 +122,6 @@ describe('Oauth2ProxyAuthProvider', () => { profile: {}, }); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); @@ -142,7 +141,6 @@ describe('Oauth2ProxyAuthProvider', () => { const profile = { displayName: 'some value' }; mockRequest.header.mockReturnValue(`Bearer token`); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); authHandler.mockResolvedValue({ profile: profile }); @@ -162,12 +160,10 @@ describe('Oauth2ProxyAuthProvider', () => { ); expect(mockResponse.json).toHaveBeenCalledWith({ backstageIdentity: { - id: 'some-id', - idToken: mockToken, identity: { - ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', userEntityRef: 'user:default/jimmymarkum', + ownershipEntityRefs: ['user:default/jimmymarkum'], }, token: mockToken, }, @@ -186,7 +182,6 @@ describe('Oauth2ProxyAuthProvider', () => { profile: {}, }); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); }); diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts index 24442379c4..dac06f964e 100644 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts @@ -27,13 +27,10 @@ describe('prepareBackstageIdentityResponse', () => { const token = mkToken({ sub: 'k:ns/n', ent: ['k:ns/o'] }); expect( prepareBackstageIdentityResponse({ - id: 'x', token, }), ).toEqual({ - id: 'x', token, - idToken: token, identity: { type: 'user', userEntityRef: 'k:ns/n', @@ -41,67 +38,4 @@ describe('prepareBackstageIdentityResponse', () => { }, }); }); - - it('populates incomplete identities', () => { - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:default/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'k:n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'k:default/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'ns/n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:ns/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'n', ent: ['k:ns/o'] }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:default/n', - ownershipEntityRefs: ['k:ns/o'], - }, - }); - }); }); diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts index 5a4e0895d9..761618c225 100644 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -import { - DEFAULT_NAMESPACE, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { BackstageIdentityResponse, BackstageSignInResult, @@ -41,21 +36,11 @@ export function prepareBackstageIdentityResponse( ): BackstageIdentityResponse { const { sub, ent } = parseJwtPayload(result.token); - const userEntityRef = stringifyEntityRef( - parseEntityRef(sub, { - defaultKind: 'user', - defaultNamespace: DEFAULT_NAMESPACE, - }), - ); return { - ...{ - // TODO: idToken is for backwards compatibility and can be removed in the future - idToken: result.token, - ...result, - }, + ...result, identity: { type: 'user', - userEntityRef, + userEntityRef: sub, ownershipEntityRefs: ent ?? [], }, }; diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index 7840fc7d74..f7fdd6369b 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Entity } from '@backstage/catalog-model'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; // @public @@ -13,10 +12,6 @@ export interface BackstageIdentityResponse extends BackstageSignInResult { // @public export interface BackstageSignInResult { - // @deprecated - entity?: Entity; - // @deprecated - id: string; token: string; } diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index fc595a13ba..cb430f2de1 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -24,7 +24,6 @@ }, "dependencies": { "@backstage/backend-common": "^0.13.1", - "@backstage/catalog-model": "^1.0.0", "@backstage/config": "^1.0.0", "@backstage/errors": "^1.0.0", "jose": "^1.27.1", diff --git a/plugins/auth-node/src/IdentityClient.test.ts b/plugins/auth-node/src/IdentityClient.test.ts index 72ef7f2a57..88d4021dc3 100644 --- a/plugins/auth-node/src/IdentityClient.test.ts +++ b/plugins/auth-node/src/IdentityClient.test.ts @@ -135,12 +135,11 @@ describe('IdentityClient', () => { const token = await factory.issueToken({ claims: { sub: 'foo' } }); const response = await client.authenticate(token); expect(response).toEqual({ - id: 'foo', token: token, identity: { - ownershipEntityRefs: [], type: 'user', userEntityRef: 'foo', + ownershipEntityRefs: [], }, }); }); @@ -202,12 +201,11 @@ describe('IdentityClient', () => { const token = await factory.issueToken({ claims: { sub: 'foo' } }); const response = await client.authenticate(token); expect(response).toEqual({ - id: 'foo', token: token, identity: { - ownershipEntityRefs: [], type: 'user', userEntityRef: 'foo', + ownershipEntityRefs: [], }, }); }); diff --git a/plugins/auth-node/src/IdentityClient.ts b/plugins/auth-node/src/IdentityClient.ts index d8e841bf75..54c2670ae8 100644 --- a/plugins/auth-node/src/IdentityClient.ts +++ b/plugins/auth-node/src/IdentityClient.ts @@ -87,7 +87,6 @@ export class IdentityClient { } const user: BackstageIdentityResponse = { - id: decoded.sub, token, identity: { type: 'user', diff --git a/plugins/auth-node/src/types.ts b/plugins/auth-node/src/types.ts index b574c2204d..0d3e015beb 100644 --- a/plugins/auth-node/src/types.ts +++ b/plugins/auth-node/src/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; - /** * A representation of a successful Backstage sign-in. * @@ -25,25 +23,6 @@ import { Entity } from '@backstage/catalog-model'; * @public */ export interface BackstageSignInResult { - /** - * An opaque ID that uniquely identifies the user within Backstage. - * - * This is typically the same as the user entity `metadata.name`. - * - * @deprecated Use the `identity` field instead - */ - id: string; - - /** - * The entity that the user is represented by within Backstage. - * - * This entity may or may not exist within the Catalog, and it can be used - * to read and store additional metadata about the user. - * - * @deprecated Use the `identity` field instead. - */ - entity?: Entity; - /** * The token used to authenticate the user within Backstage. */