auth-backend: couple of improvements to error message clarity
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user