removed all project references
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Vendored
-58
@@ -84,64 +84,6 @@ export interface Config {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* The available auth-provider options and attributes
|
||||
* @additionalProperties true
|
||||
*/
|
||||
providers?: {
|
||||
/** @visibility frontend */
|
||||
saml?: {
|
||||
entryPoint: string;
|
||||
logoutUrl?: string;
|
||||
issuer: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
cert: string;
|
||||
audience?: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
privateKey?: string;
|
||||
authnContext?: string[];
|
||||
identifierFormat?: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
decryptionPvk?: string;
|
||||
signatureAlgorithm?: 'sha256' | 'sha512';
|
||||
digestAlgorithm?: string;
|
||||
acceptedClockSkewMs?: number;
|
||||
};
|
||||
/** @visibility frontend */
|
||||
auth0?: {
|
||||
[authEnv: string]: {
|
||||
clientId: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
clientSecret: string;
|
||||
domain: string;
|
||||
callbackUrl?: string;
|
||||
audience?: string;
|
||||
connection?: string;
|
||||
connectionScope?: string;
|
||||
};
|
||||
};
|
||||
/** @visibility frontend */
|
||||
onelogin?: {
|
||||
[authEnv: string]: {
|
||||
clientId: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
clientSecret: string;
|
||||
issuer: string;
|
||||
callbackUrl?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* The backstage token expiration.
|
||||
*/
|
||||
|
||||
@@ -49,23 +49,6 @@
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-auth0-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-azure-easyauth-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-server-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-cloudflare-access-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-gcp-iap-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-gitlab-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-google-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-microsoft-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-oauth2-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-oidc-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-okta-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-backend-module-onelogin-provider": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/plugin-catalog-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
|
||||
@@ -88,7 +88,6 @@ export const authPlugin = createBackendPlugin({
|
||||
httpAuth,
|
||||
catalogApi,
|
||||
providerFactories: Object.fromEntries(providers),
|
||||
disableDefaultProviderFactories: true,
|
||||
ownershipResolver,
|
||||
});
|
||||
httpRouter.addAuthPolicy({
|
||||
|
||||
@@ -1,549 +0,0 @@
|
||||
/*
|
||||
* 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 express from 'express';
|
||||
import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter';
|
||||
import { encodeState } from './helpers';
|
||||
import { OAuthHandlers, OAuthLogoutRequest } from './types';
|
||||
import { CookieConfigurer, OAuthState } from '@backstage/plugin-auth-node';
|
||||
|
||||
const mockResponseData = {
|
||||
providerInfo: {
|
||||
accessToken: 'ACCESS_TOKEN',
|
||||
token: 'ID_TOKEN',
|
||||
expiresInSeconds: 10,
|
||||
scope: 'email',
|
||||
},
|
||||
profile: {
|
||||
email: 'foo@bar.com',
|
||||
},
|
||||
backstageIdentity: {
|
||||
token:
|
||||
'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob',
|
||||
},
|
||||
};
|
||||
|
||||
describe('OAuthAdapter', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
class MyAuthProvider implements OAuthHandlers {
|
||||
async start() {
|
||||
return {
|
||||
url: '/url',
|
||||
status: 301,
|
||||
};
|
||||
}
|
||||
async handler() {
|
||||
return {
|
||||
response: mockResponseData,
|
||||
refreshToken: 'token',
|
||||
};
|
||||
}
|
||||
async refresh() {
|
||||
return {
|
||||
response: mockResponseData,
|
||||
refreshToken: 'token',
|
||||
};
|
||||
}
|
||||
async logout(_: OAuthLogoutRequest) {}
|
||||
}
|
||||
const providerInstance = new MyAuthProvider();
|
||||
const mockCookieConfig: ReturnType<CookieConfigurer> = {
|
||||
domain: 'domain.org',
|
||||
path: '/auth/test-provider',
|
||||
secure: false,
|
||||
};
|
||||
const mockCookieConfigurer = jest.fn().mockReturnValue(mockCookieConfig);
|
||||
|
||||
const oAuthProviderOptions = {
|
||||
providerId: 'test-provider',
|
||||
appOrigin: 'http://localhost:3000',
|
||||
baseUrl: 'http://domain.org/auth',
|
||||
cookieConfigurer: mockCookieConfigurer,
|
||||
tokenIssuer: {
|
||||
issueToken: async () => 'my-id-token',
|
||||
listPublicKeys: async () => ({ keys: [] }),
|
||||
},
|
||||
isOriginAllowed: () => false,
|
||||
callbackUrl: 'http://domain.org/auth/test-provider/handler/frame',
|
||||
};
|
||||
|
||||
const defaultState = { nonce: 'nonce', env: 'development' };
|
||||
|
||||
const createEncodedQueryMockRequest = (state: any) => {
|
||||
return {
|
||||
cookies: {
|
||||
'test-provider-nonce': 'nonce',
|
||||
},
|
||||
query: {
|
||||
state: encodeState(state),
|
||||
},
|
||||
} as unknown as express.Request;
|
||||
};
|
||||
|
||||
const mockResponse = {
|
||||
cookie: jest.fn().mockReturnThis(),
|
||||
end: jest.fn().mockReturnThis(),
|
||||
setHeader: jest.fn().mockReturnThis(),
|
||||
statusCode: jest.fn().mockReturnThis(),
|
||||
redirect: jest.fn().mockReturnThis(),
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn().mockReturnThis(),
|
||||
} as unknown as express.Response;
|
||||
|
||||
const mockStartRequest = {
|
||||
query: {
|
||||
scope: 'user',
|
||||
env: 'development',
|
||||
},
|
||||
} as unknown as express.Request;
|
||||
|
||||
const expectedStartAuthCookieData = {
|
||||
httpOnly: true,
|
||||
path: '/auth/test-provider/handler',
|
||||
maxAge: TEN_MINUTES_MS,
|
||||
domain: 'domain.org',
|
||||
sameSite: 'lax',
|
||||
secure: false,
|
||||
};
|
||||
|
||||
it('sets the correct headers in start', async () => {
|
||||
const oauthProvider = new OAuthAdapter(
|
||||
providerInstance,
|
||||
oAuthProviderOptions,
|
||||
);
|
||||
|
||||
await oauthProvider.start(mockStartRequest, mockResponse);
|
||||
// nonce cookie checks
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
`${oAuthProviderOptions.providerId}-nonce`,
|
||||
expect.any(String),
|
||||
expect.objectContaining(expectedStartAuthCookieData),
|
||||
);
|
||||
expect(mockResponse.setHeader).toHaveBeenCalledTimes(2);
|
||||
expect(mockResponse.setHeader).toHaveBeenCalledWith('Location', '/url');
|
||||
expect(mockResponse.setHeader).toHaveBeenCalledWith('Content-Length', '0');
|
||||
expect(mockResponse.statusCode).toEqual(301);
|
||||
expect(mockResponse.end).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const refreshCookieData = {
|
||||
...expectedStartAuthCookieData,
|
||||
path: '/auth/test-provider',
|
||||
maxAge: THOUSAND_DAYS_MS,
|
||||
};
|
||||
|
||||
it('sets the refresh cookie if refresh is enabled', async () => {
|
||||
const oauthProvider = new OAuthAdapter(providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
isOriginAllowed: () => false,
|
||||
});
|
||||
|
||||
const mockRequest = createEncodedQueryMockRequest(defaultState);
|
||||
|
||||
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
||||
expect(mockCookieConfigurer).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
expect.stringContaining('test-provider-refresh-token'),
|
||||
expect.stringContaining('token'),
|
||||
expect.objectContaining(refreshCookieData),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sets the refresh cookie if refresh is enabled with redirect', async () => {
|
||||
const oauthProvider = new OAuthAdapter(providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
isOriginAllowed: () => false,
|
||||
});
|
||||
|
||||
const state = {
|
||||
...defaultState,
|
||||
redirectUrl: 'http://localhost:3000',
|
||||
flow: 'redirect',
|
||||
};
|
||||
const mockRequest = createEncodedQueryMockRequest(state);
|
||||
|
||||
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
||||
expect(mockResponse.redirect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('persists scope through cookie if enabled', async () => {
|
||||
const handlers = {
|
||||
start: jest.fn(async (_req: { state: OAuthState }) => ({
|
||||
url: '/url',
|
||||
status: 301,
|
||||
})),
|
||||
handler: jest.fn(async () => ({ response: mockResponseData })),
|
||||
refresh: jest.fn(async () => ({ response: mockResponseData })),
|
||||
};
|
||||
const oauthProvider = new OAuthAdapter(handlers, {
|
||||
...oAuthProviderOptions,
|
||||
persistScopes: true,
|
||||
});
|
||||
|
||||
// First we test the /start request, making sure state is set
|
||||
await oauthProvider.start(mockStartRequest, mockResponse);
|
||||
|
||||
expect(handlers.start).toHaveBeenCalledTimes(1);
|
||||
expect(handlers.start).toHaveBeenCalledWith({
|
||||
...mockStartRequest,
|
||||
scope: 'user',
|
||||
state: {
|
||||
nonce: expect.any(String),
|
||||
env: 'development',
|
||||
scope: 'user',
|
||||
},
|
||||
});
|
||||
|
||||
// Then test the /handler, making sure the granted scope cookie is set
|
||||
const providedState = handlers.start.mock.calls[0][0].state;
|
||||
const mockHandleReq = {
|
||||
cookies: {
|
||||
'test-provider-nonce': providedState.nonce,
|
||||
},
|
||||
query: {
|
||||
state: encodeState(providedState),
|
||||
},
|
||||
} as unknown as express.Request;
|
||||
const mockHandleRes = {
|
||||
cookie: jest.fn().mockReturnThis(),
|
||||
setHeader: jest.fn().mockReturnThis(),
|
||||
end: jest.fn().mockReturnThis(),
|
||||
redirect: jest.fn().mockReturnThis(),
|
||||
} as unknown as express.Response;
|
||||
|
||||
await oauthProvider.frameHandler(mockHandleReq, mockHandleRes);
|
||||
expect(mockHandleRes.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockHandleRes.cookie).toHaveBeenCalledWith(
|
||||
'test-provider-granted-scope',
|
||||
'user',
|
||||
expect.objectContaining(refreshCookieData),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
|
||||
// Then make sure scopes are forwarded correctly during refresh
|
||||
const mockRefreshReq = {
|
||||
query: { scope: 'ignore-me' },
|
||||
cookies: {
|
||||
'test-provider-granted-scope': 'user',
|
||||
'test-provider-refresh-token': 'refresh-token',
|
||||
},
|
||||
header: jest.fn().mockReturnValue('XMLHttpRequest'),
|
||||
} as unknown as express.Request;
|
||||
const mockRefreshRes = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
json: jest.fn().mockReturnThis(),
|
||||
redirect: jest.fn().mockReturnThis(),
|
||||
} as unknown as express.Response;
|
||||
await oauthProvider.refresh(mockRefreshReq, mockRefreshRes);
|
||||
expect(handlers.refresh).toHaveBeenCalledTimes(1);
|
||||
expect(handlers.refresh).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
scope: 'user',
|
||||
refreshToken: 'refresh-token',
|
||||
}),
|
||||
);
|
||||
expect(mockRefreshRes.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const mockRequestWithHeader = {
|
||||
header: () => 'XMLHttpRequest',
|
||||
cookies: {
|
||||
'test-provider-refresh-token': 'token',
|
||||
},
|
||||
query: {},
|
||||
get: jest.fn(),
|
||||
} as unknown as express.Request;
|
||||
|
||||
it('removes refresh cookie and calls logout handler when logging out', async () => {
|
||||
const logoutSpy = jest.spyOn(providerInstance, 'logout');
|
||||
const oauthProvider = new OAuthAdapter(providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
isOriginAllowed: () => false,
|
||||
});
|
||||
|
||||
await oauthProvider.logout(mockRequestWithHeader, mockResponse);
|
||||
expect(mockRequestWithHeader.get).toHaveBeenCalledTimes(1);
|
||||
expect(logoutSpy).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
expect.stringContaining('test-provider-refresh-token'),
|
||||
'',
|
||||
expect.objectContaining({ path: '/auth/test-provider' }),
|
||||
);
|
||||
expect(mockResponse.end).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('gets new access-token when refreshing', async () => {
|
||||
const oauthProvider = new OAuthAdapter(providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
isOriginAllowed: () => false,
|
||||
});
|
||||
|
||||
await oauthProvider.refresh(mockRequestWithHeader, mockResponse);
|
||||
expect(mockResponse.json).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.json).toHaveBeenCalledWith({
|
||||
...mockResponseData,
|
||||
backstageIdentity: {
|
||||
token: mockResponseData.backstageIdentity.token,
|
||||
identity: {
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/jimmymarkum',
|
||||
ownershipEntityRefs: ['user:default/jimmymarkum'],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sets new access-token when old cookie exists', async () => {
|
||||
const oauthProvider = new OAuthAdapter(providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
isOriginAllowed: () => false,
|
||||
});
|
||||
|
||||
const mockRequest = {
|
||||
...mockRequestWithHeader,
|
||||
cookies: {
|
||||
'test-provider-refresh-token': 'old-token',
|
||||
},
|
||||
} as unknown as express.Request;
|
||||
|
||||
await oauthProvider.refresh(mockRequest, mockResponse);
|
||||
expect(mockRequest.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockCookieConfigurer).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
'test-provider-refresh-token',
|
||||
'token',
|
||||
expect.objectContaining(refreshCookieData),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sets the correct nonce cookie configuration', async () => {
|
||||
const config = {
|
||||
baseUrl: 'http://domain.org/auth',
|
||||
appUrl: 'http://domain.org',
|
||||
isOriginAllowed: () => false,
|
||||
};
|
||||
|
||||
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
});
|
||||
|
||||
await oauthProvider.start(mockStartRequest, mockResponse);
|
||||
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
`${oAuthProviderOptions.providerId}-nonce`,
|
||||
expect.any(String),
|
||||
expect.objectContaining(expectedStartAuthCookieData),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const config = {
|
||||
baseUrl: 'http://domain.org/auth',
|
||||
appUrl: 'http://domain.org',
|
||||
isOriginAllowed: () => false,
|
||||
};
|
||||
|
||||
const mockStartRequestWithOrigin = {
|
||||
query: {
|
||||
scope: 'user',
|
||||
env: 'development',
|
||||
origin: 'http://other.domain',
|
||||
},
|
||||
} as unknown as express.Request;
|
||||
|
||||
it('sets the correct nonce cookie configuration using origin from request', async () => {
|
||||
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
||||
});
|
||||
|
||||
await oauthProvider.start(mockStartRequestWithOrigin, mockResponse);
|
||||
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
`${oAuthProviderOptions.providerId}-nonce`,
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
...expectedStartAuthCookieData,
|
||||
secure: true,
|
||||
sameSite: 'none',
|
||||
}),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const secureCookieData = {
|
||||
...refreshCookieData,
|
||||
secure: true,
|
||||
sameSite: 'lax',
|
||||
maxAge: THOUSAND_DAYS_MS,
|
||||
};
|
||||
|
||||
it('sets the correct cookie configuration using an secure callbackUrl', async () => {
|
||||
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
||||
});
|
||||
|
||||
const mockRequest = createEncodedQueryMockRequest(defaultState);
|
||||
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
||||
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
expect.stringContaining('test-provider-refresh-token'),
|
||||
expect.stringContaining('token'),
|
||||
expect.objectContaining(secureCookieData),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const secureSameSiteNoneCookieData = {
|
||||
...secureCookieData,
|
||||
sameSite: 'none',
|
||||
};
|
||||
|
||||
it('sets the correct cookie configuration when on different domains and secure', async () => {
|
||||
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
callbackUrl: 'https://authdomain.org/auth/test-provider/handler/frame',
|
||||
});
|
||||
|
||||
const mockRequest = createEncodedQueryMockRequest(defaultState);
|
||||
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
||||
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
expect.stringContaining('test-provider-refresh-token'),
|
||||
expect.stringContaining('token'),
|
||||
expect.objectContaining({
|
||||
...secureSameSiteNoneCookieData,
|
||||
domain: 'authdomain.org',
|
||||
}),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const configOriginAllowed = {
|
||||
...config,
|
||||
isOriginAllowed: () => true,
|
||||
};
|
||||
|
||||
it('sets the correct cookie configuration using origin from state', async () => {
|
||||
const oauthProvider = OAuthAdapter.fromConfig(
|
||||
configOriginAllowed,
|
||||
providerInstance,
|
||||
{
|
||||
...oAuthProviderOptions,
|
||||
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
||||
},
|
||||
);
|
||||
|
||||
const mockRequest = createEncodedQueryMockRequest({
|
||||
...defaultState,
|
||||
origin: 'http://other.domain',
|
||||
});
|
||||
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
||||
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
expect.stringContaining('test-provider-refresh-token'),
|
||||
expect.stringContaining('token'),
|
||||
expect.objectContaining(secureSameSiteNoneCookieData),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const mockRequestWithGetMockReturn = {
|
||||
header: () => 'XMLHttpRequest',
|
||||
cookies: {
|
||||
'test-provider-refresh-token': 'old-token',
|
||||
},
|
||||
query: {},
|
||||
get: jest.fn().mockReturnValue('http://other.domain'),
|
||||
} as unknown as express.Request;
|
||||
|
||||
it('sets the correct cookie configuration using origin from header', async () => {
|
||||
const oauthProvider = OAuthAdapter.fromConfig(config, providerInstance, {
|
||||
...oAuthProviderOptions,
|
||||
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
||||
});
|
||||
|
||||
await oauthProvider.refresh(mockRequestWithGetMockReturn, mockResponse);
|
||||
expect(mockRequestWithGetMockReturn.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
||||
expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.cookie).toHaveBeenCalledWith(
|
||||
'test-provider-refresh-token',
|
||||
'token',
|
||||
expect.objectContaining(secureSameSiteNoneCookieData),
|
||||
);
|
||||
expect(mockResponse.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('executed a response redirect when flow query string is set to "redirect"', async () => {
|
||||
const handlers = {
|
||||
start: jest.fn(async (_req: { state: OAuthState }) => ({
|
||||
url: '/url',
|
||||
status: 301,
|
||||
})),
|
||||
handler: jest.fn(async () => ({ response: mockResponseData })),
|
||||
refresh: jest.fn(async () => ({ response: mockResponseData })),
|
||||
};
|
||||
const configWithNoPopupEnabled = {
|
||||
...configOriginAllowed,
|
||||
};
|
||||
const oauthProvider = OAuthAdapter.fromConfig(
|
||||
configWithNoPopupEnabled,
|
||||
handlers,
|
||||
{
|
||||
...oAuthProviderOptions,
|
||||
callbackUrl: 'https://domain.org/auth/test-provider/handler/frame',
|
||||
},
|
||||
);
|
||||
|
||||
const state = {
|
||||
...defaultState,
|
||||
origin: 'http://other.domain',
|
||||
redirectUrl: 'http://domain.org',
|
||||
flow: 'redirect',
|
||||
};
|
||||
|
||||
const mockRequest = {
|
||||
...createEncodedQueryMockRequest(state),
|
||||
get: jest.fn().mockReturnValue('http://other.domain'),
|
||||
} as unknown as express.Request;
|
||||
|
||||
await oauthProvider.frameHandler(mockRequest, mockResponse);
|
||||
expect(mockRequest.get).not.toHaveBeenCalled();
|
||||
expect(mockCookieConfigurer).not.toHaveBeenCalled();
|
||||
expect(mockResponse.cookie).not.toHaveBeenCalled();
|
||||
expect(mockResponse.redirect).toHaveBeenCalledTimes(1);
|
||||
expect(mockResponse.redirect).toHaveBeenCalledWith('http://domain.org');
|
||||
});
|
||||
});
|
||||
@@ -1,357 +0,0 @@
|
||||
/*
|
||||
* 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 express, { CookieOptions } from 'express';
|
||||
import crypto from 'crypto';
|
||||
import { URL } from 'url';
|
||||
import {
|
||||
AuthProviderConfig,
|
||||
AuthProviderRouteHandlers,
|
||||
BackstageIdentityResponse,
|
||||
BackstageSignInResult,
|
||||
CookieConfigurer,
|
||||
OAuthState,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
AuthenticationError,
|
||||
InputError,
|
||||
isError,
|
||||
NotAllowedError,
|
||||
} from '@backstage/errors';
|
||||
import { defaultCookieConfigurer, readState, verifyNonce } from './helpers';
|
||||
import {
|
||||
postMessageResponse,
|
||||
ensuresXRequestedWith,
|
||||
WebMessageResponse,
|
||||
} from '../flow';
|
||||
import {
|
||||
OAuthHandlers,
|
||||
OAuthStartRequest,
|
||||
OAuthRefreshRequest,
|
||||
OAuthLogoutRequest,
|
||||
} from './types';
|
||||
import { prepareBackstageIdentityResponse } from '../../providers/prepareBackstageIdentityResponse';
|
||||
|
||||
export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;
|
||||
export const TEN_MINUTES_MS = 600 * 1000;
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead
|
||||
*/
|
||||
export type OAuthAdapterOptions = {
|
||||
providerId: string;
|
||||
persistScopes?: boolean;
|
||||
appOrigin: string;
|
||||
baseUrl: string;
|
||||
cookieConfigurer: CookieConfigurer;
|
||||
isOriginAllowed: (origin: string) => boolean;
|
||||
callbackUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use `createOAuthRouteHandlers` from `@backstage/plugin-auth-node` instead
|
||||
*/
|
||||
export class OAuthAdapter implements AuthProviderRouteHandlers {
|
||||
static fromConfig(
|
||||
config: AuthProviderConfig,
|
||||
handlers: OAuthHandlers,
|
||||
options: Pick<
|
||||
OAuthAdapterOptions,
|
||||
'providerId' | 'persistScopes' | 'callbackUrl'
|
||||
>,
|
||||
): OAuthAdapter {
|
||||
const { appUrl, baseUrl, isOriginAllowed } = config;
|
||||
const { origin: appOrigin } = new URL(appUrl);
|
||||
|
||||
const cookieConfigurer = config.cookieConfigurer ?? defaultCookieConfigurer;
|
||||
|
||||
return new OAuthAdapter(handlers, {
|
||||
...options,
|
||||
appOrigin,
|
||||
baseUrl,
|
||||
cookieConfigurer,
|
||||
isOriginAllowed,
|
||||
});
|
||||
}
|
||||
|
||||
private readonly baseCookieOptions: CookieOptions;
|
||||
|
||||
constructor(
|
||||
private readonly handlers: OAuthHandlers,
|
||||
private readonly options: OAuthAdapterOptions,
|
||||
) {
|
||||
this.baseCookieOptions = {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
};
|
||||
}
|
||||
|
||||
async start(req: express.Request, res: express.Response): Promise<void> {
|
||||
// retrieve scopes from request
|
||||
const scope = req.query.scope?.toString() ?? '';
|
||||
const env = req.query.env?.toString();
|
||||
const origin = req.query.origin?.toString();
|
||||
const redirectUrl = req.query.redirectUrl?.toString();
|
||||
const flow = req.query.flow?.toString();
|
||||
|
||||
if (!env) {
|
||||
throw new InputError('No env provided in request query parameters');
|
||||
}
|
||||
|
||||
const cookieConfig = this.getCookieConfig(origin);
|
||||
|
||||
const nonce = crypto.randomBytes(16).toString('base64');
|
||||
// set a nonce cookie before redirecting to oauth provider
|
||||
this.setNonceCookie(res, nonce, cookieConfig);
|
||||
|
||||
const state: OAuthState = { nonce, env, origin, redirectUrl, flow };
|
||||
|
||||
// If scopes are persisted then we pass them through the state so that we
|
||||
// can set the cookie on successful auth
|
||||
if (this.options.persistScopes) {
|
||||
state.scope = scope;
|
||||
}
|
||||
const forwardReq = Object.assign(req, { scope, state });
|
||||
|
||||
const { url, status } = await this.handlers.start(
|
||||
forwardReq as OAuthStartRequest,
|
||||
);
|
||||
|
||||
res.statusCode = status || 302;
|
||||
res.setHeader('Location', url);
|
||||
res.setHeader('Content-Length', '0');
|
||||
res.end();
|
||||
}
|
||||
|
||||
async frameHandler(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<void> {
|
||||
let appOrigin = this.options.appOrigin;
|
||||
|
||||
try {
|
||||
const state: OAuthState = readState(req.query.state?.toString() ?? '');
|
||||
|
||||
if (state.origin) {
|
||||
try {
|
||||
appOrigin = new URL(state.origin).origin;
|
||||
} catch {
|
||||
throw new NotAllowedError('App origin is invalid, failed to parse');
|
||||
}
|
||||
if (!this.options.isOriginAllowed(appOrigin)) {
|
||||
throw new NotAllowedError(`Origin '${appOrigin}' is not allowed`);
|
||||
}
|
||||
}
|
||||
|
||||
// verify nonce cookie and state cookie on callback
|
||||
verifyNonce(req, this.options.providerId);
|
||||
|
||||
const { response, refreshToken } = await this.handlers.handler(req);
|
||||
|
||||
const cookieConfig = this.getCookieConfig(appOrigin);
|
||||
|
||||
// Store the scope that we have been granted for this session. This is useful if
|
||||
// the provider does not return granted scopes on refresh or if they are normalized.
|
||||
if (this.options.persistScopes && state.scope) {
|
||||
this.setGrantedScopeCookie(res, state.scope, cookieConfig);
|
||||
response.providerInfo.scope = state.scope;
|
||||
}
|
||||
|
||||
if (refreshToken) {
|
||||
// set new refresh token
|
||||
this.setRefreshTokenCookie(res, refreshToken, cookieConfig);
|
||||
}
|
||||
|
||||
const identity = await this.populateIdentity(response.backstageIdentity);
|
||||
|
||||
const responseObj: WebMessageResponse = {
|
||||
type: 'authorization_response',
|
||||
response: { ...response, backstageIdentity: identity },
|
||||
};
|
||||
|
||||
if (state.flow === 'redirect') {
|
||||
if (!state.redirectUrl) {
|
||||
throw new InputError(
|
||||
'No redirectUrl provided in request query parameters',
|
||||
);
|
||||
}
|
||||
res.redirect(state.redirectUrl);
|
||||
return undefined;
|
||||
}
|
||||
// post message back to popup if successful
|
||||
return postMessageResponse(res, appOrigin, responseObj);
|
||||
} catch (error) {
|
||||
const { name, message } = isError(error)
|
||||
? error
|
||||
: new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value
|
||||
// post error message back to popup if failure
|
||||
return postMessageResponse(res, appOrigin, {
|
||||
type: 'authorization_response',
|
||||
error: { name, message },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async logout(req: express.Request, res: express.Response): Promise<void> {
|
||||
if (!ensuresXRequestedWith(req)) {
|
||||
throw new AuthenticationError('Invalid X-Requested-With header');
|
||||
}
|
||||
|
||||
if (this.handlers.logout) {
|
||||
const refreshToken = this.getRefreshTokenFromCookie(req);
|
||||
const revokeRequest: OAuthLogoutRequest = Object.assign(req, {
|
||||
refreshToken,
|
||||
});
|
||||
await this.handlers.logout(revokeRequest);
|
||||
}
|
||||
|
||||
// remove refresh token cookie if it is set
|
||||
const origin = req.get('origin');
|
||||
const cookieConfig = this.getCookieConfig(origin);
|
||||
this.removeRefreshTokenCookie(res, cookieConfig);
|
||||
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async refresh(req: express.Request, res: express.Response): Promise<void> {
|
||||
if (!ensuresXRequestedWith(req)) {
|
||||
throw new AuthenticationError('Invalid X-Requested-With header');
|
||||
}
|
||||
|
||||
if (!this.handlers.refresh) {
|
||||
throw new InputError(
|
||||
`Refresh token is not supported for provider ${this.options.providerId}`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const refreshToken = this.getRefreshTokenFromCookie(req);
|
||||
|
||||
// throw error if refresh token is missing in the request
|
||||
if (!refreshToken) {
|
||||
throw new InputError('Missing session cookie');
|
||||
}
|
||||
|
||||
let scope = req.query.scope?.toString() ?? '';
|
||||
if (this.options.persistScopes) {
|
||||
scope = this.getGrantedScopeFromCookie(req);
|
||||
}
|
||||
const forwardReq = Object.assign(req, { scope, refreshToken });
|
||||
|
||||
// get new access_token
|
||||
const { response, refreshToken: newRefreshToken } =
|
||||
await this.handlers.refresh(forwardReq as OAuthRefreshRequest);
|
||||
|
||||
const backstageIdentity = await this.populateIdentity(
|
||||
response.backstageIdentity,
|
||||
);
|
||||
|
||||
if (newRefreshToken && newRefreshToken !== refreshToken) {
|
||||
const origin = req.get('origin');
|
||||
const cookieConfig = this.getCookieConfig(origin);
|
||||
this.setRefreshTokenCookie(res, newRefreshToken, cookieConfig);
|
||||
}
|
||||
|
||||
res.status(200).json({ ...response, backstageIdentity });
|
||||
} catch (error) {
|
||||
throw new AuthenticationError('Refresh failed', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the response from the OAuth provider includes a Backstage identity, we
|
||||
* make sure it's populated with all the information we can derive from the user ID.
|
||||
*/
|
||||
private async populateIdentity(
|
||||
identity?: BackstageSignInResult,
|
||||
): Promise<BackstageIdentityResponse | undefined> {
|
||||
if (!identity) {
|
||||
return undefined;
|
||||
}
|
||||
if (!identity.token) {
|
||||
throw new InputError(`Identity response must return a token`);
|
||||
}
|
||||
|
||||
return prepareBackstageIdentityResponse(identity);
|
||||
}
|
||||
|
||||
private setNonceCookie = (
|
||||
res: express.Response,
|
||||
nonce: string,
|
||||
cookieConfig: ReturnType<CookieConfigurer>,
|
||||
) => {
|
||||
res.cookie(`${this.options.providerId}-nonce`, nonce, {
|
||||
maxAge: TEN_MINUTES_MS,
|
||||
...this.baseCookieOptions,
|
||||
...cookieConfig,
|
||||
path: `${cookieConfig.path}/handler`,
|
||||
});
|
||||
};
|
||||
|
||||
private setGrantedScopeCookie = (
|
||||
res: express.Response,
|
||||
scope: string,
|
||||
cookieConfig: ReturnType<CookieConfigurer>,
|
||||
) => {
|
||||
res.cookie(`${this.options.providerId}-granted-scope`, scope, {
|
||||
maxAge: THOUSAND_DAYS_MS,
|
||||
...this.baseCookieOptions,
|
||||
...cookieConfig,
|
||||
});
|
||||
};
|
||||
|
||||
private getRefreshTokenFromCookie = (req: express.Request) => {
|
||||
return req.cookies[`${this.options.providerId}-refresh-token`];
|
||||
};
|
||||
|
||||
private getGrantedScopeFromCookie = (req: express.Request) => {
|
||||
return req.cookies[`${this.options.providerId}-granted-scope`];
|
||||
};
|
||||
|
||||
private setRefreshTokenCookie = (
|
||||
res: express.Response,
|
||||
refreshToken: string,
|
||||
cookieConfig: ReturnType<CookieConfigurer>,
|
||||
) => {
|
||||
res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {
|
||||
maxAge: THOUSAND_DAYS_MS,
|
||||
...this.baseCookieOptions,
|
||||
...cookieConfig,
|
||||
});
|
||||
};
|
||||
|
||||
private removeRefreshTokenCookie = (
|
||||
res: express.Response,
|
||||
cookieConfig: ReturnType<CookieConfigurer>,
|
||||
) => {
|
||||
res.cookie(`${this.options.providerId}-refresh-token`, '', {
|
||||
maxAge: 0,
|
||||
...this.baseCookieOptions,
|
||||
...cookieConfig,
|
||||
});
|
||||
};
|
||||
|
||||
private getCookieConfig = (origin?: string) => {
|
||||
return this.options.cookieConfigurer({
|
||||
providerId: this.options.providerId,
|
||||
baseUrl: this.options.baseUrl,
|
||||
callbackUrl: this.options.callbackUrl,
|
||||
appOrigin: origin ?? this.options.appOrigin,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
|
||||
export { OAuthEnvironmentHandler } from './OAuthEnvironmentHandler';
|
||||
export type { OAuthAdapterOptions } from './OAuthAdapter';
|
||||
export { OAuthAdapter } from './OAuthAdapter';
|
||||
export { encodeState, verifyNonce, readState } from './helpers';
|
||||
export type {
|
||||
OAuthHandlers,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { atlassian } from './provider';
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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 { atlassianAuthenticator } from '@backstage/plugin-auth-backend-module-atlassian-provider';
|
||||
import {
|
||||
SignInResolver,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
} from '../../lib/legacy';
|
||||
import { OAuthResult } from '../../lib/oauth';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import { AuthHandler } from '../types';
|
||||
|
||||
/**
|
||||
* Auth provider integration for Atlassian auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const atlassian = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: atlassianAuthenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* 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 OAuth2Strategy, { InternalOAuthError } from 'passport-oauth2';
|
||||
import { Profile } from 'passport';
|
||||
|
||||
interface ProfileResponse {
|
||||
account_id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
picture: string;
|
||||
nickname: string;
|
||||
}
|
||||
|
||||
interface AtlassianStrategyOptions {
|
||||
clientID: string;
|
||||
clientSecret: string;
|
||||
callbackURL: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
const defaultScopes = ['offline_access', 'read:me'];
|
||||
|
||||
export default class AtlassianStrategy extends OAuth2Strategy {
|
||||
private readonly profileURL: string;
|
||||
|
||||
constructor(
|
||||
options: AtlassianStrategyOptions,
|
||||
verify: OAuth2Strategy.VerifyFunction,
|
||||
) {
|
||||
if (!options.scope) {
|
||||
throw new TypeError('Atlassian requires a scope option');
|
||||
}
|
||||
|
||||
const scopes = options.scope.split(' ');
|
||||
|
||||
const optionsWithURLs = {
|
||||
...options,
|
||||
authorizationURL: `https://auth.atlassian.com/authorize`,
|
||||
tokenURL: `https://auth.atlassian.com/oauth/token`,
|
||||
scope: Array.from(new Set([...defaultScopes, ...scopes])),
|
||||
};
|
||||
|
||||
super(optionsWithURLs, verify);
|
||||
this.profileURL = 'https://api.atlassian.com/me';
|
||||
this.name = 'atlassian';
|
||||
|
||||
this._oauth2.useAuthorizationHeaderforGET(true);
|
||||
}
|
||||
|
||||
authorizationParams() {
|
||||
return {
|
||||
audience: 'api.atlassian.com',
|
||||
prompt: 'consent',
|
||||
};
|
||||
}
|
||||
|
||||
userProfile(
|
||||
accessToken: string,
|
||||
done: (err?: Error | null, profile?: any) => void,
|
||||
): void {
|
||||
this._oauth2.get(this.profileURL, accessToken, (err, body) => {
|
||||
if (err) {
|
||||
return done(
|
||||
new InternalOAuthError(
|
||||
'Failed to fetch user profile',
|
||||
err.statusCode,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!body) {
|
||||
return done(
|
||||
new Error('Failed to fetch user profile, body cannot be empty'),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const json = typeof body !== 'string' ? body.toString() : body;
|
||||
const profile = AtlassianStrategy.parse(json);
|
||||
return done(null, profile);
|
||||
} catch (e) {
|
||||
return done(new Error('Failed to parse user profile'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static parse(json: string): Profile {
|
||||
const resp = JSON.parse(json) as ProfileResponse;
|
||||
|
||||
return {
|
||||
id: resp.account_id,
|
||||
provider: 'atlassian',
|
||||
username: resp.nickname,
|
||||
displayName: resp.name,
|
||||
emails: [{ value: resp.email }],
|
||||
photos: [{ value: resp.picture }],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { auth0 } from './provider';
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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 { OAuthProviderOptions, OAuthResult } from '../../lib/oauth';
|
||||
|
||||
import { AuthHandler } from '../types';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
AuthResolverContext,
|
||||
createOAuthProviderFactory,
|
||||
SignInResolver,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
} from '../../lib/legacy';
|
||||
import { auth0Authenticator } from '@backstage/plugin-auth-backend-module-auth0-provider';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated The Auth0 auth provider was extracted to `@backstage/plugin-auth-backend-module-auth0-provider`.
|
||||
*/
|
||||
export type Auth0AuthProviderOptions = OAuthProviderOptions & {
|
||||
domain: string;
|
||||
signInResolver?: SignInResolver<OAuthResult>;
|
||||
authHandler: AuthHandler<OAuthResult>;
|
||||
resolverContext: AuthResolverContext;
|
||||
audience?: string;
|
||||
connection?: string;
|
||||
connectionScope?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Auth provider integration for auth0 auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const auth0 = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: auth0Authenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* 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 Auth0InternalStrategy from 'passport-auth0';
|
||||
import { StateStore } from 'passport-oauth2';
|
||||
|
||||
export interface Auth0StrategyOptionsWithRequest {
|
||||
clientID: string;
|
||||
clientSecret: string;
|
||||
callbackURL: string;
|
||||
domain: string;
|
||||
passReqToCallback: true;
|
||||
store: StateStore;
|
||||
}
|
||||
|
||||
export default class Auth0Strategy extends Auth0InternalStrategy {
|
||||
constructor(
|
||||
options: Auth0StrategyOptionsWithRequest,
|
||||
verify: Auth0InternalStrategy.VerifyFunction,
|
||||
) {
|
||||
const optionsWithURLs = {
|
||||
...options,
|
||||
authorizationURL: `https://${options.domain}/authorize`,
|
||||
tokenURL: `https://${options.domain}/oauth/token`,
|
||||
userInfoURL: `https://${options.domain}/userinfo`,
|
||||
apiUrl: `https://${options.domain}/api`,
|
||||
};
|
||||
super(optionsWithURLs, verify);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { awsAlb } from './provider';
|
||||
export type { AwsAlbResult } from './types';
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 {
|
||||
AwsAlbResult,
|
||||
awsAlbAuthenticator,
|
||||
} from '@backstage/plugin-auth-backend-module-aws-alb-provider';
|
||||
import {
|
||||
SignInResolver,
|
||||
createProxyAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { AuthHandler } from '../types';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
|
||||
/**
|
||||
* Auth provider integration for AWS ALB auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const awsAlb = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth
|
||||
* response into the profile that will be presented to the user. The default
|
||||
* implementation just provides the authenticated email that the IAP
|
||||
* presented.
|
||||
*/
|
||||
authHandler?: AuthHandler<AwsAlbResult>;
|
||||
/**
|
||||
* Configures sign-in for this provider.
|
||||
*/
|
||||
signIn: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<AwsAlbResult>;
|
||||
};
|
||||
}) {
|
||||
return createProxyAuthProviderFactory({
|
||||
authenticator: awsAlbAuthenticator,
|
||||
profileTransform: options?.authHandler,
|
||||
signInResolver: options?.signIn?.resolver,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 { AwsAlbResult as _AwsAlbResult } from '@backstage/plugin-auth-backend-module-aws-alb-provider';
|
||||
|
||||
/**
|
||||
* The result of the initial auth challenge. This is the input to the auth
|
||||
* callbacks.
|
||||
*
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-backend-module-aws-alb-provider` instead
|
||||
*/
|
||||
export type AwsAlbResult = _AwsAlbResult;
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { easyAuth } from './provider';
|
||||
import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated import AzureEasyAuthResult from `@backstage/plugin-auth-backend-module-azure-easyauth-provider` instead
|
||||
*/
|
||||
export type EasyAuthResult = AzureEasyAuthResult;
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
SignInResolver,
|
||||
createProxyAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { AuthHandler } from '../types';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
AzureEasyAuthResult,
|
||||
azureEasyAuthAuthenticator,
|
||||
} from '@backstage/plugin-auth-backend-module-azure-easyauth-provider';
|
||||
|
||||
/**
|
||||
* Auth provider integration for Azure EasyAuth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const easyAuth = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<AzureEasyAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<AzureEasyAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createProxyAuthProviderFactory({
|
||||
authenticator: azureEasyAuthAuthenticator,
|
||||
profileTransform: options?.authHandler,
|
||||
signInResolver: options?.signIn?.resolver,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { bitbucket } from './provider';
|
||||
export type {
|
||||
BitbucketPassportProfile,
|
||||
BitbucketOAuthResult,
|
||||
} from './provider';
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
bitbucketAuthenticator,
|
||||
bitbucketSignInResolvers,
|
||||
} from '@backstage/plugin-auth-backend-module-bitbucket-provider';
|
||||
import {
|
||||
SignInResolver,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { Profile as PassportProfile } from 'passport';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
adaptOAuthSignInResolverToLegacy,
|
||||
} from '../../lib/legacy';
|
||||
import { OAuthResult } from '../../lib/oauth';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import { AuthHandler } from '../types';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated The Bitbucket auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-provider`.
|
||||
*/
|
||||
export type BitbucketOAuthResult = {
|
||||
fullProfile: BitbucketPassportProfile;
|
||||
params: {
|
||||
id_token?: string;
|
||||
scope: string;
|
||||
expires_in: number;
|
||||
};
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated The Bitbucket auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-provider`.
|
||||
*/
|
||||
export type BitbucketPassportProfile = PassportProfile & {
|
||||
id?: string;
|
||||
displayName?: string;
|
||||
username?: string;
|
||||
avatarUrl?: string;
|
||||
_json?: {
|
||||
links?: {
|
||||
avatar?: {
|
||||
href?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Auth provider integration for Bitbucket auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const bitbucket = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: bitbucketAuthenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
resolvers: adaptOAuthSignInResolverToLegacy({
|
||||
userIdMatchingUserEntityAnnotation:
|
||||
bitbucketSignInResolvers.userIdMatchingUserEntityAnnotation(),
|
||||
usernameMatchingUserEntityAnnotation:
|
||||
bitbucketSignInResolvers.usernameMatchingUserEntityAnnotation(),
|
||||
}),
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { bitbucketServer } from './provider';
|
||||
export type { BitbucketServerOAuthResult } from './provider';
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Profile as PassportProfile } from 'passport';
|
||||
import {
|
||||
AuthResolverContext,
|
||||
createOAuthProviderFactory,
|
||||
SignInResolver,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
bitbucketServerAuthenticator,
|
||||
bitbucketServerSignInResolvers,
|
||||
} from '@backstage/plugin-auth-backend-module-bitbucket-server-provider';
|
||||
import { OAuthProviderOptions } from '../../lib/oauth';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
} from '../../lib/legacy';
|
||||
import { AuthHandler } from '../types';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated The Bitbucket Server auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-server-provider`.
|
||||
*/
|
||||
export type BitbucketServerOAuthResult = {
|
||||
fullProfile: PassportProfile;
|
||||
params: {
|
||||
scope: string;
|
||||
access_token?: string;
|
||||
token_type?: string;
|
||||
expires_in?: number;
|
||||
};
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated The Bitbucket Server auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-server-provider`.
|
||||
*/
|
||||
export type BitbucketServerAuthProviderOptions = OAuthProviderOptions & {
|
||||
host: string;
|
||||
authorizationUrl: string;
|
||||
tokenUrl: string;
|
||||
authHandler: AuthHandler<BitbucketServerOAuthResult>;
|
||||
signInResolver?: SignInResolver<BitbucketServerOAuthResult>;
|
||||
resolverContext: AuthResolverContext;
|
||||
};
|
||||
|
||||
export const bitbucketServer = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<BitbucketServerOAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<BitbucketServerOAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: bitbucketServerAuthenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
resolvers: {
|
||||
/**
|
||||
* Looks up the user by matching their email to the entity email.
|
||||
*/
|
||||
emailMatchingUserEntityProfileEmail:
|
||||
(): SignInResolver<BitbucketServerOAuthResult> => {
|
||||
const resolver =
|
||||
bitbucketServerSignInResolvers.emailMatchingUserEntityProfileEmail();
|
||||
return async (info, ctx) => {
|
||||
return resolver(
|
||||
{
|
||||
profile: info.profile,
|
||||
result: {
|
||||
fullProfile: info.result.fullProfile,
|
||||
session: {
|
||||
accessToken: info.result.accessToken,
|
||||
tokenType: info.result.params.token_type ?? 'bearer',
|
||||
scope: info.result.params.scope,
|
||||
expiresInSeconds: info.result.params.expires_in,
|
||||
refreshToken: info.result.refreshToken,
|
||||
},
|
||||
},
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
export { cfAccess } from './provider';
|
||||
export type {
|
||||
CloudflareAccessClaims,
|
||||
CloudflareAccessGroup,
|
||||
CloudflareAccessResult,
|
||||
CloudflareAccessIdentityProfile,
|
||||
} from './provider';
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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 {
|
||||
cloudflareAccessSignInResolvers,
|
||||
createCloudflareAccessAuthenticator,
|
||||
} from '@backstage/plugin-auth-backend-module-cloudflare-access-provider';
|
||||
import {
|
||||
SignInResolver,
|
||||
createProxyAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import { AuthHandler } from '../types';
|
||||
import { CacheService } from '@backstage/backend-plugin-api';
|
||||
|
||||
/**
|
||||
* CloudflareAccessClaims
|
||||
*
|
||||
* Can be used in externally provided auth handler or sign in resolver to
|
||||
* enrich user profile for sign-in user entity
|
||||
*
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-backend-module-cloudflare-access-provider` instead
|
||||
*/
|
||||
export type CloudflareAccessClaims = {
|
||||
/**
|
||||
* `aud` identifies the application to which the JWT is issued.
|
||||
*/
|
||||
aud: string[];
|
||||
/**
|
||||
* `email` contains the email address of the authenticated user.
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* iat and exp are the issuance and expiration timestamps.
|
||||
*/
|
||||
exp: number;
|
||||
iat: number;
|
||||
/**
|
||||
* `nonce` is the session identifier.
|
||||
*/
|
||||
nonce: string;
|
||||
/**
|
||||
* `identity_nonce` is available in the Application Token and can be used to
|
||||
* query all group membership for a given user.
|
||||
*/
|
||||
identity_nonce: string;
|
||||
/**
|
||||
* `sub` contains the identifier of the authenticated user.
|
||||
*/
|
||||
sub: string;
|
||||
/**
|
||||
* `iss` the issuer is the application’s Cloudflare Access Domain URL.
|
||||
*/
|
||||
iss: string;
|
||||
/**
|
||||
* `custom` contains SAML attributes in the Application Token specified by an
|
||||
* administrator in the identity provider configuration.
|
||||
*/
|
||||
custom: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CloudflareAccessGroup
|
||||
*
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-backend-module-cloudflare-access-provider` instead
|
||||
*/
|
||||
export type CloudflareAccessGroup = {
|
||||
/**
|
||||
* Group id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Name of group as defined in Cloudflare zero trust dashboard
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Access group email address
|
||||
*/
|
||||
email: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CloudflareAccessIdentityProfile
|
||||
*
|
||||
* Can be used in externally provided auth handler or sign in resolver to
|
||||
* enrich user profile for sign-in user entity
|
||||
*
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-backend-module-cloudflare-access-provider` instead
|
||||
*/
|
||||
export type CloudflareAccessIdentityProfile = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
groups: CloudflareAccessGroup[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-backend-module-cloudflare-access-provider` instead
|
||||
*/
|
||||
export type CloudflareAccessResult = {
|
||||
claims: CloudflareAccessClaims;
|
||||
cfIdentity: CloudflareAccessIdentityProfile;
|
||||
expiresInSeconds?: number;
|
||||
token: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Auth provider integration for Cloudflare Access auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const cfAccess = createAuthProviderIntegration({
|
||||
create(options: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<CloudflareAccessResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<CloudflareAccessResult>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache service object that was configured for the Backstage backend,
|
||||
* should be provided via the backend auth plugin.
|
||||
*/
|
||||
cache?: CacheService;
|
||||
}) {
|
||||
return createProxyAuthProviderFactory({
|
||||
authenticator: createCloudflareAccessAuthenticator({
|
||||
cache: options.cache,
|
||||
}),
|
||||
profileTransform: options?.authHandler,
|
||||
signInResolver: options?.signIn?.resolver,
|
||||
signInResolverFactories: cloudflareAccessSignInResolvers,
|
||||
});
|
||||
},
|
||||
resolvers: cloudflareAccessSignInResolvers,
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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 {
|
||||
AuthProviderFactory,
|
||||
SignInResolver,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
|
||||
/**
|
||||
* Creates a standardized representation of an integration with a third-party
|
||||
* auth provider.
|
||||
*
|
||||
* The returned object facilitates the creation of provider instances, and
|
||||
* supplies built-in sign-in resolvers for the specific provider.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export function createAuthProviderIntegration<
|
||||
TCreateOptions extends unknown[],
|
||||
TResolvers extends
|
||||
| {
|
||||
[name in string]: (...args: any[]) => SignInResolver<any>;
|
||||
},
|
||||
>(config: {
|
||||
create: (...args: TCreateOptions) => AuthProviderFactory;
|
||||
resolvers?: TResolvers;
|
||||
}): Readonly<{
|
||||
create: (...args: TCreateOptions) => AuthProviderFactory;
|
||||
// If no resolvers are defined, this receives the type `never`
|
||||
resolvers: Readonly<string extends keyof TResolvers ? never : TResolvers>;
|
||||
}> {
|
||||
return Object.freeze({
|
||||
...config,
|
||||
resolvers: Object.freeze(config.resolvers ?? ({} as any)),
|
||||
});
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { gcpIap } from './provider';
|
||||
export type { GcpIapResult, GcpIapTokenInfo } from './types';
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 { gcpIapAuthenticator } from '@backstage/plugin-auth-backend-module-gcp-iap-provider';
|
||||
import {
|
||||
SignInResolver,
|
||||
createProxyAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import { AuthHandler } from '../types';
|
||||
import { GcpIapResult } from './types';
|
||||
|
||||
/**
|
||||
* Auth provider integration for Google Identity-Aware Proxy auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const gcpIap = createAuthProviderIntegration({
|
||||
create(options: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth
|
||||
* response into the profile that will be presented to the user. The default
|
||||
* implementation just provides the authenticated email that the IAP
|
||||
* presented.
|
||||
*/
|
||||
authHandler?: AuthHandler<GcpIapResult>;
|
||||
|
||||
/**
|
||||
* Configures sign-in for this provider.
|
||||
*/
|
||||
signIn: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<GcpIapResult>;
|
||||
};
|
||||
}) {
|
||||
return createProxyAuthProviderFactory({
|
||||
authenticator: gcpIapAuthenticator,
|
||||
profileTransform: options?.authHandler,
|
||||
signInResolver: options?.signIn?.resolver,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 {
|
||||
GcpIapTokenInfo as _GcpIapTokenInfo,
|
||||
GcpIapResult as _GcpIapResult,
|
||||
} from '@backstage/plugin-auth-backend-module-gcp-iap-provider';
|
||||
|
||||
/**
|
||||
* The data extracted from an IAP token.
|
||||
*
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-backend-module-gcp-iap-provider` instead
|
||||
*/
|
||||
export type GcpIapTokenInfo = _GcpIapTokenInfo;
|
||||
|
||||
/**
|
||||
* The result of the initial auth challenge. This is the input to the auth
|
||||
* callbacks.
|
||||
*
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-backend-module-gcp-iap-provider` instead
|
||||
*/
|
||||
export type GcpIapResult = _GcpIapResult;
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { github } from './provider';
|
||||
export type { GithubOAuthResult } from './provider';
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* 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 { Profile as PassportProfile } from 'passport';
|
||||
import { AuthHandler, StateEncoder } from '../types';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
createOAuthProviderFactory,
|
||||
OAuthAuthenticatorResult,
|
||||
ProfileTransform,
|
||||
SignInResolver,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { githubAuthenticator } from '@backstage/plugin-auth-backend-module-github-provider';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export type GithubOAuthResult = {
|
||||
fullProfile: PassportProfile;
|
||||
params: {
|
||||
scope: string;
|
||||
expires_in?: string;
|
||||
refresh_token_expires_in?: string;
|
||||
};
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Auth provider integration for GitHub auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const github = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<GithubOAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<GithubOAuthResult>;
|
||||
};
|
||||
|
||||
/**
|
||||
* The state encoder used to encode the 'state' parameter on the OAuth request.
|
||||
*
|
||||
* It should return a string that takes the state params (from the request), url encodes the params
|
||||
* and finally base64 encodes them.
|
||||
*
|
||||
* Providing your own stateEncoder will allow you to add addition parameters to the state field.
|
||||
*
|
||||
* It is typed as follows:
|
||||
* `export type StateEncoder = (input: OAuthState) => Promise<{encodedState: string}>;`
|
||||
*
|
||||
* Note: the stateEncoder must encode a 'nonce' value and an 'env' value. Without this, the OAuth flow will fail
|
||||
* (These two values will be set by the req.state by default)
|
||||
*
|
||||
* For more information, please see the helper module in ../../oauth/helpers #readState
|
||||
*/
|
||||
stateEncoder?: StateEncoder;
|
||||
}) {
|
||||
const authHandler = options?.authHandler;
|
||||
const signInResolver = options?.signIn?.resolver;
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: githubAuthenticator,
|
||||
profileTransform:
|
||||
authHandler &&
|
||||
((async (result, ctx) =>
|
||||
authHandler!(
|
||||
{
|
||||
fullProfile: result.fullProfile,
|
||||
accessToken: result.session.accessToken,
|
||||
params: {
|
||||
scope: result.session.scope,
|
||||
expires_in: result.session.expiresInSeconds
|
||||
? String(result.session.expiresInSeconds)
|
||||
: '',
|
||||
refresh_token_expires_in: result.session
|
||||
.refreshTokenExpiresInSeconds
|
||||
? String(result.session.refreshTokenExpiresInSeconds)
|
||||
: '',
|
||||
},
|
||||
},
|
||||
ctx,
|
||||
)) as ProfileTransform<OAuthAuthenticatorResult<PassportProfile>>),
|
||||
signInResolver:
|
||||
signInResolver &&
|
||||
((async ({ profile, result }, ctx) =>
|
||||
signInResolver(
|
||||
{
|
||||
profile: profile,
|
||||
result: {
|
||||
fullProfile: result.fullProfile,
|
||||
accessToken: result.session.accessToken,
|
||||
refreshToken: result.session.refreshToken,
|
||||
params: {
|
||||
scope: result.session.scope,
|
||||
expires_in: result.session.expiresInSeconds
|
||||
? String(result.session.expiresInSeconds)
|
||||
: '',
|
||||
refresh_token_expires_in: result.session
|
||||
.refreshTokenExpiresInSeconds
|
||||
? String(result.session.refreshTokenExpiresInSeconds)
|
||||
: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
ctx,
|
||||
)) as SignInResolver<OAuthAuthenticatorResult<PassportProfile>>),
|
||||
});
|
||||
},
|
||||
resolvers: {
|
||||
/**
|
||||
* Looks up the user by matching their GitHub username to the entity name.
|
||||
*/
|
||||
usernameMatchingUserEntityName: (): SignInResolver<GithubOAuthResult> => {
|
||||
return async (info, ctx) => {
|
||||
const { fullProfile } = info.result;
|
||||
|
||||
const userId = fullProfile.username;
|
||||
if (!userId) {
|
||||
throw new Error(`GitHub user profile does not contain a username`);
|
||||
}
|
||||
|
||||
return ctx.signInWithCatalogUser({ entityRef: { name: userId } });
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { gitlab } from './provider';
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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 { AuthHandler } from '../types';
|
||||
import { OAuthResult } from '../../lib/oauth';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
SignInResolver,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
} from '../../lib/legacy';
|
||||
import { gitlabAuthenticator } from '@backstage/plugin-auth-backend-module-gitlab-provider';
|
||||
|
||||
/**
|
||||
* Auth provider integration for GitLab auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const gitlab = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: gitlabAuthenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { google } from './provider';
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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 { googleAuthenticator } from '@backstage/plugin-auth-backend-module-google-provider';
|
||||
import { createOAuthProviderFactory } from '@backstage/plugin-auth-node';
|
||||
import { google } from './provider';
|
||||
|
||||
jest.mock('@backstage/plugin-auth-node', () => ({
|
||||
...jest.requireActual('@backstage/plugin-auth-node'),
|
||||
createOAuthProviderFactory: jest.fn(() => 'provider-factory'),
|
||||
}));
|
||||
|
||||
describe('createGoogleProvider', () => {
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
it('should be created', async () => {
|
||||
expect(google.create()).toBe('provider-factory');
|
||||
|
||||
expect(createOAuthProviderFactory).toHaveBeenCalledWith({
|
||||
authenticator: googleAuthenticator,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created with sign-in resolver', async () => {
|
||||
expect(google.create({ signIn: { resolver: jest.fn() } })).toBe(
|
||||
'provider-factory',
|
||||
);
|
||||
|
||||
expect(createOAuthProviderFactory).toHaveBeenCalledWith({
|
||||
authenticator: googleAuthenticator,
|
||||
signInResolver: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created with sign-in resolver and auth handler', async () => {
|
||||
expect(
|
||||
google.create({
|
||||
signIn: { resolver: jest.fn() },
|
||||
authHandler: jest.fn(),
|
||||
}),
|
||||
).toBe('provider-factory');
|
||||
|
||||
expect(createOAuthProviderFactory).toHaveBeenCalledWith({
|
||||
authenticator: googleAuthenticator,
|
||||
signInResolver: expect.any(Function),
|
||||
profileTransform: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('should have resolvers', () => {
|
||||
expect(google.resolvers).toEqual({
|
||||
emailLocalPartMatchingUserEntityName: expect.any(Function),
|
||||
emailMatchingUserEntityAnnotation: expect.any(Function),
|
||||
emailMatchingUserEntityProfileEmail: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
googleAuthenticator,
|
||||
googleSignInResolvers,
|
||||
} from '@backstage/plugin-auth-backend-module-google-provider';
|
||||
import {
|
||||
SignInResolver,
|
||||
commonSignInResolvers,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
adaptOAuthSignInResolverToLegacy,
|
||||
} from '../../lib/legacy';
|
||||
import { OAuthResult } from '../../lib/oauth';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import { AuthHandler } from '../types';
|
||||
|
||||
/**
|
||||
* Auth provider integration for Google auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const google = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: googleAuthenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
resolvers: adaptOAuthSignInResolverToLegacy({
|
||||
emailLocalPartMatchingUserEntityName:
|
||||
commonSignInResolvers.emailLocalPartMatchingUserEntityName(),
|
||||
emailMatchingUserEntityProfileEmail:
|
||||
commonSignInResolvers.emailMatchingUserEntityProfileEmail(),
|
||||
emailMatchingUserEntityAnnotation:
|
||||
googleSignInResolvers.emailMatchingUserEntityAnnotation(),
|
||||
}),
|
||||
});
|
||||
@@ -14,30 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type { AwsAlbResult } from './aws-alb';
|
||||
export type { EasyAuthResult } from './azure-easyauth';
|
||||
export type {
|
||||
BitbucketOAuthResult,
|
||||
BitbucketPassportProfile,
|
||||
} from './bitbucket';
|
||||
export type { BitbucketServerOAuthResult } from './bitbucketServer';
|
||||
export type {
|
||||
CloudflareAccessClaims,
|
||||
CloudflareAccessGroup,
|
||||
CloudflareAccessResult,
|
||||
CloudflareAccessIdentityProfile,
|
||||
} from './cloudflare-access';
|
||||
export type { GithubOAuthResult } from './github';
|
||||
export type { OAuth2ProxyResult } from './oauth2-proxy';
|
||||
export type { OidcAuthResult } from './oidc';
|
||||
export type { SamlAuthResult } from './saml';
|
||||
export type { GcpIapResult, GcpIapTokenInfo } from './gcp-iap';
|
||||
|
||||
export { providers, defaultAuthProviderFactories } from './providers';
|
||||
export { createOriginFilter, type ProviderFactories } from './router';
|
||||
|
||||
export { createAuthProviderIntegration } from './createAuthProviderIntegration';
|
||||
|
||||
export type {
|
||||
AuthProviderConfig,
|
||||
AuthProviderRouteHandlers,
|
||||
@@ -54,5 +32,3 @@ export type {
|
||||
ProfileInfo,
|
||||
OAuthStartResponse,
|
||||
} from './types';
|
||||
|
||||
export { prepareBackstageIdentityResponse } from './prepareBackstageIdentityResponse';
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { microsoft } from './provider';
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* 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 { AuthHandler } from '../types';
|
||||
import { OAuthResult } from '../../lib/oauth';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
SignInResolver,
|
||||
commonSignInResolvers,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
adaptOAuthSignInResolverToLegacy,
|
||||
} from '../../lib/legacy';
|
||||
import {
|
||||
microsoftAuthenticator,
|
||||
microsoftSignInResolvers,
|
||||
} from '@backstage/plugin-auth-backend-module-microsoft-provider';
|
||||
|
||||
/**
|
||||
* Auth provider integration for Microsoft auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const microsoft = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: microsoftAuthenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
resolvers: adaptOAuthSignInResolverToLegacy({
|
||||
emailLocalPartMatchingUserEntityName:
|
||||
commonSignInResolvers.emailLocalPartMatchingUserEntityName(),
|
||||
emailMatchingUserEntityProfileEmail:
|
||||
commonSignInResolvers.emailMatchingUserEntityProfileEmail(),
|
||||
emailMatchingUserEntityAnnotation:
|
||||
microsoftSignInResolvers.emailMatchingUserEntityAnnotation(),
|
||||
userIdMatchingUserEntityAnnotation:
|
||||
microsoftSignInResolvers.userIdMatchingUserEntityAnnotation(),
|
||||
}),
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { oauth2Proxy } from './provider';
|
||||
import { OAuth2ProxyResult as _OAuth2ProxyResult } from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-backend-module-oauth2-proxy-provider` instead
|
||||
*/
|
||||
export type OAuth2ProxyResult = _OAuth2ProxyResult;
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 {
|
||||
SignInResolver,
|
||||
createProxyAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { AuthHandler } from '../types';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
type OAuth2ProxyResult,
|
||||
oauth2ProxyAuthenticator,
|
||||
} from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider';
|
||||
|
||||
/**
|
||||
* Auth provider integration for oauth2-proxy auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const oauth2Proxy = createAuthProviderIntegration({
|
||||
create(options: {
|
||||
/**
|
||||
* Configure an auth handler to generate a profile for the user.
|
||||
*
|
||||
* The default implementation uses the value of the `X-Forwarded-Preferred-Username`
|
||||
* header as the display name, falling back to `X-Forwarded-User`, and the value of
|
||||
* the `X-Forwarded-Email` header as the email address.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuth2ProxyResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<OAuth2ProxyResult>;
|
||||
};
|
||||
}) {
|
||||
return createProxyAuthProviderFactory({
|
||||
authenticator: oauth2ProxyAuthenticator,
|
||||
profileTransform: options?.authHandler,
|
||||
signInResolver: options?.signIn?.resolver,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { oauth2 } from './provider';
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 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 { OAuthResult } from '../../lib/oauth';
|
||||
import { AuthHandler } from '../types';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
} from '../../lib/legacy';
|
||||
import {
|
||||
SignInResolver,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import { oauth2Authenticator } from '@backstage/plugin-auth-backend-module-oauth2-provider';
|
||||
|
||||
/**
|
||||
* Auth provider integration for generic OAuth2 auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const oauth2 = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: oauth2Authenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { oidc } from './provider';
|
||||
|
||||
import { OidcAuthResult as OidcAuthResult_ } from '@backstage/plugin-auth-backend-module-oidc-provider';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Use OidcAuthResult from `@backstage/plugin-auth-backend-module-oidc-provider` instead
|
||||
*/
|
||||
export type OidcAuthResult = OidcAuthResult_;
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
mockServices,
|
||||
registerMswTestHooks,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { Config, ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
AuthProviderConfig,
|
||||
AuthResolverContext,
|
||||
CookieConfigurer,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import express from 'express';
|
||||
import { JWK, SignJWT, exportJWK, generateKeyPair } from 'jose';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { oidc } from './provider';
|
||||
|
||||
describe('oidc.create', () => {
|
||||
const userinfo = {
|
||||
sub: 'test',
|
||||
iss: 'https://oidc.test',
|
||||
aud: 'clientId',
|
||||
nonce: 'foo',
|
||||
};
|
||||
const server = setupServer();
|
||||
registerMswTestHooks(server);
|
||||
|
||||
let publicKey: JWK;
|
||||
let tokenset: object;
|
||||
let providerFactoryOptions: {
|
||||
providerId: string;
|
||||
globalConfig: AuthProviderConfig;
|
||||
config: Config;
|
||||
logger: LoggerService;
|
||||
resolverContext: AuthResolverContext;
|
||||
baseUrl: string;
|
||||
appUrl: string;
|
||||
isOriginAllowed: (origin: string) => boolean;
|
||||
cookieConfigurer?: CookieConfigurer;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const keyPair = await generateKeyPair('RS256');
|
||||
const privateKey = await exportJWK(keyPair.privateKey);
|
||||
publicKey = await exportJWK(keyPair.publicKey);
|
||||
publicKey.alg = privateKey.alg = 'RS256';
|
||||
|
||||
tokenset = {
|
||||
id_token: await new SignJWT({
|
||||
iat: Date.now(),
|
||||
exp: Date.now() + 10000,
|
||||
...userinfo,
|
||||
})
|
||||
.setProtectedHeader({ alg: privateKey.alg, kid: privateKey.kid })
|
||||
.sign(keyPair.privateKey),
|
||||
access_token: 'accessToken',
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get(
|
||||
'https://oidc.test/.well-known/openid-configuration',
|
||||
(_req, res, ctx) =>
|
||||
res(
|
||||
ctx.json({
|
||||
issuer: 'https://oidc.test',
|
||||
token_endpoint: 'https://oidc.test/oauth2/token',
|
||||
userinfo_endpoint: 'https://oidc.test/idp/userinfo.openid',
|
||||
jwks_uri: 'https://oidc.test/jwks.json',
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.post('https://oidc.test/oauth2/token', (_req, res, ctx) =>
|
||||
res(ctx.json(tokenset)),
|
||||
),
|
||||
rest.get('https://oidc.test/jwks.json', async (_req, res, ctx) =>
|
||||
res(ctx.json({ keys: [{ ...publicKey }] })),
|
||||
),
|
||||
rest.get(
|
||||
'https://oidc.test/idp/userinfo.openid',
|
||||
async (_req, res, ctx) => res(ctx.json(userinfo)),
|
||||
),
|
||||
);
|
||||
providerFactoryOptions = {
|
||||
providerId: 'myoidc',
|
||||
baseUrl: 'http://backstage.test/api/auth',
|
||||
appUrl: 'http://backstage.test',
|
||||
isOriginAllowed: _ => true,
|
||||
globalConfig: {
|
||||
baseUrl: 'http://backstage.test/api/auth',
|
||||
appUrl: 'http://backstage.test',
|
||||
isOriginAllowed: _ => true,
|
||||
},
|
||||
config: new ConfigReader({
|
||||
development: {
|
||||
metadataUrl: 'https://oidc.test/.well-known/openid-configuration',
|
||||
clientId: 'clientId',
|
||||
clientSecret: 'clientSecret',
|
||||
},
|
||||
}),
|
||||
logger: mockServices.logger.mock(),
|
||||
resolverContext: {
|
||||
issueToken: jest.fn(),
|
||||
findCatalogUser: jest.fn(),
|
||||
signInWithCatalogUser: jest.fn(),
|
||||
resolveOwnershipEntityRefs: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('invokes authHandler with tokenset and userinfo response', async () => {
|
||||
const authHandler = jest.fn();
|
||||
const provider = oidc.create({ authHandler })(providerFactoryOptions);
|
||||
const state = Buffer.from('nonce=foo&env=development').toString('hex');
|
||||
|
||||
await provider.frameHandler(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `http://backstage.test/api/auth/myoidc/handler/frame?code=blahblah&state=${state}`,
|
||||
query: { state },
|
||||
cookies: { 'myoidc-nonce': 'foo' },
|
||||
session: { 'oidc:oidc.test': { state, nonce: 'foo' } },
|
||||
} as unknown as express.Request,
|
||||
{ setHeader: jest.fn(), end: jest.fn() } as unknown as express.Response,
|
||||
);
|
||||
|
||||
expect(authHandler).toHaveBeenCalledWith(
|
||||
{ tokenset, userinfo },
|
||||
providerFactoryOptions.resolverContext,
|
||||
);
|
||||
});
|
||||
|
||||
it('invokes sign-in resolver with tokenset and userinfo response', async () => {
|
||||
const resolver = jest.fn();
|
||||
const provider = oidc.create({ signIn: { resolver } })(
|
||||
providerFactoryOptions,
|
||||
);
|
||||
const state = Buffer.from('nonce=foo&env=development').toString('hex');
|
||||
|
||||
await provider.frameHandler(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `http://backstage.test/api/auth/myoidc/handler/frame?code=blahblah&state=${state}`,
|
||||
query: { state },
|
||||
cookies: { 'myoidc-nonce': 'foo' },
|
||||
session: { 'oidc:oidc.test': { state, nonce: 'foo' } },
|
||||
} as unknown as express.Request,
|
||||
{ setHeader: jest.fn(), end: jest.fn() } as unknown as express.Response,
|
||||
);
|
||||
|
||||
expect(resolver).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ result: { tokenset, userinfo } }),
|
||||
providerFactoryOptions.resolverContext,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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 { AuthHandler } from '../types';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
createOAuthProviderFactory,
|
||||
AuthResolverContext,
|
||||
BackstageSignInResult,
|
||||
OAuthAuthenticatorResult,
|
||||
SignInInfo,
|
||||
SignInResolver,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
oidcAuthenticator,
|
||||
OidcAuthResult,
|
||||
} from '@backstage/plugin-auth-backend-module-oidc-provider';
|
||||
import {
|
||||
commonByEmailLocalPartResolver,
|
||||
commonByEmailResolver,
|
||||
} from '../resolvers';
|
||||
|
||||
/**
|
||||
* Auth provider integration for generic OpenID Connect auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const oidc = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OidcAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider; convert user profile respones into
|
||||
* Backstage identities.
|
||||
*/
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OidcAuthResult>;
|
||||
};
|
||||
}) {
|
||||
const authHandler = options?.authHandler;
|
||||
const signInResolver = options?.signIn?.resolver;
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: oidcAuthenticator,
|
||||
profileTransform:
|
||||
authHandler &&
|
||||
((
|
||||
result: OAuthAuthenticatorResult<OidcAuthResult>,
|
||||
context: AuthResolverContext,
|
||||
) => authHandler(result.fullProfile, context)),
|
||||
signInResolver:
|
||||
signInResolver &&
|
||||
((
|
||||
info: SignInInfo<OAuthAuthenticatorResult<OidcAuthResult>>,
|
||||
context: AuthResolverContext,
|
||||
): Promise<BackstageSignInResult> =>
|
||||
signInResolver(
|
||||
{
|
||||
result: info.result.fullProfile,
|
||||
profile: info.profile,
|
||||
},
|
||||
context,
|
||||
)),
|
||||
});
|
||||
},
|
||||
resolvers: {
|
||||
/**
|
||||
* Looks up the user by matching their email local part to the entity name.
|
||||
*/
|
||||
emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver,
|
||||
/**
|
||||
* Looks up the user by matching their email to the entity email.
|
||||
*/
|
||||
emailMatchingUserEntityProfileEmail: () => commonByEmailResolver,
|
||||
},
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { okta } from './provider';
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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 { AuthHandler } from '../types';
|
||||
import { OAuthResult } from '../../lib/oauth';
|
||||
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import {
|
||||
SignInResolver,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
} from '../../lib/legacy';
|
||||
import { oktaAuthenticator } from '@backstage/plugin-auth-backend-module-okta-provider';
|
||||
import {
|
||||
commonByEmailLocalPartResolver,
|
||||
commonByEmailResolver,
|
||||
} from '../resolvers';
|
||||
|
||||
/**
|
||||
* Auth provider integration for Okta auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const okta = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: oktaAuthenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
resolvers: {
|
||||
/**
|
||||
* Looks up the user by matching their email local part to the entity name.
|
||||
*/
|
||||
emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver,
|
||||
/**
|
||||
* Looks up the user by matching their email to the entity email.
|
||||
*/
|
||||
emailMatchingUserEntityProfileEmail: () => commonByEmailResolver,
|
||||
/**
|
||||
* Looks up the user by matching their email to the `okta.com/email` annotation.
|
||||
*/
|
||||
emailMatchingUserEntityAnnotation(): SignInResolver<OAuthResult> {
|
||||
return async (info, ctx) => {
|
||||
const { profile } = info;
|
||||
|
||||
if (!profile.email) {
|
||||
throw new Error('Okta profile contained no email');
|
||||
}
|
||||
|
||||
return ctx.signInWithCatalogUser({
|
||||
annotations: {
|
||||
'okta.com/email': profile.email,
|
||||
},
|
||||
});
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { onelogin } from './provider';
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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 { oneLoginAuthenticator } from '@backstage/plugin-auth-backend-module-onelogin-provider';
|
||||
import {
|
||||
SignInResolver,
|
||||
createOAuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
adaptLegacyOAuthHandler,
|
||||
adaptLegacyOAuthSignInResolver,
|
||||
} from '../../lib/legacy';
|
||||
import { OAuthResult } from '../../lib/oauth';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import { AuthHandler } from '../types';
|
||||
|
||||
/**
|
||||
* Auth provider integration for OneLogin auth
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const onelogin = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return createOAuthProviderFactory({
|
||||
authenticator: oneLoginAuthenticator,
|
||||
profileTransform: adaptLegacyOAuthHandler(options?.authHandler),
|
||||
signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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 { prepareBackstageIdentityResponse as _prepareBackstageIdentityResponse } from '@backstage/plugin-auth-node';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-auth-node` instead
|
||||
*/
|
||||
export const prepareBackstageIdentityResponse =
|
||||
_prepareBackstageIdentityResponse;
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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 { atlassian } from './atlassian';
|
||||
import { auth0 } from './auth0';
|
||||
import { awsAlb } from './aws-alb';
|
||||
import { bitbucket } from './bitbucket';
|
||||
import { cfAccess } from './cloudflare-access';
|
||||
import { gcpIap } from './gcp-iap';
|
||||
import { github } from './github';
|
||||
import { gitlab } from './gitlab';
|
||||
import { google } from './google';
|
||||
import { microsoft } from './microsoft';
|
||||
import { oauth2 } from './oauth2';
|
||||
import { oauth2Proxy } from './oauth2-proxy';
|
||||
import { oidc } from './oidc';
|
||||
import { okta } from './okta';
|
||||
import { onelogin } from './onelogin';
|
||||
import { saml } from './saml';
|
||||
import { bitbucketServer } from './bitbucketServer';
|
||||
import { easyAuth } from './azure-easyauth';
|
||||
import { AuthProviderFactory } from '@backstage/plugin-auth-node';
|
||||
|
||||
/**
|
||||
* All built-in auth provider integrations.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const providers = Object.freeze({
|
||||
atlassian,
|
||||
auth0,
|
||||
awsAlb,
|
||||
bitbucket,
|
||||
bitbucketServer,
|
||||
cfAccess,
|
||||
gcpIap,
|
||||
github,
|
||||
gitlab,
|
||||
google,
|
||||
microsoft,
|
||||
oauth2,
|
||||
oauth2Proxy,
|
||||
oidc,
|
||||
okta,
|
||||
onelogin,
|
||||
saml,
|
||||
easyAuth,
|
||||
});
|
||||
|
||||
/**
|
||||
* All auth provider factories that are installed by default.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export const defaultAuthProviderFactories: {
|
||||
[providerId: string]: AuthProviderFactory;
|
||||
} = {
|
||||
google: google.create(),
|
||||
github: github.create(),
|
||||
gitlab: gitlab.create(),
|
||||
saml: saml.create(),
|
||||
okta: okta.create(),
|
||||
auth0: auth0.create(),
|
||||
microsoft: microsoft.create(),
|
||||
easyAuth: easyAuth.create(),
|
||||
oauth2: oauth2.create(),
|
||||
oidc: oidc.create(),
|
||||
onelogin: onelogin.create(),
|
||||
awsalb: awsAlb.create(),
|
||||
bitbucket: bitbucket.create(),
|
||||
bitbucketServer: bitbucketServer.create(),
|
||||
atlassian: atlassian.create(),
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { saml } from './provider';
|
||||
export type { SamlAuthResult } from './provider';
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* 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 express from 'express';
|
||||
import { SamlConfig, VerifiedCallback } from '@node-saml/passport-saml';
|
||||
import {
|
||||
Strategy as SamlStrategy,
|
||||
Profile as SamlProfile,
|
||||
VerifyWithoutRequest,
|
||||
} from '@node-saml/passport-saml';
|
||||
import {
|
||||
executeFrameHandlerStrategy,
|
||||
executeRedirectStrategy,
|
||||
} from '../../lib/passport';
|
||||
import { AuthHandler } from '../types';
|
||||
import { postMessageResponse } from '../../lib/flow';
|
||||
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
|
||||
import { AuthenticationError, isError } from '@backstage/errors';
|
||||
import { prepareBackstageIdentityResponse } from '../prepareBackstageIdentityResponse';
|
||||
import {
|
||||
AuthProviderRouteHandlers,
|
||||
AuthResolverContext,
|
||||
ClientAuthResponse,
|
||||
SignInResolver,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Migrate the auth plugin to the new backend system https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
|
||||
*/
|
||||
export type SamlAuthResult = {
|
||||
fullProfile: any;
|
||||
};
|
||||
|
||||
type Options = SamlConfig & {
|
||||
signInResolver?: SignInResolver<SamlAuthResult>;
|
||||
authHandler: AuthHandler<SamlAuthResult>;
|
||||
resolverContext: AuthResolverContext;
|
||||
appUrl: string;
|
||||
};
|
||||
|
||||
export class SamlAuthProvider implements AuthProviderRouteHandlers {
|
||||
private readonly strategy: SamlStrategy;
|
||||
private readonly signInResolver?: SignInResolver<SamlAuthResult>;
|
||||
private readonly authHandler: AuthHandler<SamlAuthResult>;
|
||||
private readonly resolverContext: AuthResolverContext;
|
||||
private readonly appUrl: string;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.appUrl = options.appUrl;
|
||||
this.signInResolver = options.signInResolver;
|
||||
this.authHandler = options.authHandler;
|
||||
this.resolverContext = options.resolverContext;
|
||||
|
||||
const verifier: VerifyWithoutRequest = (
|
||||
profile: SamlProfile | null,
|
||||
done: VerifiedCallback,
|
||||
) => {
|
||||
// TODO: There's plenty more validation and profile handling to do here,
|
||||
// this provider is currently only intended to validate the provider pattern
|
||||
// for non-oauth auth flows.
|
||||
// TODO: This flow doesn't issue an identity token that can be used to validate
|
||||
// the identity of the user in other backends, which we need in some form.
|
||||
done(null, { fullProfile: profile });
|
||||
};
|
||||
this.strategy = new SamlStrategy(options, verifier, verifier);
|
||||
}
|
||||
|
||||
async start(req: express.Request, res: express.Response): Promise<void> {
|
||||
const { url } = await executeRedirectStrategy(req, this.strategy, {});
|
||||
res.redirect(url);
|
||||
}
|
||||
|
||||
async frameHandler(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { result } = await executeFrameHandlerStrategy<SamlAuthResult>(
|
||||
req,
|
||||
this.strategy,
|
||||
);
|
||||
|
||||
const { profile } = await this.authHandler(result, this.resolverContext);
|
||||
|
||||
const response: ClientAuthResponse<{}> = {
|
||||
profile,
|
||||
providerInfo: {},
|
||||
};
|
||||
|
||||
if (this.signInResolver) {
|
||||
const signInResponse = await this.signInResolver(
|
||||
{
|
||||
result,
|
||||
profile,
|
||||
},
|
||||
this.resolverContext,
|
||||
);
|
||||
|
||||
response.backstageIdentity =
|
||||
prepareBackstageIdentityResponse(signInResponse);
|
||||
}
|
||||
|
||||
return postMessageResponse(res, this.appUrl, {
|
||||
type: 'authorization_response',
|
||||
response,
|
||||
});
|
||||
} catch (error) {
|
||||
const { name, message } = isError(error)
|
||||
? error
|
||||
: new Error('Encountered invalid error'); // Being a bit safe and not forwarding the bad value
|
||||
return postMessageResponse(res, this.appUrl, {
|
||||
type: 'authorization_response',
|
||||
error: { name, message },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async logout(_req: express.Request, res: express.Response): Promise<void> {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
type SignatureAlgorithm = 'sha1' | 'sha256' | 'sha512';
|
||||
|
||||
/**
|
||||
* Auth provider integration for SAML auth
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const saml = createAuthProviderIntegration({
|
||||
create(options?: {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<SamlAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<SamlAuthResult>;
|
||||
};
|
||||
}) {
|
||||
return ({ providerId, globalConfig, config, resolverContext }) => {
|
||||
const authHandler: AuthHandler<SamlAuthResult> = options?.authHandler
|
||||
? options.authHandler
|
||||
: async ({ fullProfile }) => ({
|
||||
profile: {
|
||||
email: fullProfile.email,
|
||||
displayName: fullProfile.displayName,
|
||||
},
|
||||
});
|
||||
|
||||
return new SamlAuthProvider({
|
||||
callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
|
||||
entryPoint: config.getString('entryPoint'),
|
||||
logoutUrl: config.getOptionalString('logoutUrl'),
|
||||
audience: config.getString('audience'),
|
||||
issuer: config.getString('issuer'),
|
||||
idpCert: config.getString('cert'),
|
||||
privateKey: config.getOptionalString('privateKey'),
|
||||
authnContext: config.getOptionalStringArray('authnContext'),
|
||||
identifierFormat: config.getOptionalString('identifierFormat'),
|
||||
decryptionPvk: config.getOptionalString('decryptionPvk'),
|
||||
signatureAlgorithm: config.getOptionalString('signatureAlgorithm') as
|
||||
| SignatureAlgorithm
|
||||
| undefined,
|
||||
digestAlgorithm: config.getOptionalString('digestAlgorithm'),
|
||||
acceptedClockSkewMs: config.getOptionalNumber('acceptedClockSkewMs'),
|
||||
wantAuthnResponseSigned: config.getOptionalBoolean(
|
||||
'wantAuthnResponseSigned',
|
||||
),
|
||||
wantAssertionsSigned: config.getOptionalBoolean('wantAssertionsSigned'),
|
||||
appUrl: globalConfig.appUrl,
|
||||
authHandler,
|
||||
signInResolver: options?.signIn?.resolver,
|
||||
resolverContext,
|
||||
});
|
||||
};
|
||||
},
|
||||
resolvers: {
|
||||
/**
|
||||
* Looks up the user by matching their nameID to the entity name.
|
||||
*/
|
||||
nameIdMatchingUserEntityName(): SignInResolver<SamlAuthResult> {
|
||||
return async (info, ctx) => {
|
||||
const id = info.result.fullProfile.nameID;
|
||||
|
||||
if (!id) {
|
||||
throw new AuthenticationError('No nameID found in SAML response');
|
||||
}
|
||||
|
||||
return ctx.signInWithCatalogUser({
|
||||
entityRef: { name: id },
|
||||
});
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { defaultAuthProviderFactories } from '../providers';
|
||||
import { AuthOwnershipResolver } from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
TokenManager,
|
||||
@@ -49,10 +48,6 @@ import { StaticTokenIssuer } from '../identity/StaticTokenIssuer';
|
||||
import { StaticKeyStore } from '../identity/StaticKeyStore';
|
||||
import { bindProviderRouters, ProviderFactories } from '../providers/router';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please migrate to the new backend system as this will be removed in the future.
|
||||
*/
|
||||
export interface RouterOptions {
|
||||
logger: LoggerService;
|
||||
database: DatabaseService;
|
||||
@@ -63,15 +58,10 @@ export interface RouterOptions {
|
||||
httpAuth?: HttpAuthService;
|
||||
tokenFactoryAlgorithm?: string;
|
||||
providerFactories?: ProviderFactories;
|
||||
disableDefaultProviderFactories?: boolean;
|
||||
catalogApi?: CatalogApi;
|
||||
ownershipResolver?: AuthOwnershipResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please migrate to the new backend system as this will be removed in the future.
|
||||
*/
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
@@ -151,15 +141,8 @@ export async function createRouter(
|
||||
router.use(express.urlencoded({ extended: false }));
|
||||
router.use(express.json());
|
||||
|
||||
const providers = options.disableDefaultProviderFactories
|
||||
? providerFactories
|
||||
: {
|
||||
...defaultAuthProviderFactories,
|
||||
...providerFactories,
|
||||
};
|
||||
|
||||
bindProviderRouters(router, {
|
||||
providers,
|
||||
providers: providerFactories,
|
||||
appUrl,
|
||||
baseUrl: authUrl,
|
||||
tokenIssuer,
|
||||
|
||||
@@ -4902,7 +4902,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-atlassian-provider@workspace:^, @backstage/plugin-auth-backend-module-atlassian-provider@workspace:plugins/auth-backend-module-atlassian-provider":
|
||||
"@backstage/plugin-auth-backend-module-atlassian-provider@workspace:plugins/auth-backend-module-atlassian-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-atlassian-provider@workspace:plugins/auth-backend-module-atlassian-provider"
|
||||
dependencies:
|
||||
@@ -4920,7 +4920,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-auth0-provider@workspace:^, @backstage/plugin-auth-backend-module-auth0-provider@workspace:plugins/auth-backend-module-auth0-provider":
|
||||
"@backstage/plugin-auth-backend-module-auth0-provider@workspace:plugins/auth-backend-module-auth0-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-auth0-provider@workspace:plugins/auth-backend-module-auth0-provider"
|
||||
dependencies:
|
||||
@@ -4940,7 +4940,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-aws-alb-provider@workspace:^, @backstage/plugin-auth-backend-module-aws-alb-provider@workspace:plugins/auth-backend-module-aws-alb-provider":
|
||||
"@backstage/plugin-auth-backend-module-aws-alb-provider@workspace:plugins/auth-backend-module-aws-alb-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-aws-alb-provider@workspace:plugins/auth-backend-module-aws-alb-provider"
|
||||
dependencies:
|
||||
@@ -4959,7 +4959,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:^, @backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:plugins/auth-backend-module-azure-easyauth-provider":
|
||||
"@backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:plugins/auth-backend-module-azure-easyauth-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-azure-easyauth-provider@workspace:plugins/auth-backend-module-azure-easyauth-provider"
|
||||
dependencies:
|
||||
@@ -4977,7 +4977,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-provider@workspace:^, @backstage/plugin-auth-backend-module-bitbucket-provider@workspace:plugins/auth-backend-module-bitbucket-provider":
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-provider@workspace:plugins/auth-backend-module-bitbucket-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-bitbucket-provider@workspace:plugins/auth-backend-module-bitbucket-provider"
|
||||
dependencies:
|
||||
@@ -4995,7 +4995,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-server-provider@workspace:^, @backstage/plugin-auth-backend-module-bitbucket-server-provider@workspace:plugins/auth-backend-module-bitbucket-server-provider":
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-server-provider@workspace:plugins/auth-backend-module-bitbucket-server-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-bitbucket-server-provider@workspace:plugins/auth-backend-module-bitbucket-server-provider"
|
||||
dependencies:
|
||||
@@ -5013,7 +5013,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:^, @backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:plugins/auth-backend-module-cloudflare-access-provider":
|
||||
"@backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:plugins/auth-backend-module-cloudflare-access-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-cloudflare-access-provider@workspace:plugins/auth-backend-module-cloudflare-access-provider"
|
||||
dependencies:
|
||||
@@ -5034,7 +5034,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-gcp-iap-provider@workspace:^, @backstage/plugin-auth-backend-module-gcp-iap-provider@workspace:plugins/auth-backend-module-gcp-iap-provider":
|
||||
"@backstage/plugin-auth-backend-module-gcp-iap-provider@workspace:plugins/auth-backend-module-gcp-iap-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-gcp-iap-provider@workspace:plugins/auth-backend-module-gcp-iap-provider"
|
||||
dependencies:
|
||||
@@ -5065,7 +5065,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-gitlab-provider@workspace:^, @backstage/plugin-auth-backend-module-gitlab-provider@workspace:plugins/auth-backend-module-gitlab-provider":
|
||||
"@backstage/plugin-auth-backend-module-gitlab-provider@workspace:plugins/auth-backend-module-gitlab-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-gitlab-provider@workspace:plugins/auth-backend-module-gitlab-provider"
|
||||
dependencies:
|
||||
@@ -5083,7 +5083,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-google-provider@workspace:^, @backstage/plugin-auth-backend-module-google-provider@workspace:plugins/auth-backend-module-google-provider":
|
||||
"@backstage/plugin-auth-backend-module-google-provider@workspace:plugins/auth-backend-module-google-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-google-provider@workspace:plugins/auth-backend-module-google-provider"
|
||||
dependencies:
|
||||
@@ -5116,7 +5116,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-microsoft-provider@workspace:^, @backstage/plugin-auth-backend-module-microsoft-provider@workspace:plugins/auth-backend-module-microsoft-provider":
|
||||
"@backstage/plugin-auth-backend-module-microsoft-provider@workspace:plugins/auth-backend-module-microsoft-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-microsoft-provider@workspace:plugins/auth-backend-module-microsoft-provider"
|
||||
dependencies:
|
||||
@@ -5137,7 +5137,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-oauth2-provider@workspace:^, @backstage/plugin-auth-backend-module-oauth2-provider@workspace:plugins/auth-backend-module-oauth2-provider":
|
||||
"@backstage/plugin-auth-backend-module-oauth2-provider@workspace:plugins/auth-backend-module-oauth2-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-oauth2-provider@workspace:plugins/auth-backend-module-oauth2-provider"
|
||||
dependencies:
|
||||
@@ -5154,7 +5154,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-oauth2-proxy-provider@workspace:^, @backstage/plugin-auth-backend-module-oauth2-proxy-provider@workspace:plugins/auth-backend-module-oauth2-proxy-provider":
|
||||
"@backstage/plugin-auth-backend-module-oauth2-proxy-provider@workspace:plugins/auth-backend-module-oauth2-proxy-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-oauth2-proxy-provider@workspace:plugins/auth-backend-module-oauth2-proxy-provider"
|
||||
dependencies:
|
||||
@@ -5167,7 +5167,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-oidc-provider@workspace:^, @backstage/plugin-auth-backend-module-oidc-provider@workspace:plugins/auth-backend-module-oidc-provider":
|
||||
"@backstage/plugin-auth-backend-module-oidc-provider@workspace:plugins/auth-backend-module-oidc-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-oidc-provider@workspace:plugins/auth-backend-module-oidc-provider"
|
||||
dependencies:
|
||||
@@ -5191,7 +5191,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-okta-provider@workspace:^, @backstage/plugin-auth-backend-module-okta-provider@workspace:plugins/auth-backend-module-okta-provider":
|
||||
"@backstage/plugin-auth-backend-module-okta-provider@workspace:plugins/auth-backend-module-okta-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-okta-provider@workspace:plugins/auth-backend-module-okta-provider"
|
||||
dependencies:
|
||||
@@ -5209,7 +5209,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-auth-backend-module-onelogin-provider@workspace:^, @backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider":
|
||||
"@backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-auth-backend-module-onelogin-provider@workspace:plugins/auth-backend-module-onelogin-provider"
|
||||
dependencies:
|
||||
@@ -5286,23 +5286,6 @@ __metadata:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-auth0-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-azure-easyauth-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-server-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-cloudflare-access-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-gcp-iap-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-gitlab-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-google-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-microsoft-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-oauth2-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-oidc-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-okta-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-backend-module-onelogin-provider": "workspace:^"
|
||||
"@backstage/plugin-auth-node": "workspace:^"
|
||||
"@backstage/plugin-catalog-node": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user