mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(server): granular permissions for api keys (#11824)
feat(server): api auth permissions
This commit is contained in:
parent
a372b56d44
commit
f230b3aa42
43 changed files with 817 additions and 135 deletions
|
|
@ -1,12 +1,12 @@
|
|||
import { LoginResponseDto, createApiKey } from '@immich/sdk';
|
||||
import { LoginResponseDto, Permission, createApiKey } from '@immich/sdk';
|
||||
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||
import { errorDto } from 'src/responses';
|
||||
import { app, asBearerAuth, utils } from 'src/utils';
|
||||
import request from 'supertest';
|
||||
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
const create = (accessToken: string) =>
|
||||
createApiKey({ apiKeyCreateDto: { name: 'api key' } }, { headers: asBearerAuth(accessToken) });
|
||||
const create = (accessToken: string, permissions: Permission[]) =>
|
||||
createApiKey({ apiKeyCreateDto: { name: 'api key', permissions } }, { headers: asBearerAuth(accessToken) });
|
||||
|
||||
describe('/api-keys', () => {
|
||||
let admin: LoginResponseDto;
|
||||
|
|
@ -30,15 +30,65 @@ describe('/api-keys', () => {
|
|||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
|
||||
it('should not work without permission', async () => {
|
||||
const { secret } = await create(user.accessToken, [Permission.ApiKeyRead]);
|
||||
const { status, body } = await request(app).post('/api-keys').set('x-api-key', secret).send({ name: 'API Key' });
|
||||
expect(status).toBe(403);
|
||||
expect(body).toEqual(errorDto.missingPermission('apiKey.create'));
|
||||
});
|
||||
|
||||
it('should work with apiKey.create', async () => {
|
||||
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate, Permission.ApiKeyRead]);
|
||||
const { status, body } = await request(app)
|
||||
.post('/api-keys')
|
||||
.set('x-api-key', secret)
|
||||
.send({
|
||||
name: 'API Key',
|
||||
permissions: [Permission.ApiKeyRead],
|
||||
});
|
||||
expect(body).toEqual({
|
||||
secret: expect.any(String),
|
||||
apiKey: {
|
||||
id: expect.any(String),
|
||||
name: 'API Key',
|
||||
permissions: [Permission.ApiKeyRead],
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
});
|
||||
expect(status).toBe(201);
|
||||
});
|
||||
|
||||
it('should not create an api key with all permissions', async () => {
|
||||
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate]);
|
||||
const { status, body } = await request(app)
|
||||
.post('/api-keys')
|
||||
.set('x-api-key', secret)
|
||||
.send({ name: 'API Key', permissions: [Permission.All] });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('Cannot grant permissions you do not have'));
|
||||
});
|
||||
|
||||
it('should not create an api key with more permissions', async () => {
|
||||
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate]);
|
||||
const { status, body } = await request(app)
|
||||
.post('/api-keys')
|
||||
.set('x-api-key', secret)
|
||||
.send({ name: 'API Key', permissions: [Permission.ApiKeyRead] });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest('Cannot grant permissions you do not have'));
|
||||
});
|
||||
|
||||
it('should create an api key', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/api-keys')
|
||||
.send({ name: 'API Key' })
|
||||
.send({ name: 'API Key', permissions: [Permission.All] })
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(body).toEqual({
|
||||
apiKey: {
|
||||
id: expect.any(String),
|
||||
name: 'API Key',
|
||||
permissions: [Permission.All],
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
|
|
@ -63,9 +113,9 @@ describe('/api-keys', () => {
|
|||
|
||||
it('should return a list of api keys', async () => {
|
||||
const [{ apiKey: apiKey1 }, { apiKey: apiKey2 }, { apiKey: apiKey3 }] = await Promise.all([
|
||||
create(admin.accessToken),
|
||||
create(admin.accessToken),
|
||||
create(admin.accessToken),
|
||||
create(admin.accessToken, [Permission.All]),
|
||||
create(admin.accessToken, [Permission.All]),
|
||||
create(admin.accessToken, [Permission.All]),
|
||||
]);
|
||||
const { status, body } = await request(app).get('/api-keys').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(body).toHaveLength(3);
|
||||
|
|
@ -82,7 +132,7 @@ describe('/api-keys', () => {
|
|||
});
|
||||
|
||||
it('should require authorization', async () => {
|
||||
const { apiKey } = await create(user.accessToken);
|
||||
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||
const { status, body } = await request(app)
|
||||
.get(`/api-keys/${apiKey.id}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
|
@ -99,7 +149,7 @@ describe('/api-keys', () => {
|
|||
});
|
||||
|
||||
it('should get api key details', async () => {
|
||||
const { apiKey } = await create(user.accessToken);
|
||||
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||
const { status, body } = await request(app)
|
||||
.get(`/api-keys/${apiKey.id}`)
|
||||
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||
|
|
@ -107,6 +157,7 @@ describe('/api-keys', () => {
|
|||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
name: 'api key',
|
||||
permissions: [Permission.All],
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
});
|
||||
|
|
@ -121,7 +172,7 @@ describe('/api-keys', () => {
|
|||
});
|
||||
|
||||
it('should require authorization', async () => {
|
||||
const { apiKey } = await create(user.accessToken);
|
||||
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||
const { status, body } = await request(app)
|
||||
.put(`/api-keys/${apiKey.id}`)
|
||||
.send({ name: 'new name' })
|
||||
|
|
@ -140,7 +191,7 @@ describe('/api-keys', () => {
|
|||
});
|
||||
|
||||
it('should update api key details', async () => {
|
||||
const { apiKey } = await create(user.accessToken);
|
||||
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||
const { status, body } = await request(app)
|
||||
.put(`/api-keys/${apiKey.id}`)
|
||||
.send({ name: 'new name' })
|
||||
|
|
@ -149,6 +200,7 @@ describe('/api-keys', () => {
|
|||
expect(body).toEqual({
|
||||
id: expect.any(String),
|
||||
name: 'new name',
|
||||
permissions: [Permission.All],
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
});
|
||||
|
|
@ -163,7 +215,7 @@ describe('/api-keys', () => {
|
|||
});
|
||||
|
||||
it('should require authorization', async () => {
|
||||
const { apiKey } = await create(user.accessToken);
|
||||
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||
const { status, body } = await request(app)
|
||||
.delete(`/api-keys/${apiKey.id}`)
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
|
@ -180,7 +232,7 @@ describe('/api-keys', () => {
|
|||
});
|
||||
|
||||
it('should delete an api key', async () => {
|
||||
const { apiKey } = await create(user.accessToken);
|
||||
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||
const { status } = await request(app)
|
||||
.delete(`/api-keys/${apiKey.id}`)
|
||||
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||
|
|
@ -190,14 +242,14 @@ describe('/api-keys', () => {
|
|||
|
||||
describe('authentication', () => {
|
||||
it('should work as a header', async () => {
|
||||
const { secret } = await create(admin.accessToken);
|
||||
const { secret } = await create(user.accessToken, [Permission.All]);
|
||||
const { status, body } = await request(app).get('/api-keys').set('x-api-key', secret);
|
||||
expect(body).toHaveLength(1);
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
|
||||
it('should work as a query param', async () => {
|
||||
const { secret } = await create(admin.accessToken);
|
||||
const { secret } = await create(user.accessToken, [Permission.All]);
|
||||
const { status, body } = await request(app).get(`/api-keys?apiKey=${secret}`);
|
||||
expect(body).toHaveLength(1);
|
||||
expect(status).toBe(200);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue