Skip to content

Commit

Permalink
fix: limit total of PATs a user can have (#2301)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
nunogois and sighphyre committed Nov 2, 2022
1 parent 98cda92 commit 9fb431a
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 9 deletions.
Expand Up @@ -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 {
Expand Down Expand Up @@ -250,6 +251,7 @@ export const PersonalAPITokensTab = ({ user }: IPersonalAPITokensTabProps) => {
<Button
variant="contained"
color="primary"
disabled={tokens.length >= PAT_LIMIT}
onClick={() => setCreateOpen(true)}
>
New token
Expand Down
15 changes: 6 additions & 9 deletions frontend/tsconfig.json
Expand Up @@ -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,
Expand All @@ -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" }]
}
9 changes: 9 additions & 0 deletions src/lib/db/pat-store.ts
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions src/lib/services/pat-service.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
) {
Expand Down
1 change: 1 addition & 0 deletions src/lib/types/stores/pat-store.ts
Expand Up @@ -9,4 +9,5 @@ export interface IPatStore extends Store<IPat, number> {
description: string,
userId: number,
): Promise<boolean>;
countByUser(userId: number): Promise<number>;
}
2 changes: 2 additions & 0 deletions src/lib/util/constants.ts
Expand Up @@ -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;
34 changes: 34 additions & 0 deletions src/test/e2e/api/admin/user/pat.e2e.test.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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 changes: 4 additions & 0 deletions src/test/fixtures/fake-pat-store.ts
Expand Up @@ -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.');
}
Expand Down

0 comments on commit 9fb431a

Please sign in to comment.