auth-backend

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2022-08-19 13:54:05 +02:00
parent fa07cc8ff2
commit 2fc41ebf07
20 changed files with 89 additions and 101 deletions
@@ -26,17 +26,10 @@ import {
} from '@backstage/catalog-model';
import { TokenManager } from '@backstage/backend-common';
type UserQuery = {
annotations: Record<string, string>;
};
type MemberClaimQuery = {
entityRefs: string[];
logger?: Logger;
};
/**
* A catalog client tailored for reading out identity data from the catalog.
*
* @public
*/
export class CatalogIdentityClient {
private readonly catalogApi: CatalogApi;
@@ -52,7 +45,9 @@ export class CatalogIdentityClient {
*
* Throws a NotFoundError or ConflictError if 0 or multiple users are found.
*/
async findUser(query: UserQuery): Promise<UserEntity> {
async findUser(query: {
annotations: Record<string, string>;
}): Promise<UserEntity> {
const filter: Record<string, string> = {
kind: 'user',
};
@@ -81,7 +76,10 @@ export class CatalogIdentityClient {
*
* Returns a superset of the entity names that can be passed directly to `issueToken` as `ent`.
*/
async resolveCatalogMembership(query: MemberClaimQuery): Promise<string[]> {
async resolveCatalogMembership(query: {
entityRefs: string[];
logger?: Logger;
}): Promise<string[]> {
const { entityRefs, logger } = query;
const resolvedEntityRefs = entityRefs
.map((ref: string) => {
@@ -24,6 +24,7 @@ export const safelyEncodeURIComponent = (value: string) => {
return encodeURIComponent(value).replace(/'/g, '%27');
};
/** @public */
export const postMessageResponse = (
res: express.Response,
appOrigin: string,
@@ -68,6 +69,7 @@ export const postMessageResponse = (
res.end(`<html><body><script>${script}</script></body></html>`);
};
/** @public */
export const ensuresXRequestedWith = (req: express.Request) => {
const requiredHeader = req.header('X-Requested-With');
if (!requiredHeader || requiredHeader !== 'XMLHttpRequest') {
@@ -19,6 +19,8 @@ import { AuthResponse } from '../../providers/types';
/**
* Payload sent as a post message after the auth request is complete.
* If successful then has a valid payload with Auth information else contains an error.
*
* @public
*/
export type WebMessageResponse =
| {
@@ -44,7 +44,8 @@ import { prepareBackstageIdentityResponse } from '../../providers/prepareBacksta
export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;
export const TEN_MINUTES_MS = 600 * 1000;
export type Options = {
/** @public */
export type OAuthAdapterOptions = {
providerId: string;
secure: boolean;
persistScopes?: boolean;
@@ -54,11 +55,16 @@ export type Options = {
isOriginAllowed: (origin: string) => boolean;
callbackUrl: string;
};
/** @public */
export class OAuthAdapter implements AuthProviderRouteHandlers {
static fromConfig(
config: AuthProviderConfig,
handlers: OAuthHandlers,
options: Pick<Options, 'providerId' | 'persistScopes' | 'callbackUrl'>,
options: Pick<
OAuthAdapterOptions,
'providerId' | 'persistScopes' | 'callbackUrl'
>,
): OAuthAdapter {
const { origin: appOrigin } = new URL(config.appUrl);
@@ -83,7 +89,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
constructor(
private readonly handlers: OAuthHandlers,
private readonly options: Options,
private readonly options: OAuthAdapterOptions,
) {
this.baseCookieOptions = {
httpOnly: true,
@@ -20,6 +20,7 @@ import { InputError, NotFoundError } from '@backstage/errors';
import { readState } from './helpers';
import { AuthProviderRouteHandlers } from '../../providers/types';
/** @public */
export class OAuthEnvironmentHandler implements AuthProviderRouteHandlers {
static mapConfig(
config: Config,
@@ -19,6 +19,7 @@ import { OAuthState } from './types';
import pickBy from 'lodash/pickBy';
import { CookieConfigurer } from '../../providers/types';
/** @public */
export const readState = (stateString: string): OAuthState => {
const state = Object.fromEntries(
new URLSearchParams(Buffer.from(stateString, 'hex').toString('utf-8')),
@@ -35,6 +36,7 @@ export const readState = (stateString: string): OAuthState => {
return state as OAuthState;
};
/** @public */
export const encodeState = (state: OAuthState): string => {
const stateString = new URLSearchParams(
pickBy<string>(state, value => value !== undefined),
@@ -43,6 +45,7 @@ export const encodeState = (state: OAuthState): string => {
return Buffer.from(stateString, 'utf-8').toString('hex');
};
/** @public */
export const verifyNonce = (req: express.Request, providerId: string) => {
const cookieNonce = req.cookies[`${providerId}-nonce`];
const state: OAuthState = readState(req.query.state?.toString() ?? '');
@@ -15,6 +15,7 @@
*/
export { OAuthEnvironmentHandler } from './OAuthEnvironmentHandler';
export type { OAuthAdapterOptions } from './OAuthAdapter';
export { OAuthAdapter } from './OAuthAdapter';
export { encodeState, verifyNonce, readState } from './helpers';
export type {
@@ -21,6 +21,8 @@ import { RedirectInfo, ProfileInfo } from '../../providers/types';
/**
* Common options for passport.js-based OAuth providers
*
* @public
*/
export type OAuthProviderOptions = {
/**
@@ -37,6 +39,7 @@ export type OAuthProviderOptions = {
callbackUrl: string;
};
/** @public */
export type OAuthResult = {
fullProfile: PassportProfile;
params: {
@@ -59,6 +62,7 @@ export type OAuthResponse = {
backstageIdentity?: BackstageSignInResult;
};
/** @public */
export type OAuthProviderInfo = {
/**
* An access token issued for the signed in user.
@@ -78,6 +82,7 @@ export type OAuthProviderInfo = {
scope: string;
};
/** @public */
export type OAuthState = {
/* A type for the serialized value in the `state` parameter of the OAuth authorization flow
*/
@@ -87,11 +92,13 @@ export type OAuthState = {
scope?: string;
};
/** @public */
export type OAuthStartRequest = express.Request<{}> & {
scope: string;
state: OAuthState;
};
/** @public */
export type OAuthRefreshRequest = express.Request<{}> & {
scope: string;
refreshToken: string;
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { atlassian, AtlassianAuthProvider } from './provider';
export { atlassian } from './provider';
@@ -44,6 +44,7 @@ import {
import express from 'express';
import { createAuthProviderIntegration } from '../createAuthProviderIntegration';
/** @public */
export type AtlassianAuthProviderOptions = OAuthProviderOptions & {
scopes: string;
signInResolver?: SignInResolver<OAuthResult>;
@@ -58,10 +59,6 @@ export const atlassianDefaultAuthHandler: AuthHandler<OAuthResult> = async ({
profile: makeProfileInfo(fullProfile, params.id_token),
});
/**
* @public
* @deprecated This export is deprecated and will be removed in the future.
*/
export class AtlassianAuthProvider implements OAuthHandlers {
private readonly _strategy: AtlassianStrategy;
private readonly signInResolver?: SignInResolver<OAuthResult>;
@@ -54,6 +54,7 @@ type Options = OAuthProviderOptions & {
resolverContext: AuthResolverContext;
};
/** @public */
export type BitbucketOAuthResult = {
fullProfile: BitbucketPassportProfile;
params: {
@@ -65,6 +66,7 @@ export type BitbucketOAuthResult = {
refreshToken?: string;
};
/** @public */
export type BitbucketPassportProfile = PassportProfile & {
id?: string;
displayName?: string;
@@ -146,7 +146,6 @@ export type CloudflareAccessIdentityProfile = {
};
/**
*
* @public
*/
export type CloudflareAccessResult = {
@@ -22,6 +22,8 @@ import { AuthProviderFactory, SignInResolver } from './types';
*
* The returned object facilitates the creation of provider instances, and
* supplies built-in sign-in resolvers for the specific provider.
*
* @public
*/
export function createAuthProviderIntegration<
TCreateOptions extends unknown[],
@@ -52,6 +52,7 @@ type PrivateInfo = {
refreshToken?: string;
};
/** @public */
export type GithubOAuthResult = {
fullProfile: PassportProfile;
params: {
+1 -1
View File
@@ -14,7 +14,6 @@
* limitations under the License.
*/
export { AtlassianAuthProvider } from './atlassian';
export type { AwsAlbResult } from './aws-alb';
export type {
BitbucketOAuthResult,
@@ -50,6 +49,7 @@ export type {
StateEncoder,
AuthResponse,
ProfileInfo,
RedirectInfo,
} from './types';
export { prepareBackstageIdentityResponse } from './prepareBackstageIdentityResponse';
@@ -126,6 +126,7 @@ export type AuthProviderConfig = {
cookieConfigurer?: CookieConfigurer;
};
/** @public */
export type RedirectInfo = {
/**
* URL to redirect to
@@ -147,6 +148,8 @@ export type RedirectInfo = {
* `/auth/[provider]/handler/frame -> frameHandler`
* `/auth/[provider]/refresh -> refresh`
* `/auth/[provider]/logout -> logout`
*
* @public
*/
export interface AuthProviderRouteHandlers {
/**
@@ -192,6 +195,7 @@ export interface AuthProviderRouteHandlers {
logout?(req: express.Request, res: express.Response): Promise<void>;
}
/** @public */
export type AuthProviderFactory = (options: {
providerId: string;
globalConfig: AuthProviderConfig;
+5 -1
View File
@@ -36,8 +36,10 @@ import passport from 'passport';
import { Minimatch } from 'minimatch';
import { CatalogAuthResolverContext } from '../lib/resolvers';
type ProviderFactories = { [s: string]: AuthProviderFactory };
/** @public */
export type ProviderFactories = { [s: string]: AuthProviderFactory };
/** @public */
export interface RouterOptions {
logger: Logger;
database: PluginDatabaseManager;
@@ -48,6 +50,7 @@ export interface RouterOptions {
providerFactories?: ProviderFactories;
}
/** @public */
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
@@ -187,6 +190,7 @@ export async function createRouter(
return router;
}
/** @public */
export function createOriginFilter(
config: Config,
): (origin: string) => boolean {