mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
fix: limit total of PATs a user can have (#2301)
* fix: limit total of PATs a user can have * increase PAT limit to 10 * Update src/lib/services/pat-service.ts Co-authored-by: Simon Hornby <liquidwicked64@gmail.com> * disable button on the front-end when PAT limit is reached * import from server instead of repeating ourselves Co-authored-by: Simon Hornby <liquidwicked64@gmail.com>
This commit is contained in:
parent
98cda9258d
commit
9fb431aab7
@ -19,6 +19,7 @@ import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
|
||||
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
||||
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { PAT_LIMIT } from '@server/util/constants';
|
||||
import { usePersonalAPITokens } from 'hooks/api/getters/usePersonalAPITokens/usePersonalAPITokens';
|
||||
import { useSearch } from 'hooks/useSearch';
|
||||
import {
|
||||
@ -250,6 +251,7 @@ export const PersonalAPITokensTab = ({ user }: IPersonalAPITokensTabProps) => {
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={tokens.length >= PAT_LIMIT}
|
||||
onClick={() => setCreateOpen(true)}
|
||||
>
|
||||
New token
|
||||
|
@ -2,11 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
@ -19,10 +15,11 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@server/*": ["./../../src/lib/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
@ -87,6 +87,15 @@ export default class PatStore implements IPatStore {
|
||||
return present;
|
||||
}
|
||||
|
||||
async countByUser(userId: number): Promise<number> {
|
||||
const result = await this.db.raw(
|
||||
`SELECT COUNT(*) AS count FROM ${TABLE} WHERE user_id = ?`,
|
||||
[userId],
|
||||
);
|
||||
const { count } = result.rows[0];
|
||||
return count;
|
||||
}
|
||||
|
||||
async get(id: number): Promise<Pat> {
|
||||
const row = await this.db(TABLE).where({ id }).first();
|
||||
return fromRow(row);
|
||||
|
@ -8,6 +8,8 @@ import crypto from 'crypto';
|
||||
import User from '../types/user';
|
||||
import BadDataError from '../error/bad-data-error';
|
||||
import NameExistsError from '../error/name-exists-error';
|
||||
import { OperationDeniedError } from '../error/operation-denied-error';
|
||||
import { PAT_LIMIT } from '../util/constants';
|
||||
|
||||
export default class PatService {
|
||||
private config: IUnleashConfig;
|
||||
@ -67,6 +69,12 @@ export default class PatService {
|
||||
throw new BadDataError('The expiry date should be in future.');
|
||||
}
|
||||
|
||||
if ((await this.patStore.countByUser(userId)) >= PAT_LIMIT) {
|
||||
throw new OperationDeniedError(
|
||||
`Too many PATs (${PAT_LIMIT}) already exist for this user.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
await this.patStore.existsWithDescriptionByUser(description, userId)
|
||||
) {
|
||||
|
@ -9,4 +9,5 @@ export interface IPatStore extends Store<IPat, number> {
|
||||
description: string,
|
||||
userId: number,
|
||||
): Promise<boolean>;
|
||||
countByUser(userId: number): Promise<number>;
|
||||
}
|
||||
|
@ -55,3 +55,5 @@ export const STRING_OPERATORS = [
|
||||
export const NUM_OPERATORS = [NUM_EQ, NUM_GT, NUM_GTE, NUM_LT, NUM_LTE];
|
||||
export const DATE_OPERATORS = [DATE_AFTER, DATE_BEFORE];
|
||||
export const SEMVER_OPERATORS = [SEMVER_EQ, SEMVER_GT, SEMVER_LT];
|
||||
|
||||
export const PAT_LIMIT = 10;
|
||||
|
@ -3,6 +3,7 @@ import dbInit, { ITestDb } from '../../../helpers/database-init';
|
||||
import getLogger from '../../../../fixtures/no-logger';
|
||||
import { IPat } from '../../../../../lib/types/models/pat';
|
||||
import { IPatStore } from '../../../../../lib/types/stores/pat-store';
|
||||
import { PAT_LIMIT } from '../../../../../lib/util/constants';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
@ -269,3 +270,36 @@ test('should not get user with expired token', async () => {
|
||||
.set('Authorization', token.secret)
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
test('should fail creation of PAT when PAT limit has been reached', async () => {
|
||||
await app.request
|
||||
.post(`/auth/demo/login`)
|
||||
.send({
|
||||
email: 'user-too-many-pats@getunleash.io',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const tokenCreations = [];
|
||||
for (let i = 0; i < PAT_LIMIT; i++) {
|
||||
tokenCreations.push(
|
||||
await app.request
|
||||
.post('/api/admin/user/tokens')
|
||||
.send({
|
||||
description: `my pat ${i}`,
|
||||
expiresAt: tomorrow,
|
||||
} as IPat)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201),
|
||||
);
|
||||
}
|
||||
await Promise.all(tokenCreations);
|
||||
|
||||
await app.request
|
||||
.post('/api/admin/user/tokens')
|
||||
.send({
|
||||
description: `my pat ${PAT_LIMIT}`,
|
||||
expiresAt: tomorrow,
|
||||
} as IPat)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(403);
|
||||
});
|
||||
|
4
src/test/fixtures/fake-pat-store.ts
vendored
4
src/test/fixtures/fake-pat-store.ts
vendored
@ -27,6 +27,10 @@ export default class FakePatStore implements IPatStore {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
countByUser(userId: number): Promise<number> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
get(key: number): Promise<IPat> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user