auth-node,auth-backend: remove deprecated identity result fields
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-node': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`.
|
||||
@@ -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 ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,7 +87,6 @@ export class IdentityClient {
|
||||
}
|
||||
|
||||
const user: BackstageIdentityResponse = {
|
||||
id: decoded.sub,
|
||||
token,
|
||||
identity: {
|
||||
type: 'user',
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user