auth-node,auth-backend: remove deprecated identity result fields

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-03-17 22:56:03 +01:00
parent ffaaec5950
commit 15d3a3c39a
15 changed files with 25 additions and 254 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-node': minor
---
**BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`.
+5
View File
@@ -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.
@@ -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',
@@ -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',
@@ -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) => {
@@ -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',
@@ -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',
@@ -43,7 +43,7 @@ import {
describe('Oauth2ProxyAuthProvider', () => {
const mockToken =
'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob';
'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob';
let provider: Oauth2ProxyAuthProvider<any>;
let logger: jest.Mocked<Logger>;
@@ -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,
});
});
@@ -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'],
},
});
});
});
@@ -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 ?? [],
},
};
-5
View File
@@ -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;
}
-1
View File
@@ -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",
+2 -4
View File
@@ -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: [],
},
});
});
-1
View File
@@ -87,7 +87,6 @@ export class IdentityClient {
}
const user: BackstageIdentityResponse = {
id: decoded.sub,
token,
identity: {
type: 'user',
-21
View File
@@ -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.
*/