Skip to content

Commit

Permalink
Add basic auth header middleware
Browse files Browse the repository at this point in the history
Add basic permission tests

Change-type: minor
Signed-off-by: fisehara <[email protected]>
  • Loading branch information
fisehara committed May 9, 2023
1 parent c3a119f commit 6a47a9a
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 1 deletion.
46 changes: 46 additions & 0 deletions src/sbvr-api/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,52 @@ export const customAuthorizationMiddleware = (expectedScheme = 'Bearer') => {
// A default bearer middleware for convenience
export const authorizationMiddleware = customAuthorizationMiddleware();

export const resolveBasicAuthHeader = async (
req: Express.Request,
expectedScheme = 'Basic',
): Promise<PermissionReq['user']> => {
const auth = req.header('Authorization');
if (!auth) {
return;
}

const parts = auth.split(' ');
if (parts.length !== 2) {
return;
}

const [scheme, basicAuthContentBase64] = parts;
if (scheme.toLowerCase() !== expectedScheme.toLowerCase()) {
return;
}

const basicAuthContent = Buffer.from(basicAuthContentBase64, 'base64')
.toString()
.trim();
const [username, password] = basicAuthContent.split(';');
return checkPassword(username, password);
};

export const basicUserPasswordAuthorizationMiddleware = (
expectedScheme = 'Basic',
) => {
expectedScheme = expectedScheme.toLowerCase();
return async (
req: Express.Request,
_res?: Express.Response,
next?: Express.NextFunction,
): Promise<void> => {
try {
const user = await resolveBasicAuthHeader(req, expectedScheme);
if (user) {
req.user = user;
}
} finally {
next?.();
}
};
};

export const resolveApiKey = async (
req: HookReq | Express.Request,
paramName = 'apikey',
Expand Down
104 changes: 104 additions & 0 deletions test/05-permissions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as supertest from 'supertest';
const fixturePath = __dirname + '/fixtures/05-permissions/config';
import { testInit, testDeInit, testLocalServer } from './lib/test-init';

const basicStudentAuthHeaderBase64 =
Buffer.from('student;student').toString('base64');
const basicAdminAuthHeaderBase64 =
Buffer.from('admin;admin').toString('base64');

const differentUsers = [
{ name: 'student', basicBase64: basicStudentAuthHeaderBase64 },
{ name: 'admin', basicBase64: basicAdminAuthHeaderBase64 },
];

describe('05 basic permission tests', function () {
let pineServer: Awaited<ReturnType<typeof testInit>>;
let request: any;
before(async () => {
pineServer = await testInit({ configPath: fixturePath, deleteDb: true });
});

beforeEach(async () => {
request = supertest.agent(testLocalServer);
});

after(async () => {
await testDeInit(pineServer);
});

for (const [idx, user] of differentUsers.entries()) {
it(`should create a student as ${user.name} `, async () => {
await request
.set('Authorization', 'Basic ' + user.basicBase64)
.post('/university/student')
.send({
matrix_number: idx,
name: 'John',
lastname: user.name,
birthday: new Date(),
semester_credits: 10,
})
.expect(201);
});

it(`should read all students as ${user.name} `, async () => {
await request
.set('Authorization', 'Basic ' + user.basicBase64)
.get('/university/student(1)')
.expect(200);
});

it(`should update a student as ${user.name} `, async () => {
await request
.set('Authorization', 'Basic ' + user.basicBase64)
.patch('/university/student(1)')
.send({
name: 'Johnny',
})
.expect(200);
});
}

it(`should not allow to get students as guest `, async () => {
await request.get('/university/student').expect(401);
});

it('should not allow to delete a student as student', async () => {
await request
.set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64)
.delete('/university/student(1)')
.expect(401);
});

it('should not allow to create a faculty as student', async () => {
await request
.set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64)
.post('/university/faculty')
.send({
name: 'physics',
})
.expect(401);
});

it('should allow to create a faculty as admin', async () => {
await request
.set('Authorization', 'Basic ' + basicAdminAuthHeaderBase64)
.post('/university/faculty')
.send({
name: 'physics',
})
.expect(201);
});

it('should allow to delete a student as admin', async () => {
await request
.set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64)
.delete('/university/student(1)')
.expect(401);
});

it(`should allow to get faculties as guest `, async () => {
await request.get('/university/faculty').expect(200);
});
});
36 changes: 36 additions & 0 deletions test/fixtures/05-permissions/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { ConfigLoader } from '../../../src/server-glue/module';

const apiRoot = 'university';
const modelName = 'university';
const modelFile = __dirname + '/university.sbvr';

export default {
models: [
{
modelName,
modelFile,
apiRoot,
},
],
users: [
{
username: 'guest',
password: ' ',
permissions: ['university.faculty.read'],
},
{
username: 'student',
password: 'student',
permissions: [
'university.student.read',
'university.student.create',
'university.student.update',
],
},
{
username: 'admin',
password: 'admin',
permissions: ['resource.all'],
},
],
} as ConfigLoader.Config;
42 changes: 42 additions & 0 deletions test/fixtures/05-permissions/university.sbvr
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Vocabulary: university

Term: name
Concept Type: Short Text (Type)

Term: lastname
Concept Type: Short Text (Type)

Term: birthday
Concept Type: Date Time (Type)

Term: semester credits
Concept Type: Integer (Type)

Term: matrix number
Concept Type: Integer (Type)


Term: faculty

Fact Type: faculty has name
Necessity: each faculty has exactly one name
Necessity: each name is of exactly one faculty

Term: student

Fact Type: student has matrix number
Necessity: each student has exactly one matrix number
Necessity: each matrix number is of exactly one student

Fact Type: student has name
Necessity: each student has exactly one name

Fact Type: student has lastname
Necessity: each student has exactly one lastname

Fact Type: student has birthday
Necessity: each student has exactly one birthday

Fact Type: student has semester credits
Necessity: each student has at most one semester credits
Necessity: each student that has a semester credits, has a semester credits that is greater than or equal to 4 and is less than or equal to 16.
5 changes: 4 additions & 1 deletion test/lib/pine-init.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as express from 'express';
import { exit } from 'process';
import * as pine from '../../src/server-glue/module';
import { basicUserPasswordAuthorizationMiddleware } from '../../src/sbvr-api/permissions';

export type PineTestOptions = {
configPath: string;
Expand Down Expand Up @@ -34,13 +35,15 @@ export async function init(

try {
await cleanInit(deleteDb);
app.use(basicUserPasswordAuthorizationMiddleware());
await pine.init(app, initConfig);

// register default auth middleware for bearer token
await new Promise((resolve) => {
app.listen(initPort, () => {
resolve('server started');
});
});
return app;
} catch (e) {
console.log(`pineInit ${e}`);
exit(1);
Expand Down

0 comments on commit 6a47a9a

Please sign in to comment.