1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-10 17:53:36 +02:00

feat: add support for proxy keys

This commit is contained in:
olav 2022-08-15 15:56:12 +02:00
parent 1ba5701422
commit 37ad0a3799
10 changed files with 52 additions and 9 deletions

View File

@ -65,6 +65,7 @@ test('should add user if known token', async () => {
project: ALL,
environment: ALL,
type: ApiTokenType.CLIENT,
secret: 'a',
});
const apiTokenService = {
getUserForToken: jest.fn().mockReturnValue(apiUser),
@ -96,6 +97,7 @@ test('should not add user if not /api/client', async () => {
project: ALL,
environment: ALL,
type: ApiTokenType.CLIENT,
secret: 'a',
});
const apiTokenService = {
@ -134,6 +136,7 @@ test('should not add user if disabled', async () => {
project: ALL,
environment: ALL,
type: ApiTokenType.CLIENT,
secret: 'a',
});
const apiTokenService = {
getUserForToken: jest.fn().mockReturnValue(apiUser),

View File

@ -6,8 +6,12 @@ const isClientApi = ({ path }) => {
return path && path.startsWith('/api/client');
};
const isProxyApi = ({ path }) => {
return path && path.startsWith('/api/frontend');
};
export const TOKEN_TYPE_ERROR_MESSAGE =
'invalid token: expected an admin token but got a client token instead';
'invalid token: expected a different token type for this endpoint';
const apiAccessMiddleware = (
{
@ -31,9 +35,13 @@ const apiAccessMiddleware = (
try {
const apiToken = req.header('authorization');
const apiUser = apiTokenService.getUserForToken(apiToken);
const { CLIENT, PROXY } = ApiTokenType;
if (apiUser) {
if (apiUser.type === ApiTokenType.CLIENT && !isClientApi(req)) {
if (
(apiUser.type === CLIENT && !isClientApi(req)) ||
(apiUser.type === PROXY && !isProxyApi(req))
) {
res.status(403).send({ message: TOKEN_TYPE_ERROR_MESSAGE });
return;
}

View File

@ -47,6 +47,7 @@ function demoAuthentication(
environment: 'default',
type: ApiTokenType.CLIENT,
project: '*',
secret: 'a',
});
}
next();

View File

@ -50,6 +50,7 @@ test('should give api-user ADMIN permission', async () => {
project: '*',
environment: '*',
type: ApiTokenType.ADMIN,
secret: 'a',
}),
};
@ -75,6 +76,7 @@ test('should not give api-user ADMIN permission', async () => {
project: '*',
environment: '*',
type: ApiTokenType.CLIENT,
secret: 'a',
}),
};

View File

@ -1,6 +1,6 @@
import crypto from 'crypto';
import { Logger } from '../logger';
import { ADMIN, CLIENT } from '../types/permissions';
import { ADMIN, CLIENT, PROXY } from '../types/permissions';
import { IUnleashStores } from '../types/stores';
import { IUnleashConfig } from '../types/option';
import ApiUser from '../types/api-user';
@ -20,6 +20,22 @@ import BadDataError from '../error/bad-data-error';
import { minutesToMilliseconds } from 'date-fns';
import { IEnvironmentStore } from 'lib/types/stores/environment-store';
const resolveTokenPermissions = (tokenType: string) => {
if (tokenType === ApiTokenType.ADMIN) {
return [ADMIN];
}
if (tokenType === ApiTokenType.CLIENT) {
return [CLIENT];
}
if (tokenType === ApiTokenType.PROXY) {
return [PROXY];
}
return [];
};
export class ApiTokenService {
private store: IApiTokenStore;
@ -88,15 +104,13 @@ export class ApiTokenService {
public getUserForToken(secret: string): ApiUser | undefined {
const token = this.activeTokens.find((t) => t.secret === secret);
if (token) {
const permissions =
token.type === ApiTokenType.ADMIN ? [ADMIN] : [CLIENT];
return new ApiUser({
username: token.username,
permissions,
permissions: resolveTokenPermissions(token.type),
projects: token.projects,
environment: token.environment,
type: token.type,
secret: token.secret,
});
}
return undefined;

View File

@ -8,6 +8,7 @@ interface IApiUserData {
project?: string;
environment: string;
type: ApiTokenType;
secret: string;
}
export default class ApiUser {
@ -23,6 +24,8 @@ export default class ApiUser {
readonly type: ApiTokenType;
readonly secret: string;
constructor({
username,
permissions = [CLIENT],
@ -30,6 +33,7 @@ export default class ApiUser {
project,
environment,
type,
secret,
}: IApiUserData) {
if (!username) {
throw new TypeError('username is required');
@ -38,6 +42,7 @@ export default class ApiUser {
this.permissions = permissions;
this.environment = environment;
this.type = type;
this.secret = secret;
if (projects && projects.length > 0) {
this.projects = projects;
} else {

View File

@ -6,6 +6,7 @@ export const ALL = '*';
export enum ApiTokenType {
CLIENT = 'client',
ADMIN = 'admin',
PROXY = 'proxy',
}
export interface ILegacyApiTokenCreate {
@ -102,6 +103,12 @@ export const validateApiToken = ({
'Client token cannot be scoped to all environments',
);
}
if (type === ApiTokenType.PROXY && environment === ALL) {
throw new BadDataError(
'Proxy token cannot be scoped to all environments',
);
}
};
export const validateApiTokenEnvironment = (

View File

@ -1,6 +1,7 @@
//Special
export const ADMIN = 'ADMIN';
export const CLIENT = 'CLIENT';
export const PROXY = 'PROXY';
export const NONE = 'NONE';
export const CREATE_FEATURE = 'CREATE_FEATURE';

View File

@ -1862,6 +1862,7 @@ test('Should not allow changing project to target project without the same enabl
project: '*',
type: ApiTokenType.ADMIN,
environment: '*',
secret: 'a',
});
await expect(async () =>
app.services.projectService.changeProject(
@ -1945,6 +1946,7 @@ test('Should allow changing project to target project with the same enabled envi
project: '*',
type: ApiTokenType.ADMIN,
environment: '*',
secret: 'a',
});
await expect(async () =>
app.services.projectService.changeProject(

View File

@ -230,7 +230,7 @@ Object {
"type": "string",
},
"type": Object {
"description": "client, admin.",
"description": "client, admin, proxy.",
"type": "string",
},
"username": Object {
@ -667,7 +667,7 @@ Object {
"type": "string",
},
"type": Object {
"description": "client, admin.",
"description": "client, admin, proxy.",
"type": "string",
},
"username": Object {