removed all project references

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2025-04-15 21:42:24 +02:00
parent 57221d9148
commit d72da5ec19
54 changed files with 17 additions and 3637 deletions
-58
View File
@@ -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.
*/
-17
View File
@@ -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:^",
-1
View File
@@ -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 applications 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 },
});
};
},
},
});
+1 -18
View File
@@ -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,
+16 -33
View File
@@ -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:^"