♻️(frontend) support Bearer in servers/y-provider

Support passing API Token as Bearer in the Authorization-header.

Signed-off-by: Stephan Meijer <me@stephanmeijer.com>
This commit is contained in:
Stephan Meijer
2025-07-04 14:12:20 +02:00
parent 78a6772bab
commit 0be366b7b6
3 changed files with 80 additions and 70 deletions

View File

@@ -26,7 +26,7 @@ describe('Server Tests', () => {
expect(response.status).toBe(401); expect(response.status).toBe(401);
expect(response.body).toStrictEqual({ expect(response.body).toStrictEqual({
error: 'Forbidden: Invalid API Key', error: 'Unauthorized: Invalid API Key',
}); });
}); });

View File

@@ -18,97 +18,7 @@ import {
COLLABORATION_SERVER_ORIGIN as origin, COLLABORATION_SERVER_ORIGIN as origin,
} from '../src/env'; } from '../src/env';
console.error = vi.fn(); const expectedBlocks = [
describe('Server Tests', () => {
test('POST /api/convert with incorrect API key should responds with 401', async () => {
const app = initApp();
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', 'wrong-api-key')
.set('content-type', 'application/json');
expect(response.status).toBe(401);
expect(response.body).toStrictEqual({
error: 'Forbidden: Invalid API Key',
});
});
test('POST /api/convert with a Bearer token', async () => {
const app = initApp();
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', 'Bearer test-secret-api-key')
.set('content-type', 'application/json');
// Warning: Changing the authorization header to Bearer token format will break backend compatibility with this microservice.
expect(response.status).toBe(401);
expect(response.body).toStrictEqual({
error: 'Forbidden: Invalid API Key',
});
});
test('POST /api/convert with missing body param content', async () => {
const app = initApp();
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', apiKey)
.set('content-type', 'application/json');
expect(response.status).toBe(400);
expect(response.body).toStrictEqual({
error: 'Invalid request: missing content',
});
});
test('POST /api/convert with body param content being an empty string', async () => {
const app = initApp();
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', apiKey)
.set('content-type', 'application/json')
.send('');
expect(response.status).toBe(400);
expect(response.body).toStrictEqual({
error: 'Invalid request: missing content',
});
});
test('POST /api/convert with correct content', async () => {
const app = initApp();
const document = [
'# Example document',
'',
'Lorem ipsum dolor sit amet.',
'',
].join('\n');
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', apiKey)
.set('content-type', 'application/json')
.send(document);
expect(response.status).toBe(200);
expect(response.body).toBeInstanceOf(Buffer);
const editor = ServerBlockNoteEditor.create();
const doc = new Y.Doc();
Y.applyUpdate(doc, response.body);
const blocks = editor.yDocToBlocks(doc, 'document-store');
expect(blocks).toStrictEqual([
{ {
children: [], children: [],
content: [ content: [
@@ -145,6 +55,99 @@ describe('Server Tests', () => {
}, },
type: 'paragraph', type: 'paragraph',
}, },
]); ];
console.error = vi.fn();
describe('Server Tests', () => {
test('POST /api/convert with incorrect API key responds with 401', async () => {
const app = initApp();
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', 'wrong-api-key')
.set('content-type', 'application/json');
expect(response.status).toBe(401);
expect(response.body).toStrictEqual({
error: 'Unauthorized: Invalid API Key',
}); });
});
test('POST /api/convert with incorrect Bearer token responds with 401', async () => {
const app = initApp();
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', 'Bearer test-secret-api-key')
.set('content-type', 'application/json');
expect(response.status).toBe(401);
expect(response.body).toStrictEqual({
error: 'Unauthorized: Invalid API Key',
});
});
test('POST /api/convert with missing body param content', async () => {
const app = initApp();
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', apiKey)
.set('content-type', 'application/json');
expect(response.status).toBe(400);
expect(response.body).toStrictEqual({
error: 'Invalid request: missing content',
});
});
test('POST /api/convert with body param content being an empty string', async () => {
const app = initApp();
const response = await request(app)
.post('/api/convert')
.set('origin', origin)
.set('authorization', apiKey)
.set('content-type', 'application/json')
.send('');
expect(response.status).toBe(400);
expect(response.body).toStrictEqual({
error: 'Invalid request: missing content',
});
});
test.each([[apiKey], [`Bearer ${apiKey}`]])(
'POST /api/convert with correct content with Authorization: %s',
async (authHeader) => {
const app = initApp();
const document = [
'# Example document',
'',
'Lorem ipsum dolor sit amet.',
'',
].join('\n');
const response = await request(app)
.post('/api/convert')
.set('Origin', origin)
.set('Authorization', authHeader)
.send(document);
expect(response.status).toBe(200);
expect(response.body).toBeInstanceOf(Buffer);
const editor = ServerBlockNoteEditor.create();
const doc = new Y.Doc();
Y.applyUpdate(doc, response.body);
const blocks = editor.yDocToBlocks(doc, 'document-store');
expect(blocks).toStrictEqual(expectedBlocks);
},
);
}); });

View File

@@ -24,12 +24,19 @@ export const httpSecurity = (
res: Response, res: Response,
next: NextFunction, next: NextFunction,
): void => { ): void => {
// Secret API Key check let apiKey = req.headers['authorization'];
// Note: Changing this header to Bearer token format will break backend compatibility with this microservice.
const apiKey = req.headers['authorization'];
if (!apiKey || !VALID_API_KEYS.includes(apiKey)) { if (!apiKey) {
res.status(401).json({ error: 'Forbidden: Invalid API Key' }); res.status(401).json({ error: 'Unauthorized: No credentials given' });
return;
}
if (apiKey?.startsWith('Bearer ')) {
apiKey = apiKey.slice('Bearer '.length);
}
if (!VALID_API_KEYS.includes(apiKey)) {
res.status(401).json({ error: 'Unauthorized: Invalid API Key' });
return; return;
} }