mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
Change PAT primary key from string to number (#2163)
* Update primary key * Fix tests
This commit is contained in:
parent
879e1358ef
commit
c105ca02f1
@ -6,8 +6,8 @@ import NotFoundError from '../error/notfound-error';
|
||||
|
||||
const TABLE = 'personal_access_tokens';
|
||||
|
||||
const PAT_COLUMNS = [
|
||||
'secret',
|
||||
const PAT_PUBLIC_COLUMNS = [
|
||||
'id',
|
||||
'description',
|
||||
'user_id',
|
||||
'expires_at',
|
||||
@ -20,6 +20,7 @@ const fromRow = (row) => {
|
||||
throw new NotFoundError('No PAT found');
|
||||
}
|
||||
return new Pat({
|
||||
id: row.id,
|
||||
secret: row.secret,
|
||||
userId: row.user_id,
|
||||
description: row.description,
|
||||
@ -51,8 +52,12 @@ export default class PatStore implements IPatStore {
|
||||
return fromRow(row[0]);
|
||||
}
|
||||
|
||||
async delete(secret: string): Promise<void> {
|
||||
return this.db(TABLE).where({ secret: secret }).del();
|
||||
async delete(id: number): Promise<void> {
|
||||
return this.db(TABLE).where({ id: id }).del();
|
||||
}
|
||||
|
||||
async deleteForUser(id: number, userId: number): Promise<void> {
|
||||
return this.db(TABLE).where({ id: id, user_id: userId }).del();
|
||||
}
|
||||
|
||||
async deleteAll(): Promise<void> {
|
||||
@ -61,28 +66,28 @@ export default class PatStore implements IPatStore {
|
||||
|
||||
destroy(): void {}
|
||||
|
||||
async exists(secret: string): Promise<boolean> {
|
||||
async exists(id: number): Promise<boolean> {
|
||||
const result = await this.db.raw(
|
||||
`SELECT EXISTS(SELECT 1 FROM ${TABLE} WHERE secret = ?) AS present`,
|
||||
[secret],
|
||||
`SELECT EXISTS(SELECT 1 FROM ${TABLE} WHERE id = ?) AS present`,
|
||||
[id],
|
||||
);
|
||||
const { present } = result.rows[0];
|
||||
return present;
|
||||
}
|
||||
|
||||
async get(secret: string): Promise<Pat> {
|
||||
const row = await this.db(TABLE).where({ secret }).first();
|
||||
async get(id: number): Promise<Pat> {
|
||||
const row = await this.db(TABLE).where({ id }).first();
|
||||
return fromRow(row);
|
||||
}
|
||||
|
||||
async getAll(): Promise<Pat[]> {
|
||||
const groups = await this.db.select(PAT_COLUMNS).from(TABLE);
|
||||
const groups = await this.db.select(PAT_PUBLIC_COLUMNS).from(TABLE);
|
||||
return groups.map(fromRow);
|
||||
}
|
||||
|
||||
async getAllByUser(userId: number): Promise<Pat[]> {
|
||||
const groups = await this.db
|
||||
.select(PAT_COLUMNS)
|
||||
.select(PAT_PUBLIC_COLUMNS)
|
||||
.from(TABLE)
|
||||
.where('user_id', userId);
|
||||
return groups.map(fromRow);
|
||||
|
@ -4,6 +4,9 @@ export const patSchema = {
|
||||
$id: '#/components/schemas/patSchema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'number',
|
||||
},
|
||||
secret: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -62,7 +62,7 @@ export default class PatController extends Controller {
|
||||
|
||||
this.route({
|
||||
method: 'delete',
|
||||
path: '/:secret',
|
||||
path: '/:id',
|
||||
acceptAnyContentType: true,
|
||||
handler: this.deletePat,
|
||||
permission: NONE,
|
||||
@ -95,11 +95,11 @@ export default class PatController extends Controller {
|
||||
}
|
||||
|
||||
async deletePat(
|
||||
req: IAuthRequest<{ secret: string }>,
|
||||
req: IAuthRequest<{ id: number }>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
const { secret } = req.params;
|
||||
await this.patService.deletePat(secret);
|
||||
const { id } = req.params;
|
||||
await this.patService.deletePat(id, req.user.id);
|
||||
res.status(200).end();
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ export default class PatService {
|
||||
pat.userId = user.id;
|
||||
const newPat = await this.patStore.create(pat);
|
||||
|
||||
pat.secret = '***';
|
||||
await this.eventStore.store({
|
||||
type: PAT_CREATED,
|
||||
createdBy: user.email || user.username,
|
||||
@ -50,8 +51,8 @@ export default class PatService {
|
||||
return this.patStore.getAllByUser(user.id);
|
||||
}
|
||||
|
||||
async deletePat(secret: string): Promise<void> {
|
||||
return this.patStore.delete(secret);
|
||||
async deletePat(id: number, userId: number): Promise<void> {
|
||||
return this.patStore.deleteForUser(id, userId);
|
||||
}
|
||||
|
||||
private generateSecretKey() {
|
||||
|
@ -1,4 +1,5 @@
|
||||
export interface IPat {
|
||||
id: number;
|
||||
secret: string;
|
||||
description: string;
|
||||
userId: number;
|
||||
@ -8,6 +9,8 @@ export interface IPat {
|
||||
}
|
||||
|
||||
export default class Pat implements IPat {
|
||||
id: number;
|
||||
|
||||
secret: string;
|
||||
|
||||
description: string;
|
||||
@ -21,13 +24,15 @@ export default class Pat implements IPat {
|
||||
createdAt: Date;
|
||||
|
||||
constructor({
|
||||
secret,
|
||||
id,
|
||||
userId,
|
||||
expiresAt,
|
||||
seenAt,
|
||||
createdAt,
|
||||
secret,
|
||||
description,
|
||||
}: IPat) {
|
||||
this.id = id;
|
||||
this.secret = secret;
|
||||
this.userId = userId;
|
||||
this.expiresAt = expiresAt;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Store } from './store';
|
||||
import { IPat } from '../models/pat';
|
||||
|
||||
export interface IPatStore extends Store<IPat, string> {
|
||||
export interface IPatStore extends Store<IPat, number> {
|
||||
create(group: IPat): Promise<IPat>;
|
||||
getAllByUser(userId: number): Promise<IPat[]>;
|
||||
deleteForUser(id: number, userId: number): Promise<void>;
|
||||
}
|
||||
|
28
src/migrations/20221010114644-pat-auto-increment.js
Normal file
28
src/migrations/20221010114644-pat-auto-increment.js
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function (db, cb) {
|
||||
db.runSql(
|
||||
`
|
||||
alter table personal_access_tokens
|
||||
drop constraint personal_access_tokens_pkey;
|
||||
ALTER TABLE personal_access_tokens
|
||||
add column id serial primary key;
|
||||
`,
|
||||
cb,
|
||||
);
|
||||
};
|
||||
|
||||
exports.down = function (db, cb) {
|
||||
db.runSql(
|
||||
`
|
||||
alter table personal_access_tokens
|
||||
drop constraint personal_access_tokens_pkey;
|
||||
alter table personal_access_tokens
|
||||
drop column id;
|
||||
alter table personal_access_tokens
|
||||
add primary key (secret);
|
||||
|
||||
`,
|
||||
cb,
|
||||
);
|
||||
};
|
@ -8,6 +8,7 @@ let db: ITestDb;
|
||||
|
||||
let tomorrow = new Date();
|
||||
let firstSecret;
|
||||
let firstId;
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -44,6 +45,7 @@ test('should create a PAT', async () => {
|
||||
expect(new Date(body.expiresAt)).toEqual(tomorrow);
|
||||
expect(body.description).toEqual(description);
|
||||
firstSecret = body.secret;
|
||||
firstId = body.id;
|
||||
|
||||
const response = await request
|
||||
.get('/api/admin/user/tokens')
|
||||
@ -64,9 +66,9 @@ test('should delete the PAT', async () => {
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
|
||||
const createdSecret = body.secret;
|
||||
const createdId = body.id;
|
||||
|
||||
await request.delete(`/api/admin/user/tokens/${createdSecret}`).expect(200);
|
||||
await request.delete(`/api/admin/user/tokens/${createdId}`).expect(200);
|
||||
});
|
||||
|
||||
test('should get all PATs', async () => {
|
||||
@ -78,6 +80,36 @@ test('should get all PATs', async () => {
|
||||
.expect(200);
|
||||
|
||||
expect(body.pats).toHaveLength(1);
|
||||
expect(body.pats[0].secret).toBeUndefined();
|
||||
expect(body.pats[0].id).toBeDefined();
|
||||
});
|
||||
|
||||
test('should not allow deletion of other users PAT', async () => {
|
||||
const { request } = app;
|
||||
|
||||
await app.request
|
||||
.post(`/auth/demo/login`)
|
||||
.send({
|
||||
email: 'user-second@getunleash.io',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await request.delete(`/api/admin/user/tokens/${firstId}`).expect(200);
|
||||
|
||||
await app.request
|
||||
.post(`/auth/demo/login`)
|
||||
.send({
|
||||
email: 'user@getunleash.io',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body } = await request
|
||||
.get('/api/admin/user/tokens')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
|
||||
expect(body.pats).toHaveLength(1);
|
||||
expect(body.pats[0].secret).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should get only current user PATs', async () => {
|
||||
|
@ -1688,6 +1688,9 @@ exports[`should serve the OpenAPI spec 1`] = `
|
||||
"nullable": true,
|
||||
"type": "string",
|
||||
},
|
||||
"id": {
|
||||
"type": "number",
|
||||
},
|
||||
"secret": {
|
||||
"type": "string",
|
||||
},
|
||||
@ -7051,13 +7054,13 @@ If the provided project does not exist, the list of events will be empty.",
|
||||
],
|
||||
},
|
||||
},
|
||||
"/api/admin/user/tokens/{secret}": {
|
||||
"/api/admin/user/tokens/{id}": {
|
||||
"delete": {
|
||||
"operationId": "deletePat",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "secret",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
|
10
src/test/fixtures/fake-pat-store.ts
vendored
10
src/test/fixtures/fake-pat-store.ts
vendored
@ -6,7 +6,7 @@ export default class FakePatStore implements IPatStore {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
delete(key: string): Promise<void> {
|
||||
delete(key: number): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@ -16,11 +16,11 @@ export default class FakePatStore implements IPatStore {
|
||||
|
||||
destroy(): void {}
|
||||
|
||||
exists(key: string): Promise<boolean> {
|
||||
exists(key: number): Promise<boolean> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
get(key: string): Promise<IPat> {
|
||||
get(key: number): Promise<IPat> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@ -31,4 +31,8 @@ export default class FakePatStore implements IPatStore {
|
||||
getAllByUser(userId: number): Promise<IPat[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
deleteForUser(id: number, userId: number): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user