auth-backend: refactor EnvironmentHandler to be OAuth-specific and move env config to be a provider concern

This commit is contained in:
Patrik Oldsberg
2020-09-03 12:39:01 +02:00
parent 88cf6da18d
commit fb2d4cf241
12 changed files with 265 additions and 302 deletions
@@ -15,47 +15,32 @@
*/
import express from 'express';
import { AuthProviderRouteHandlers } from '../providers/types';
import { Config } from '@backstage/config';
import { InputError } from '@backstage/backend-common';
import { readState } from './helpers';
import { AuthProviderRouteHandlers } from '../../providers/types';
export type EnvironmentHandlers = {
[key: string]: AuthProviderRouteHandlers;
};
export class OAuthEnvironmentHandler implements AuthProviderRouteHandlers {
static mapConfig(
config: Config,
factoryFunc: (envConfig: Config) => AuthProviderRouteHandlers,
) {
const envs = config.keys();
const handlers = new Map<string, AuthProviderRouteHandlers>();
export type EnvironmentIdentifierFn = (
req: express.Request,
) => string | undefined;
export class EnvironmentHandler implements AuthProviderRouteHandlers {
constructor(
private readonly providerId: string,
private readonly providers: EnvironmentHandlers,
private readonly envIdentifier: EnvironmentIdentifierFn,
) {}
private getProviderForEnv(
req: express.Request,
res: express.Response,
): AuthProviderRouteHandlers | undefined {
const env: string | undefined = this.envIdentifier(req);
if (!env) {
throw new InputError(`Must specify 'env' query to select environment`);
for (const env of envs) {
const envConfig = config.getConfig(env);
const handler = factoryFunc(envConfig);
handlers.set(env, handler);
}
if (this.providers.hasOwnProperty(env)) {
return this.providers[env];
}
res.status(404).send(
`Missing configuration.
<br>
<br>
For this flow to work you need to supply a valid configuration for the "${env}" environment of the "${this.providerId}" provider.`,
);
return undefined;
return new OAuthEnvironmentHandler(handlers);
}
constructor(
private readonly handlers: Map<string, AuthProviderRouteHandlers>,
) {}
async start(req: express.Request, res: express.Response): Promise<void> {
const provider = this.getProviderForEnv(req, res);
await provider?.start(req, res);
@@ -78,4 +63,40 @@ For this flow to work you need to supply a valid configuration for the "${env}"
const provider = this.getProviderForEnv(req, res);
await provider?.logout?.(req, res);
}
private getRequestFromEnv(req: express.Request): string | undefined {
const reqEnv = req.query.env?.toString();
if (reqEnv) {
return reqEnv;
}
const stateParams = req.query.state?.toString();
if (!stateParams) {
return undefined;
}
const env = readState(stateParams).env;
return env;
}
private getProviderForEnv(
req: express.Request,
res: express.Response,
): AuthProviderRouteHandlers | undefined {
const env: string | undefined = this.getRequestFromEnv(req);
if (!env) {
throw new InputError(`Must specify 'env' query to select environment`);
}
if (!this.handlers.has(env)) {
res.status(404).send(
`Missing configuration.
<br>
<br>
For this flow to work you need to supply a valid configuration for the "${env}" environment of provider.`,
);
return undefined;
}
return this.handlers.get(env);
}
}
@@ -17,13 +17,12 @@
import express from 'express';
import passport from 'passport';
import Auth0Strategy from './strategy';
import { Logger } from 'winston';
import { TokenIssuer } from '../../identity';
import {
OAuthProvider,
OAuthProviderOptions,
OAuthProviderHandlers,
OAuthResponse,
OAuthEnvironmentHandler,
} from '../../lib/oauth';
import {
executeFetchUserProfileStrategy,
@@ -33,8 +32,7 @@ import {
makeProfileInfo,
PassportDoneCallback,
} from '../../lib/passport';
import { AuthProviderConfig, RedirectInfo } from '../types';
import { Config } from '@backstage/config';
import { RedirectInfo, AuthProviderFactory } from '../types';
type PrivateInfo = {
refreshToken: string;
@@ -150,29 +148,28 @@ export class Auth0AuthProvider implements OAuthProviderHandlers {
}
}
export function createAuth0Provider(
config: AuthProviderConfig,
_: string,
envConfig: Config,
_logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'auth0';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const domain = envConfig.getString('domain');
const callbackUrl = `${config.baseUrl}/${providerId}/handler/frame`;
export const createAuth0Provider: AuthProviderFactory = ({
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'auth0';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const domain = envConfig.getString('domain');
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
const provider = new Auth0AuthProvider({
clientId,
clientSecret,
callbackUrl,
domain,
});
const provider = new Auth0AuthProvider({
clientId,
clientSecret,
callbackUrl,
domain,
});
return OAuthProvider.fromConfig(config, provider, {
disableRefresh: true,
providerId,
tokenIssuer,
return OAuthProvider.fromConfig(globalConfig, provider, {
disableRefresh: true,
providerId,
tokenIssuer,
});
});
}
@@ -27,11 +27,6 @@ import { createAuth0Provider } from './auth0';
import { createMicrosoftProvider } from './microsoft';
import { AuthProviderConfig, AuthProviderFactory } from './types';
import { Config } from '@backstage/config';
import {
EnvironmentHandlers,
EnvironmentHandler,
EnvironmentIdentifierFn,
} from '../lib/EnvironmentHandler';
const factories: { [providerId: string]: AuthProviderFactory } = {
google: createGoogleProvider,
@@ -47,9 +42,9 @@ const factories: { [providerId: string]: AuthProviderFactory } = {
export const createAuthProviderRouter = (
providerId: string,
globalConfig: AuthProviderConfig,
providerConfig: Config,
config: Config,
logger: Logger,
issuer: TokenIssuer,
tokenIssuer: TokenIssuer,
) => {
const factory = factories[providerId];
if (!factory) {
@@ -57,28 +52,8 @@ export const createAuthProviderRouter = (
}
const router = Router();
const envs = providerConfig.keys();
const envProviders: EnvironmentHandlers = {};
let envIdentifier: EnvironmentIdentifierFn | undefined;
for (const env of envs) {
const envConfig = providerConfig.getConfig(env);
const provider = factory(globalConfig, env, envConfig, logger, issuer);
if (provider) {
envProviders[env] = provider;
envIdentifier = provider.identifyEnv;
}
}
if (typeof envIdentifier === 'undefined') {
throw Error(`No envIdentifier provided for '${providerId}'`);
}
const handler = new EnvironmentHandler(
providerId,
envProviders,
envIdentifier,
);
const handler = factory({ globalConfig, config, logger, tokenIssuer });
router.get('/start', handler.start.bind(handler));
router.get('/handler/frame', handler.frameHandler.bind(handler));
@@ -22,17 +22,15 @@ import {
makeProfileInfo,
PassportDoneCallback,
} from '../../lib/passport';
import { AuthProviderConfig, RedirectInfo } from '../types';
import { RedirectInfo, AuthProviderFactory } from '../types';
import {
OAuthProvider,
OAuthProviderOptions,
OAuthProviderHandlers,
OAuthResponse,
OAuthEnvironmentHandler,
} from '../../lib/oauth';
import { Logger } from 'winston';
import { TokenIssuer } from '../../identity';
import passport from 'passport';
import { Config } from '@backstage/config';
export type GithubAuthProviderOptions = OAuthProviderOptions & {
tokenUrl?: string;
@@ -136,43 +134,42 @@ export class GithubAuthProvider implements OAuthProviderHandlers {
}
}
export function createGithubProvider(
config: AuthProviderConfig,
_: string,
envConfig: Config,
_logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'github';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const enterpriseInstanceUrl = envConfig.getOptionalString(
'enterpriseInstanceUrl',
);
const authorizationUrl = enterpriseInstanceUrl
? `${enterpriseInstanceUrl}/login/oauth/authorize`
: undefined;
const tokenUrl = enterpriseInstanceUrl
? `${enterpriseInstanceUrl}/login/oauth/access_token`
: undefined;
const userProfileUrl = enterpriseInstanceUrl
? `${enterpriseInstanceUrl}/api/v3/user`
: undefined;
const callbackUrl = `${config.baseUrl}/${providerId}/handler/frame`;
export const createGithubProvider: AuthProviderFactory = ({
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'github';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const enterpriseInstanceUrl = envConfig.getOptionalString(
'enterpriseInstanceUrl',
);
const authorizationUrl = enterpriseInstanceUrl
? `${enterpriseInstanceUrl}/login/oauth/authorize`
: undefined;
const tokenUrl = enterpriseInstanceUrl
? `${enterpriseInstanceUrl}/login/oauth/access_token`
: undefined;
const userProfileUrl = enterpriseInstanceUrl
? `${enterpriseInstanceUrl}/api/v3/user`
: undefined;
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
const provider = new GithubAuthProvider({
clientId,
clientSecret,
callbackUrl,
tokenUrl,
userProfileUrl,
authorizationUrl,
});
const provider = new GithubAuthProvider({
clientId,
clientSecret,
callbackUrl,
tokenUrl,
userProfileUrl,
authorizationUrl,
});
return OAuthProvider.fromConfig(config, provider, {
disableRefresh: true,
persistScopes: true,
providerId,
tokenIssuer,
return OAuthProvider.fromConfig(globalConfig, provider, {
disableRefresh: true,
persistScopes: true,
providerId,
tokenIssuer,
});
});
}
@@ -22,17 +22,15 @@ import {
makeProfileInfo,
PassportDoneCallback,
} from '../../lib/passport';
import { AuthProviderConfig, RedirectInfo } from '../types';
import { RedirectInfo, AuthProviderFactory } from '../types';
import {
OAuthProvider,
OAuthProviderOptions,
OAuthProviderHandlers,
OAuthResponse,
OAuthEnvironmentHandler,
} from '../../lib/oauth';
import { Logger } from 'winston';
import { TokenIssuer } from '../../identity';
import passport from 'passport';
import { Config } from '@backstage/config';
export type GitlabAuthProviderOptions = OAuthProviderOptions & {
baseUrl: string;
@@ -139,30 +137,29 @@ export class GitlabAuthProvider implements OAuthProviderHandlers {
}
}
export function createGitlabProvider(
config: AuthProviderConfig,
_: string,
envConfig: Config,
_logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'gitlab';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
const baseUrl = audience || 'https://gitlab.com';
const callbackUrl = `${config.baseUrl}/${providerId}/handler/frame`;
export const createGitlabProvider: AuthProviderFactory = ({
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'gitlab';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
const baseUrl = audience || 'https://gitlab.com';
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
const provider = new GitlabAuthProvider({
clientId,
clientSecret,
callbackUrl,
baseUrl,
});
const provider = new GitlabAuthProvider({
clientId,
clientSecret,
callbackUrl,
baseUrl,
});
return OAuthProvider.fromConfig(config, provider, {
disableRefresh: true,
providerId,
tokenIssuer,
return OAuthProvider.fromConfig(globalConfig, provider, {
disableRefresh: true,
providerId,
tokenIssuer,
});
});
}
@@ -24,17 +24,15 @@ import {
executeFetchUserProfileStrategy,
PassportDoneCallback,
} from '../../lib/passport';
import { AuthProviderConfig, RedirectInfo } from '../types';
import { RedirectInfo, AuthProviderFactory } from '../types';
import {
OAuthProvider,
OAuthProviderHandlers,
OAuthProviderOptions,
OAuthResponse,
OAuthEnvironmentHandler,
} from '../../lib/oauth';
import passport from 'passport';
import { Logger } from 'winston';
import { TokenIssuer } from '../../identity';
import { Config } from '@backstage/config';
type PrivateInfo = {
refreshToken: string;
@@ -147,27 +145,26 @@ export class GoogleAuthProvider implements OAuthProviderHandlers {
}
}
export function createGoogleProvider(
config: AuthProviderConfig,
_: string,
envConfig: Config,
_logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'google';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackUrl = `${config.baseUrl}/${providerId}/handler/frame`;
export const createGoogleProvider: AuthProviderFactory = ({
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'google';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
const provider = new GoogleAuthProvider({
clientId,
clientSecret,
callbackUrl,
});
const provider = new GoogleAuthProvider({
clientId,
clientSecret,
callbackUrl,
});
return OAuthProvider.fromConfig(config, provider, {
disableRefresh: false,
providerId,
tokenIssuer,
return OAuthProvider.fromConfig(globalConfig, provider, {
disableRefresh: false,
providerId,
tokenIssuer,
});
});
}
@@ -27,17 +27,15 @@ import {
PassportDoneCallback,
} from '../../lib/passport';
import { AuthProviderConfig, RedirectInfo } from '../types';
import { RedirectInfo, AuthProviderFactory } from '../types';
import {
OAuthProvider,
OAuthProviderOptions,
OAuthProviderHandlers,
OAuthResponse,
OAuthEnvironmentHandler,
} from '../../lib/oauth';
import { Logger } from 'winston';
import { TokenIssuer } from '../../identity';
import { Config } from '@backstage/config';
import got from 'got';
@@ -204,34 +202,33 @@ export class MicrosoftAuthProvider implements OAuthProviderHandlers {
}
}
export function createMicrosoftProvider(
config: AuthProviderConfig,
_: string,
envConfig: Config,
_logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'microsoft';
export const createMicrosoftProvider: AuthProviderFactory = ({
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'microsoft';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const tenantID = envConfig.getString('tenantId');
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const tenantID = envConfig.getString('tenantId');
const callbackUrl = `${config.baseUrl}/${providerId}/handler/frame`;
const authorizationUrl = `https://login.microsoftonline.com/${tenantID}/oauth2/v2.0/authorize`;
const tokenUrl = `https://login.microsoftonline.com/${tenantID}/oauth2/v2.0/token`;
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
const authorizationUrl = `https://login.microsoftonline.com/${tenantID}/oauth2/v2.0/authorize`;
const tokenUrl = `https://login.microsoftonline.com/${tenantID}/oauth2/v2.0/token`;
const provider = new MicrosoftAuthProvider({
clientId,
clientSecret,
callbackUrl,
authorizationUrl,
tokenUrl,
const provider = new MicrosoftAuthProvider({
clientId,
clientSecret,
callbackUrl,
authorizationUrl,
tokenUrl,
});
return OAuthProvider.fromConfig(globalConfig, provider, {
disableRefresh: false,
providerId,
tokenIssuer,
});
});
return OAuthProvider.fromConfig(config, provider, {
disableRefresh: false,
providerId,
tokenIssuer,
});
}
@@ -17,13 +17,12 @@
import express from 'express';
import passport from 'passport';
import { Strategy as OAuth2Strategy } from 'passport-oauth2';
import { Logger } from 'winston';
import { TokenIssuer } from '../../identity';
import {
OAuthProvider,
OAuthProviderOptions,
OAuthProviderHandlers,
OAuthResponse,
OAuthEnvironmentHandler,
} from '../../lib/oauth';
import {
executeFetchUserProfileStrategy,
@@ -33,8 +32,7 @@ import {
makeProfileInfo,
PassportDoneCallback,
} from '../../lib/passport';
import { AuthProviderConfig, RedirectInfo } from '../types';
import { Config } from '@backstage/config';
import { RedirectInfo, AuthProviderFactory } from '../types';
type PrivateInfo = {
refreshToken: string;
@@ -158,31 +156,30 @@ export class OAuth2AuthProvider implements OAuthProviderHandlers {
}
}
export function createOAuth2Provider(
config: AuthProviderConfig,
_: string,
envConfig: Config,
_logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'oauth2';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackUrl = `${config.baseUrl}/${providerId}/handler/frame`;
const authorizationUrl = envConfig.getString('authorizationUrl');
const tokenUrl = envConfig.getString('tokenUrl');
export const createOAuth2Provider: AuthProviderFactory = ({
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'oauth2';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
const authorizationUrl = envConfig.getString('authorizationUrl');
const tokenUrl = envConfig.getString('tokenUrl');
const provider = new OAuth2AuthProvider({
clientId,
clientSecret,
callbackUrl,
authorizationUrl,
tokenUrl,
});
const provider = new OAuth2AuthProvider({
clientId,
clientSecret,
callbackUrl,
authorizationUrl,
tokenUrl,
});
return OAuthProvider.fromConfig(config, provider, {
disableRefresh: false,
providerId,
tokenIssuer,
return OAuthProvider.fromConfig(globalConfig, provider, {
disableRefresh: false,
providerId,
tokenIssuer,
});
});
}
@@ -19,6 +19,7 @@ import {
OAuthProviderOptions,
OAuthProviderHandlers,
OAuthResponse,
OAuthEnvironmentHandler,
} from '../../lib/oauth';
import { Strategy as OktaStrategy } from 'passport-okta-oauth';
import passport from 'passport';
@@ -30,11 +31,8 @@ import {
executeFetchUserProfileStrategy,
PassportDoneCallback,
} from '../../lib/passport';
import { AuthProviderConfig, RedirectInfo } from '../types';
import { Logger } from 'winston';
import { RedirectInfo, AuthProviderFactory } from '../types';
import { StateStore } from 'passport-oauth2';
import { TokenIssuer } from '../../identity';
import { Config } from '@backstage/config';
type PrivateInfo = {
refreshToken: string;
@@ -169,29 +167,28 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
}
}
export function createOktaProvider(
config: AuthProviderConfig,
_: string,
envConfig: Config,
_logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'okta';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
const callbackUrl = `${config.baseUrl}/${providerId}/handler/frame`;
export const createOktaProvider: AuthProviderFactory = ({
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'okta';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
const provider = new OktaAuthProvider({
audience,
clientId,
clientSecret,
callbackUrl,
});
const provider = new OktaAuthProvider({
audience,
clientId,
clientSecret,
callbackUrl,
});
return OAuthProvider.fromConfig(config, provider, {
disableRefresh: false,
providerId,
tokenIssuer,
return OAuthProvider.fromConfig(globalConfig, provider, {
disableRefresh: false,
providerId,
tokenIssuer,
});
});
}
@@ -26,14 +26,12 @@ import {
PassportDoneCallback,
} from '../../lib/passport';
import {
AuthProviderConfig,
AuthProviderRouteHandlers,
ProfileInfo,
AuthProviderFactory,
} from '../types';
import { postMessageResponse } from '../../lib/flow';
import { Logger } from 'winston';
import { TokenIssuer } from '../../identity';
import { Config } from '@backstage/config';
type SamlInfo = {
userId: string;
@@ -119,15 +117,12 @@ type SAMLProviderOptions = {
tokenIssuer: TokenIssuer;
};
export function createSamlProvider(
_authProviderConfig: AuthProviderConfig,
_env: string,
envConfig: Config,
_logger: Logger,
tokenIssuer: TokenIssuer,
) {
const entryPoint = envConfig.getString('entryPoint');
const issuer = envConfig.getString('issuer');
export const createSamlProvider: AuthProviderFactory = ({
config,
tokenIssuer,
}) => {
const entryPoint = config.getString('entryPoint');
const issuer = config.getString('issuer');
const opts = {
entryPoint,
issuer,
@@ -136,4 +131,4 @@ export function createSamlProvider(
};
return new SamlAuthProvider(opts);
}
};
+9 -15
View File
@@ -108,24 +108,18 @@ export interface AuthProviderRouteHandlers {
* @param {express.Response} res
*/
logout?(req: express.Request, res: express.Response): Promise<void>;
/**
*(Optional) A method to identify the environment Context of the Request
*
*Request
*- contains the environment context information encoded in the request
* @param {express.Request} req
*/
identifyEnv?(req: express.Request): string | undefined;
}
export type AuthProviderFactoryOptions = {
globalConfig: AuthProviderConfig;
config: Config;
logger: Logger;
tokenIssuer: TokenIssuer;
};
export type AuthProviderFactory = (
globalConfig: AuthProviderConfig,
env: string,
envConfig: Config,
logger: Logger,
issuer: TokenIssuer,
) => AuthProviderRouteHandlers | undefined;
options: AuthProviderFactoryOptions,
) => AuthProviderRouteHandlers;
export type AuthResponse<ProviderInfo> = {
providerInfo: ProviderInfo;