Merge pull request #1658 from spotify/rugvip/autherr

auth-backend: couple of improvements to error message clarity
This commit is contained in:
Patrik Oldsberg
2020-07-15 21:16:36 +02:00
committed by GitHub
7 changed files with 73 additions and 66 deletions
@@ -16,49 +16,56 @@
import express from 'express';
import { AuthProviderRouteHandlers } from '../providers/types';
import { NotFoundError } from '@backstage/backend-common';
export type EnvironmentHandlers = {
[key: string]: AuthProviderRouteHandlers;
};
export class EnvironmentHandler implements AuthProviderRouteHandlers {
constructor(private readonly providers: EnvironmentHandlers) {}
constructor(
private readonly providerId: string,
private readonly providers: EnvironmentHandlers,
) {}
private getProviderForEnv(req: express.Request): AuthProviderRouteHandlers {
private getProviderForEnv(
req: express.Request,
res: express.Response,
): AuthProviderRouteHandlers | undefined {
const env = req.query.env?.toString();
if (!this.providers.hasOwnProperty(env)) {
throw new NotFoundError(
`No environment for ${env} found in this provider`,
);
if (this.providers.hasOwnProperty(env)) {
return this.providers[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;
}
async start(req: express.Request, res: express.Response): Promise<void> {
const provider = this.getProviderForEnv(req);
await provider.start(req, res);
const provider = this.getProviderForEnv(req, res);
await provider?.start(req, res);
}
async frameHandler(
req: express.Request,
res: express.Response,
): Promise<void> {
const provider = this.getProviderForEnv(req);
await provider.frameHandler(req, res);
const provider = this.getProviderForEnv(req, res);
await provider?.frameHandler(req, res);
}
async refresh(req: express.Request, res: express.Response): Promise<void> {
const provider = this.getProviderForEnv(req);
if (provider.refresh) {
await provider.refresh(req, res);
}
const provider = this.getProviderForEnv(req, res);
await provider?.refresh?.(req, res);
}
async logout(req: express.Request, res: express.Response): Promise<void> {
const provider = this.getProviderForEnv(req);
if (provider.logout) {
await provider.logout(req, res);
}
const provider = this.getProviderForEnv(req, res);
await provider?.logout?.(req, res);
}
}
@@ -135,16 +135,16 @@ export function createGithubProvider(
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'github';
const envProviders: EnvironmentHandlers = {};
for (const [env, envConfig] of Object.entries(providerConfig)) {
const config = (envConfig as unknown) as OAuthProviderConfig;
const { secure, appOrigin } = config;
const callbackURLParam = `?env=${env}`;
const opts = {
clientID: config.clientId,
clientSecret: config.clientSecret,
callbackURL: `${baseUrl}/github/handler/frame${callbackURLParam}`,
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
};
if (!opts.clientID || !opts.clientSecret) {
@@ -163,12 +163,12 @@ export function createGithubProvider(
envProviders[env] = new OAuthProvider(new GithubAuthProvider(opts), {
disableRefresh: true,
persistScopes: true,
providerId: 'github',
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
return new EnvironmentHandler(envProviders);
return new EnvironmentHandler(providerId, envProviders);
}
@@ -142,6 +142,7 @@ export function createGitlabProvider(
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'gitlab';
const envProviders: EnvironmentHandlers = {};
for (const [env, envConfig] of Object.entries(providerConfig)) {
@@ -152,12 +153,10 @@ export function createGitlabProvider(
clientSecret,
audience,
} = (envConfig as unknown) as OAuthProviderConfig;
const callbackURLParam = `?env=${env}`;
const opts = {
clientID: clientId,
clientSecret: clientSecret,
callbackURL: `${baseUrl}/gitlab/handler/frame${callbackURLParam}`,
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
baseURL: audience,
};
@@ -176,12 +175,12 @@ export function createGitlabProvider(
envProviders[env] = new OAuthProvider(new GitlabAuthProvider(opts), {
disableRefresh: true,
providerId: 'gitlab',
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
return new EnvironmentHandler(envProviders);
return new EnvironmentHandler(providerId, envProviders);
}
@@ -154,16 +154,16 @@ export function createGoogleProvider(
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'google';
const envProviders: EnvironmentHandlers = {};
for (const [env, envConfig] of Object.entries(providerConfig)) {
const config = (envConfig as unknown) as OAuthProviderConfig;
const { secure, appOrigin } = config;
const callbackURLParam = `?env=${env}`;
const opts = {
clientID: config.clientId,
clientSecret: config.clientSecret,
callbackURL: `${baseUrl}/google/handler/frame${callbackURLParam}`,
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
};
if (!opts.clientID || !opts.clientSecret) {
@@ -181,12 +181,12 @@ export function createGoogleProvider(
envProviders[env] = new OAuthProvider(new GoogleAuthProvider(opts), {
disableRefresh: false,
providerId: 'google',
providerId,
secure,
baseUrl,
appOrigin,
tokenIssuer,
});
}
return new EnvironmentHandler(envProviders);
return new EnvironmentHandler(providerId, envProviders);
}
@@ -152,16 +152,16 @@ export function createOAuth2Provider(
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'oauth2';
const envProviders: EnvironmentHandlers = {};
for (const [env, envConfig] of Object.entries(providerConfig)) {
const config = (envConfig as unknown) as GenericOAuth2ProviderConfig;
const { secure, appOrigin } = config;
const callbackURLParam = `?env=${env}`;
const opts = {
clientID: config.clientId,
clientSecret: config.clientSecret,
callbackURL: `${baseUrl}/oauth2/handler/frame${callbackURLParam}`,
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
authorizationURL: config.authorizationURL,
tokenURL: config.tokenURL,
};
@@ -186,7 +186,7 @@ export function createOAuth2Provider(
envProviders[env] = new OAuthProvider(new OAuth2AuthProvider(opts), {
disableRefresh: false,
providerId: 'oauth2',
providerId,
secure,
baseUrl,
appOrigin,
@@ -194,5 +194,5 @@ export function createOAuth2Provider(
});
}
return new EnvironmentHandler(envProviders);
return new EnvironmentHandler(providerId, envProviders);
}
@@ -47,17 +47,16 @@ type PrivateInfo = {
};
export class OktaAuthProvider implements OAuthProviderHandlers {
private readonly _strategy: any;
/**
/**
* Due to passport-okta-oauth forcing options.state = true,
* passport-oauth2 requires express-session to be installed
* so that the 'state' parameter of the oauth2 flow can be stored.
* This implementation of StateStore matches the NullStore found within
* passport-oauth2, which is the StateStore implementation used when options.state = false,
* allowing us to avoid using express-session in order to integrate with Okta.
*/
*/
private _store: StateStore = {
store(_req: express.Request, cb: any) {
cb(null, null);
@@ -65,25 +64,27 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
verify(_req: express.Request, _state: string, cb: any) {
cb(null, true);
},
}
};
constructor(options: OAuthProviderOptions) {
this._strategy = new OktaStrategy({
passReqToCallback: false as true,
...options,
store: this._store,
response_type: 'code',
}, (
accessToken: any,
refreshToken: any,
params: any,
rawProfile: passport.Profile,
done: PassportDoneCallback<OAuthResponse, PrivateInfo>,
) => {
const profile = makeProfileInfo(rawProfile, params.id_token);
this._strategy = new OktaStrategy(
{
passReqToCallback: false as true,
...options,
store: this._store,
response_type: 'code',
},
(
accessToken: any,
refreshToken: any,
params: any,
rawProfile: passport.Profile,
done: PassportDoneCallback<OAuthResponse, PrivateInfo>,
) => {
const profile = makeProfileInfo(rawProfile, params.id_token);
done(
undefined,
done(
undefined,
{
providerInfo: {
idToken: params.id_token,
@@ -96,13 +97,14 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
{
refreshToken,
},
)
});
);
},
);
}
async start(
req: express.Request,
options: Record<string, string>
options: Record<string, string>,
): Promise<RedirectInfo> {
const providerOptions = {
...options,
@@ -113,7 +115,7 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
}
async handler(
req: express.Request
req: express.Request,
): Promise<{ response: OAuthResponse; refreshToken: string }> {
const { response, privateInfo } = await executeFrameHandlerStrategy<
OAuthResponse,
@@ -148,7 +150,6 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
},
profile,
});
}
private async populateIdentity(
@@ -173,17 +174,17 @@ export function createOktaProvider(
logger: Logger,
tokenIssuer: TokenIssuer,
) {
const providerId = 'okta';
const envProviders: EnvironmentHandlers = {};
for (const [env, envConfig] of Object.entries(providerConfig)) {
const config = (envConfig as unknown) as OAuthProviderConfig;
const { secure, appOrigin } = config;
const callbackURLParam = `?env=${env}`;
const opts = {
audience: config.audience,
clientID: config.clientId,
clientSecret: config.clientSecret,
callbackURL: `${baseUrl}/okta/handler/frame${callbackURLParam}`,
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
};
if (!opts.clientID || !opts.clientSecret || !opts.audience) {
@@ -201,7 +202,7 @@ export function createOktaProvider(
envProviders[env] = new OAuthProvider(new OktaAuthProvider(opts), {
disableRefresh: false,
providerId: 'okta',
providerId,
secure,
baseUrl,
appOrigin,
@@ -209,5 +210,5 @@ export function createOktaProvider(
});
}
return new EnvironmentHandler(envProviders);
return new EnvironmentHandler(providerId, envProviders);
}
@@ -147,5 +147,5 @@ export function createSamlProvider(
envProviders[env] = new SamlAuthProvider(opts);
}
return new EnvironmentHandler(envProviders);
return new EnvironmentHandler('saml', envProviders);
}