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:
parent
1ba5701422
commit
37ad0a3799
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ function demoAuthentication(
|
||||
environment: 'default',
|
||||
type: ApiTokenType.CLIENT,
|
||||
project: '*',
|
||||
secret: 'a',
|
||||
});
|
||||
}
|
||||
next();
|
||||
|
@ -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',
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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 = (
|
||||
|
@ -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';
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user