mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-11 00:08:30 +01:00
feat: authorization middleware (#3464)
This commit is contained in:
parent
70d802883b
commit
33487d11b9
@ -123,13 +123,15 @@ export const UserProfileContent = ({
|
||||
|
||||
<StyledDivider />
|
||||
|
||||
<StyledLogoutButton
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
href={`${basePath}/logout`}
|
||||
>
|
||||
Log out
|
||||
</StyledLogoutButton>
|
||||
<form method="POST" action={`${basePath}/logout`}>
|
||||
<StyledLogoutButton
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
>
|
||||
Log out
|
||||
</StyledLogoutButton>
|
||||
</form>
|
||||
</StyledPaper>
|
||||
}
|
||||
/>
|
||||
|
@ -107,7 +107,7 @@ export default async function getApp(
|
||||
switch (config.authentication.type) {
|
||||
case IAuthType.OPEN_SOURCE: {
|
||||
app.use(baseUriPath, apiTokenMiddleware(config, services));
|
||||
ossAuthentication(app, config.server.baseUriPath);
|
||||
ossAuthentication(app, config.getLogger, config.server.baseUriPath);
|
||||
break;
|
||||
}
|
||||
case IAuthType.ENTERPRISE: {
|
||||
|
39
src/lib/middleware/authorization-middleware.ts
Normal file
39
src/lib/middleware/authorization-middleware.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { IAuthRequest } from '../routes/unleash-types';
|
||||
import { NextFunction, Response } from 'express';
|
||||
import AuthenticationRequired from '../types/authentication-required';
|
||||
import { LogProvider } from '../logger';
|
||||
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
const authorizationMiddleware = (
|
||||
getLogger: LogProvider,
|
||||
baseUriPath: string,
|
||||
): any => {
|
||||
const logger = getLogger('/middleware/authorization-middleware.ts');
|
||||
logger.debug('Enabling Authorization middleware');
|
||||
|
||||
const generateAuthResponse = async () =>
|
||||
new AuthenticationRequired({
|
||||
type: 'password',
|
||||
path: `${baseUriPath}/auth/simple/login`,
|
||||
message: 'You must sign in order to use Unleash',
|
||||
});
|
||||
|
||||
return async (req: IAuthRequest, res: Response, next: NextFunction) => {
|
||||
if (req.session && req.session.user) {
|
||||
req.user = req.session.user;
|
||||
return next();
|
||||
}
|
||||
if (req.user) {
|
||||
return next();
|
||||
}
|
||||
if (req.header('authorization')) {
|
||||
// API clients should get 401 without body
|
||||
return res.sendStatus(401);
|
||||
}
|
||||
// Admin UI users should get auth-response
|
||||
const authRequired = await generateAuthResponse();
|
||||
return res.status(401).json(authRequired);
|
||||
};
|
||||
};
|
||||
|
||||
export default authorizationMiddleware;
|
@ -7,6 +7,10 @@ import ossAuth from './oss-authentication';
|
||||
import getApp from '../app';
|
||||
import User from '../types/user';
|
||||
import sessionDb from './session-db';
|
||||
import { Knex } from 'knex';
|
||||
import { LogProvider } from '../logger';
|
||||
|
||||
const getLogger = (() => ({ debug() {} })) as unknown as LogProvider;
|
||||
|
||||
async function getSetup(preRouterHook) {
|
||||
const base = `/random${Math.round(Math.random() * 1000)}`;
|
||||
@ -14,7 +18,7 @@ async function getSetup(preRouterHook) {
|
||||
server: { baseUriPath: base },
|
||||
preRouterHook: (_app) => {
|
||||
preRouterHook(_app);
|
||||
ossAuth(_app, base);
|
||||
ossAuth(_app, getLogger, base);
|
||||
_app.get(`${base}/api/protectedResource`, (req, res) => {
|
||||
res.status(200).json({ message: 'OK' }).end();
|
||||
});
|
||||
@ -22,7 +26,7 @@ async function getSetup(preRouterHook) {
|
||||
});
|
||||
const stores = createStores();
|
||||
const services = createServices(stores, config);
|
||||
const unleashSession = sessionDb(config, undefined);
|
||||
const unleashSession = sessionDb(config, {} as Knex);
|
||||
const app = await getApp(config, stores, services, unleashSession);
|
||||
|
||||
return {
|
||||
|
@ -1,33 +1,19 @@
|
||||
import { Application, NextFunction, Response } from 'express';
|
||||
import { IAuthRequest } from '../routes/unleash-types';
|
||||
import AuthenticationRequired from '../types/authentication-required';
|
||||
|
||||
function ossAuthHook(app: Application, baseUriPath: string): void {
|
||||
const generateAuthResponse = async () =>
|
||||
new AuthenticationRequired({
|
||||
type: 'password',
|
||||
path: `${baseUriPath}/auth/simple/login`,
|
||||
message: 'You must sign in order to use Unleash',
|
||||
});
|
||||
import { Application } from 'express';
|
||||
import authorizationMiddleware from './authorization-middleware';
|
||||
import { LogProvider } from '../logger';
|
||||
|
||||
function ossAuthHook(
|
||||
app: Application,
|
||||
getLogger: LogProvider,
|
||||
baseUriPath: string,
|
||||
): void {
|
||||
app.use(
|
||||
`${baseUriPath}/api`,
|
||||
async (req: IAuthRequest, res: Response, next: NextFunction) => {
|
||||
if (req.session && req.session.user) {
|
||||
req.user = req.session.user;
|
||||
return next();
|
||||
}
|
||||
if (req.user) {
|
||||
return next();
|
||||
}
|
||||
if (req.header('authorization')) {
|
||||
// API clients should get 401 without body
|
||||
return res.sendStatus(401);
|
||||
}
|
||||
// Admin UI users should get auth-response
|
||||
const authRequired = await generateAuthResponse();
|
||||
return res.status(401).json(authRequired);
|
||||
},
|
||||
authorizationMiddleware(getLogger, baseUriPath),
|
||||
);
|
||||
app.use(
|
||||
`${baseUriPath}/logout`,
|
||||
authorizationMiddleware(getLogger, baseUriPath),
|
||||
);
|
||||
}
|
||||
export default ossAuthHook;
|
||||
|
@ -37,6 +37,7 @@ function sessionDb(
|
||||
: config.server.baseUriPath,
|
||||
secure: config.secureHeaders,
|
||||
maxAge: age,
|
||||
sameSite: 'lax',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ test('should redirect to "/" after logout', async () => {
|
||||
const request = supertest(app);
|
||||
expect.assertions(0);
|
||||
await request
|
||||
.get(`${baseUriPath}/logout`)
|
||||
.post(`${baseUriPath}/logout`)
|
||||
.expect(302)
|
||||
.expect('Location', `${baseUriPath}/`);
|
||||
});
|
||||
@ -45,7 +45,7 @@ test('should redirect to "/basePath" after logout when baseUriPath is set', asyn
|
||||
const request = supertest(app);
|
||||
expect.assertions(0);
|
||||
await request
|
||||
.get('/logout')
|
||||
.post('/logout')
|
||||
.expect(302)
|
||||
.expect('Location', `${baseUriPath}/`);
|
||||
});
|
||||
@ -64,7 +64,7 @@ test('should set "Clear-Site-Data" header', async () => {
|
||||
const request = supertest(app);
|
||||
expect.assertions(0);
|
||||
await request
|
||||
.get(`${baseUriPath}/logout`)
|
||||
.post(`${baseUriPath}/logout`)
|
||||
.expect(302)
|
||||
.expect('Clear-Site-Data', '"cookies", "storage"');
|
||||
});
|
||||
@ -86,7 +86,7 @@ test('should not set "Clear-Site-Data" header', async () => {
|
||||
const request = supertest(app);
|
||||
expect.assertions(1);
|
||||
await request
|
||||
.get(`${baseUriPath}/logout`)
|
||||
.post(`${baseUriPath}/logout`)
|
||||
.expect(302)
|
||||
.expect((res) =>
|
||||
expect(res.headers['Clear-Site-Data']).toBeUndefined(),
|
||||
@ -108,7 +108,7 @@ test('should clear "unleash-session" cookies', async () => {
|
||||
const request = supertest(app);
|
||||
expect.assertions(0);
|
||||
await request
|
||||
.get(`${baseUriPath}/logout`)
|
||||
.post(`${baseUriPath}/logout`)
|
||||
.expect(302)
|
||||
.expect(
|
||||
'Set-Cookie',
|
||||
@ -134,7 +134,7 @@ test('should clear "unleash-session" cookie even when disabled clear site data',
|
||||
const request = supertest(app);
|
||||
expect.assertions(0);
|
||||
await request
|
||||
.get(`${baseUriPath}/logout`)
|
||||
.post(`${baseUriPath}/logout`)
|
||||
.expect(302)
|
||||
.expect(
|
||||
'Set-Cookie',
|
||||
@ -162,7 +162,7 @@ test('should call destroy on session', async () => {
|
||||
app.use('/logout', new LogoutController(config, { sessionService }).router);
|
||||
|
||||
const request = supertest(app);
|
||||
await request.get(`${baseUriPath}/logout`);
|
||||
await request.post(`${baseUriPath}/logout`);
|
||||
|
||||
expect(fakeSession.destroy.mock.calls.length).toBe(1);
|
||||
});
|
||||
@ -186,7 +186,7 @@ test('should handle req.logout with callback function', async () => {
|
||||
app.use('/logout', new LogoutController(config, { sessionService }).router);
|
||||
|
||||
const request = supertest(app);
|
||||
await request.get(`${baseUriPath}/logout`);
|
||||
await request.post(`${baseUriPath}/logout`);
|
||||
|
||||
expect(logoutFunction).toHaveBeenCalledTimes(1);
|
||||
expect(logoutFunction).toHaveBeenCalledWith(expect.anything());
|
||||
@ -211,7 +211,7 @@ test('should handle req.logout without callback function', async () => {
|
||||
app.use('/logout', new LogoutController(config, { sessionService }).router);
|
||||
|
||||
const request = supertest(app);
|
||||
await request.get(`${baseUriPath}/logout`);
|
||||
await request.post(`${baseUriPath}/logout`);
|
||||
|
||||
expect(logoutFunction).toHaveBeenCalledTimes(1);
|
||||
expect(logoutFunction).toHaveBeenCalledWith();
|
||||
@ -238,7 +238,7 @@ test('should redirect to alternative logoutUrl', async () => {
|
||||
|
||||
const request = supertest(app);
|
||||
await request
|
||||
.get('/logout')
|
||||
.post('/logout')
|
||||
.expect(302)
|
||||
.expect('Location', '/some-other-path');
|
||||
});
|
||||
@ -282,7 +282,7 @@ test('Should destroy sessions for user', async () => {
|
||||
let activeSessionsBeforeLogout = await sessionStore.getSessionsForUser(1);
|
||||
expect(activeSessionsBeforeLogout).toHaveLength(2);
|
||||
app.use('/logout', new LogoutController(config, { sessionService }).router);
|
||||
await supertest(app).get('/logout').expect(302);
|
||||
await supertest(app).post('/logout').expect(302);
|
||||
let activeSessions = await sessionStore.getSessionsForUser(1);
|
||||
expect(activeSessions).toHaveLength(0);
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Response } from 'express';
|
||||
import { promisify } from 'util';
|
||||
import { IUnleashConfig } from '../types';
|
||||
import { IUnleashConfig, NONE } from '../types';
|
||||
import Controller from './controller';
|
||||
import { IAuthRequest } from './unleash-types';
|
||||
import { IUnleashServices } from '../types';
|
||||
@ -24,7 +24,14 @@ class LogoutController extends Controller {
|
||||
this.baseUri = config.server.baseUriPath;
|
||||
this.clearSiteDataOnLogout = config.session.clearSiteDataOnLogout;
|
||||
this.cookieName = config.session.cookieName;
|
||||
this.get('/', this.logout);
|
||||
|
||||
this.route({
|
||||
method: 'post',
|
||||
path: '/',
|
||||
handler: this.logout,
|
||||
permission: NONE,
|
||||
acceptAnyContentType: true,
|
||||
});
|
||||
}
|
||||
|
||||
async logout(req: IAuthRequest, res: Response): Promise<void> {
|
||||
|
@ -222,7 +222,7 @@ test('should not fail creation of PAT when a description already exists for anot
|
||||
});
|
||||
|
||||
test('should get user id 1', async () => {
|
||||
await app.request.get('/logout').expect(302);
|
||||
await app.request.post('/logout').expect(302);
|
||||
await app.request
|
||||
.get('/api/admin/user')
|
||||
.set('Authorization', firstSecret)
|
||||
|
Loading…
Reference in New Issue
Block a user