backend-{plugin,app}-api: added addAuthPolicy to HttpRouterService + implementation

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: Carl-Erik Bergström <cbergstrom@spotify.com>
Co-authored-by: blam <ben@blam.sh>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-09 13:04:48 +01:00
parent 746701e094
commit 344a82b4e5
7 changed files with 118 additions and 5 deletions
+1
View File
@@ -73,6 +73,7 @@
"minimist": "^1.2.5",
"morgan": "^1.10.0",
"node-forge": "^1.3.1",
"path-to-regexp": "^6.2.1",
"selfsigned": "^2.0.0",
"stoppable": "^1.1.0",
"winston": "^3.2.1",
@@ -0,0 +1,89 @@
/*
* Copyright 2024 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 {
HttpAuthService,
HttpRouterServiceAuthPolicy,
} from '@backstage/backend-plugin-api';
import { RequestHandler } from 'express';
import { pathToRegexp } from 'path-to-regexp';
export function createPathPolicyPredicate(policyPath: string) {
if (policyPath === '/' || policyPath === '*') {
return () => true;
}
const pathRegex = pathToRegexp(policyPath, undefined, {
end: false,
});
return (path: string): boolean => {
return pathRegex.test(path);
};
}
export function createCredentialsBarrier(options: {
httpAuth: HttpAuthService;
}): {
middleware: RequestHandler;
addAuthPolicy: (policy: HttpRouterServiceAuthPolicy) => void;
} {
const { httpAuth } = options;
const unauthenticatedPredicates = new Array<(path: string) => boolean>();
const cookiePredicates = new Array<(path: string) => boolean>();
const middleware: RequestHandler = async (req, _, next) => {
const allowsUnauthenticated = unauthenticatedPredicates.some(predicate =>
predicate(req.path),
);
if (allowsUnauthenticated) {
next();
return;
}
const allowsCookie = cookiePredicates.some(predicate =>
predicate(req.path),
);
if (allowsCookie) {
// don't we need a user-cookie allow type here?
await httpAuth.credentials(req, {
allow: ['user-cookie', 'user', 'service'],
});
next();
return;
}
await httpAuth.credentials(req, {
allow: ['user', 'service'],
});
next();
};
const addAuthPolicy = (policy: HttpRouterServiceAuthPolicy) => {
if (policy.allow === 'unauthenticated') {
unauthenticatedPredicates.push(createPathPolicyPredicate(policy.path));
} else if (policy.allow === 'user-cookie') {
cookiePredicates.push(createPathPolicyPredicate(policy.path));
}
throw new Error('Invalid auth policy');
};
return { middleware, addAuthPolicy };
}
+10
View File
@@ -299,10 +299,20 @@ export interface HttpAuthService {
// @public (undocumented)
export interface HttpRouterService {
// (undocumented)
addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void;
// (undocumented)
use(handler: Handler): void;
}
// @public (undocumented)
export interface HttpRouterServiceAuthPolicy {
// (undocumented)
allow: 'unauthenticated' | 'user-cookie';
// (undocumented)
path: string;
}
// @public (undocumented)
export interface IdentityService extends IdentityApi {}
@@ -16,9 +16,17 @@
import { Handler } from 'express';
/** @public */
export interface HttpRouterServiceAuthPolicy {
path: string;
allow: 'unauthenticated' | 'user-cookie';
}
/**
* @public
*/
export interface HttpRouterService {
use(handler: Handler): void;
addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void;
}
@@ -29,7 +29,10 @@ export type {
export type { RootConfigService } from './RootConfigService';
export type { DatabaseService } from './DatabaseService';
export type { DiscoveryService } from './DiscoveryService';
export type { HttpRouterService } from './HttpRouterService';
export type {
HttpRouterService,
HttpRouterServiceAuthPolicy,
} from './HttpRouterService';
export type {
HttpAuthService,
BackstageCredentialTypes,
@@ -182,6 +182,7 @@ export namespace mockServices {
export const factory = httpRouterServiceFactory;
export const mock = simpleMock(coreServices.httpRouter, () => ({
use: jest.fn(),
addAuthPolicy: jest.fn(),
}));
}
export namespace rootHttpRouter {
+5 -4
View File
@@ -3258,6 +3258,7 @@ __metadata:
minimist: ^1.2.5
morgan: ^1.10.0
node-forge: ^1.3.1
path-to-regexp: ^6.2.1
selfsigned: ^2.0.0
stoppable: ^1.1.0
supertest: ^6.1.3
@@ -37508,10 +37509,10 @@ __metadata:
languageName: node
linkType: hard
"path-to-regexp@npm:^6.2.0":
version: 6.2.0
resolution: "path-to-regexp@npm:6.2.0"
checksum: a6aca74d2d6e2e7594d812f653cf85e9cb5054d3a8d80f099722a44ef6ad22639b02078e5ea83d11db16321c3e4359e3f1ab0274fa78dad0754a6e53f630b0fc
"path-to-regexp@npm:^6.2.0, path-to-regexp@npm:^6.2.1":
version: 6.2.1
resolution: "path-to-regexp@npm:6.2.1"
checksum: f0227af8284ea13300f4293ba111e3635142f976d4197f14d5ad1f124aebd9118783dd2e5f1fe16f7273743cc3dbeddfb7493f237bb27c10fdae07020cc9b698
languageName: node
linkType: hard