@@ -22,26 +22,18 @@ import {
|
||||
SignInPage,
|
||||
} from '@backstage/core-components';
|
||||
import React from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { providers } from '../src/identityProviders';
|
||||
import {
|
||||
configApiRef,
|
||||
createApiFactory,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { AuthProxyDiscoveryApi } from '../src/AuthProxyDiscoveryApi';
|
||||
|
||||
// TODO(Rugvip): make this available via some util, or maybe Utility API?
|
||||
function readBasePath(configApi: typeof configApiRef.T) {
|
||||
let { pathname } = new URL(
|
||||
configApi.getOptionalString('app.baseUrl') ?? '/',
|
||||
'http://sample.dev', // baseUrl can be specified as just a path
|
||||
);
|
||||
pathname = pathname.replace(/\/*$/, '');
|
||||
return pathname;
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
apis: [
|
||||
createApiFactory({
|
||||
@@ -65,8 +57,33 @@ const app = createApp({
|
||||
});
|
||||
|
||||
function RedirectToRoot() {
|
||||
window.location.pathname = readBasePath(useApi(configApiRef));
|
||||
return <div />;
|
||||
const identityApi = useApi(identityApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const { token } = await identityApi.getCredentials();
|
||||
if (!token) {
|
||||
throw new Error('Expected Backstage token in sign-in response');
|
||||
}
|
||||
return token;
|
||||
}, [identityApi]);
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
} else if (error) {
|
||||
return <>An error occurred: {error}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
ref={form => form?.submit()}
|
||||
action={window.location.href}
|
||||
method="POST"
|
||||
>
|
||||
<input type="hidden" name="type" value="sign-in" />
|
||||
<input type="hidden" name="token" value={value} />
|
||||
<input type="submit" value="Continue" />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
const App = app.createRoot(
|
||||
|
||||
@@ -73,7 +73,6 @@ export async function buildBundle(options: BuildOptions) {
|
||||
configs.push(
|
||||
await createConfig(publicPaths, {
|
||||
...commonConfigOptions,
|
||||
publicSubPath: '/public',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -220,7 +220,6 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
|
||||
config,
|
||||
await createConfig(publicPaths, {
|
||||
...commonConfigOptions,
|
||||
publicSubPath: '/public',
|
||||
}),
|
||||
])
|
||||
: webpack(config);
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/config-loader": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/plugin-app-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
|
||||
@@ -65,8 +65,10 @@ export const appPlugin = createBackendPlugin({
|
||||
config: coreServices.rootConfig,
|
||||
database: coreServices.database,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
auth: coreServices.auth,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
},
|
||||
async init({ logger, config, database, httpRouter }) {
|
||||
async init({ logger, config, database, httpRouter, auth, httpAuth }) {
|
||||
const appPackageName =
|
||||
config.getOptionalString('app.packageName') ?? 'app';
|
||||
|
||||
@@ -76,11 +78,15 @@ export const appPlugin = createBackendPlugin({
|
||||
logger: winstonLogger,
|
||||
config,
|
||||
database,
|
||||
auth,
|
||||
httpAuth,
|
||||
appPackageName,
|
||||
staticFallbackHandler,
|
||||
schema,
|
||||
});
|
||||
httpRouter.use(router);
|
||||
|
||||
// Access control is handled within the router
|
||||
httpRouter.addAuthPolicy({
|
||||
allow: 'unauthenticated',
|
||||
path: '/',
|
||||
|
||||
@@ -38,6 +38,8 @@ import {
|
||||
CACHE_CONTROL_REVALIDATE_CACHE,
|
||||
} from '../lib/headers';
|
||||
import { ConfigSchema } from '@backstage/config-loader';
|
||||
import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
|
||||
// express uses mime v1 while we only have types for mime v2
|
||||
type Mime = { lookup(arg0: string): string };
|
||||
@@ -46,6 +48,8 @@ type Mime = { lookup(arg0: string): string };
|
||||
export interface RouterOptions {
|
||||
config: Config;
|
||||
logger: Logger;
|
||||
auth?: AuthService;
|
||||
httpAuth?: HttpAuthService;
|
||||
|
||||
/**
|
||||
* If a database is provided it will be used to cache previously deployed static assets.
|
||||
@@ -99,7 +103,14 @@ export interface RouterOptions {
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { config, logger, appPackageName, staticFallbackHandler } = options;
|
||||
const {
|
||||
config,
|
||||
logger,
|
||||
appPackageName,
|
||||
staticFallbackHandler,
|
||||
auth,
|
||||
httpAuth,
|
||||
} = options;
|
||||
|
||||
const disableConfigInjection =
|
||||
options.disableConfigInjection ??
|
||||
@@ -154,9 +165,80 @@ export async function createRouter(
|
||||
|
||||
router.use(helmet.frameguard({ action: 'deny' }));
|
||||
|
||||
const publicDistDir = resolvePath(appDistDir, 'public');
|
||||
|
||||
const enablePublicEntryPoint = await fs.pathExists(publicDistDir);
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- Cookie refresh
|
||||
- Backend endpoint, /.backstage/v1-cookie
|
||||
- Frontend provider
|
||||
- Remove issueUserCookie from HttpAuthService and move to auth-node
|
||||
- Move RedirectToRoot to auth-react
|
||||
- Document index-public-experimental & how to use
|
||||
- Logout? How do we clear the cookie?
|
||||
|
||||
*/
|
||||
|
||||
if (enablePublicEntryPoint && auth && httpAuth) {
|
||||
const publicRouter = Router();
|
||||
|
||||
// TODO
|
||||
// publicRouter.use(createCookieAuthRouter({ httpAuth }))
|
||||
|
||||
publicRouter.use(async (req, _res, next) => {
|
||||
const credentials = await httpAuth.credentials(req, {
|
||||
allow: ['user', 'service', 'none'],
|
||||
allowLimitedAccess: true,
|
||||
});
|
||||
|
||||
if (credentials.principal.type === 'none') {
|
||||
next();
|
||||
} else {
|
||||
next('router');
|
||||
}
|
||||
});
|
||||
|
||||
publicRouter.post(
|
||||
'*',
|
||||
express.urlencoded({ extended: true }),
|
||||
async (req, res, next) => {
|
||||
if (req.body.type === 'sign-in') {
|
||||
const credentials = await auth.authenticate(req.body.token);
|
||||
|
||||
if (!auth.isPrincipal(credentials, 'user')) {
|
||||
throw new AuthenticationError('Invalid token, not a user');
|
||||
}
|
||||
|
||||
await httpAuth.issueUserCookie(res, {
|
||||
credentials,
|
||||
});
|
||||
|
||||
// Resume as if it was a GET request towards the outer protected router, serving index.html
|
||||
req.method = 'GET';
|
||||
next('router');
|
||||
} else {
|
||||
throw new Error('Invalid POST request to /');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
publicRouter.use(
|
||||
await createEntryPointRouter({
|
||||
logger: logger.child({ entry: 'public' }),
|
||||
rootDir: publicDistDir,
|
||||
assetStore: assetStore?.withNamespace('public'),
|
||||
appConfigs, // TODO(Rugvip): We should not be including the full config here
|
||||
}),
|
||||
);
|
||||
|
||||
router.use(publicRouter);
|
||||
}
|
||||
|
||||
router.use(
|
||||
await createEntryPointRouter({
|
||||
logger,
|
||||
logger: logger.child({ entry: 'main' }),
|
||||
rootDir: appDistDir,
|
||||
assetStore,
|
||||
staticFallbackHandler,
|
||||
|
||||
@@ -4691,6 +4691,7 @@ __metadata:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/config-loader": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-app-node": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@types/express": ^4.17.6
|
||||
|
||||
Reference in New Issue
Block a user