From d30b4e387a94ea407ca92ce635182412daf5ccab Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 7 Aug 2023 10:34:08 +0200 Subject: [PATCH] auth-node: add readDeclarativeSignInResolver Signed-off-by: Patrik Oldsberg --- .../src/oauth/createOAuthProviderFactory.ts | 16 ++++- plugins/auth-node/src/sign-in/index.ts | 4 ++ .../sign-in/readDeclarativeSignInResolver.ts | 69 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts diff --git a/plugins/auth-node/src/oauth/createOAuthProviderFactory.ts b/plugins/auth-node/src/oauth/createOAuthProviderFactory.ts index 282fa6fad9..e08756860a 100644 --- a/plugins/auth-node/src/oauth/createOAuthProviderFactory.ts +++ b/plugins/auth-node/src/oauth/createOAuthProviderFactory.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readDeclarativeSignInResolver } from '../sign-in'; import { AuthProviderFactory, SignInResolver } from '../types'; import { OAuthEnvironmentHandler } from './OAuthEnvironmentHandler'; import { createOAuthRouteHandlers } from './createOAuthRouteHandlers'; @@ -23,6 +24,7 @@ import { OAuthAuthenticatorResult, OAuthProfileTransform, } from './types'; +import { SignInResolverFactory } from '../sign-in/createSignInResolverFactory'; /** @public */ export function createOAuthProviderFactory(options: { @@ -30,9 +32,21 @@ export function createOAuthProviderFactory(options: { stateTransform?: OAuthStateTransform; profileTransform?: OAuthProfileTransform; signInResolver?: SignInResolver>; + signInResolverFactories?: { + [name in string]: SignInResolverFactory< + OAuthAuthenticatorResult, + unknown + >; + }; }): AuthProviderFactory { return ctx => { return OAuthEnvironmentHandler.mapConfig(ctx.config, envConfig => { + const signInResolver = + options.signInResolver ?? + readDeclarativeSignInResolver(envConfig, { + signInResolverFactories: options.signInResolverFactories, + }); + return createOAuthRouteHandlers({ authenticator: options.authenticator, appUrl: ctx.appUrl, @@ -44,7 +58,7 @@ export function createOAuthProviderFactory(options: { resolverContext: ctx.resolverContext, stateTransform: options.stateTransform, profileTransform: options.profileTransform, - signInResolver: options.signInResolver, + signInResolver, }); }); }; diff --git a/plugins/auth-node/src/sign-in/index.ts b/plugins/auth-node/src/sign-in/index.ts index fd5b95b970..05d0792ba4 100644 --- a/plugins/auth-node/src/sign-in/index.ts +++ b/plugins/auth-node/src/sign-in/index.ts @@ -15,3 +15,7 @@ */ export { createSignInResolverFactory } from './createSignInResolverFactory'; +export { + readDeclarativeSignInResolver, + type ReadDeclarativeSignInResolverOptions, +} from './readDeclarativeSignInResolver'; diff --git a/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts b/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts new file mode 100644 index 0000000000..31c4dd92a9 --- /dev/null +++ b/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts @@ -0,0 +1,69 @@ +/* + * 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 { Config } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { SignInResolver } from '../types'; +import { SignInResolverFactory } from './createSignInResolverFactory'; + +/** @public */ +export interface ReadDeclarativeSignInResolverOptions { + config: Config; + signInResolverFactories: { + [name in string]: SignInResolverFactory; + }; +} + +/** @public */ +export function readDeclarativeSignInResolver( + options: ReadDeclarativeSignInResolverOptions, +): SignInResolver | undefined { + const resolvers = + options.config + .getOptionalConfigArray('signIn.resolvers') + ?.map(resolverConfig => { + const resolverName = resolverConfig.getString('resolver'); + if (!Object.hasOwn(options.signInResolverFactories, resolverName)) { + throw new Error( + `Sign-in resolver '${resolverName}' is not available`, + ); + } + const resolver = options.signInResolverFactories[resolverName]; + const { resolver: _ignored, ...resolverOptions } = + resolverConfig.get(); + + return resolver(resolverOptions); + }) ?? []; + + if (resolvers.length === 0) { + return undefined; + } + + return async (profile, context) => { + for (const resolver of resolvers ?? []) { + try { + return await resolver(profile, context); + } catch (error) { + if (error?.name === 'NotFoundError') { + continue; + } + throw error; + } + } + + throw new Error('Failed to sign-in, unable to resolve user identity'); + }; +}