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 8c75383
Show file tree
Hide file tree
Showing 5 changed files with 216 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
94 changes: 94 additions & 0 deletions test/05-permissions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as supertest from 'supertest';
import { expect } from 'chai';
const fixturePath = __dirname + '/fixtures/04-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');
describe('04 basic permission tests', function () {
let pineServer: Awaited<ReturnType<typeof testInit>>;
let request;
before(async () => {
pineServer = await testInit(fixturePath, true);
request = supertest.agent(testLocalServer);
});

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

describe('Basic', () => {
it('check /ping route is OK', async () => {
await request.get('/ping').expect(200, 'OK');
});
});

describe.only('university vocabular', () => {
it('check /university/student is served by pinejs', async () => {
const res = await request
.set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64)
.get('/university/student')
.expect(200);
expect(res.body)
.to.be.an('object')
.that.has.ownProperty('d')
.to.be.an('array');
});

it('create a student', async () => {
await request
.set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64)
.post('/university/student')
.send({
matrix_number: 1,
name: 'John',
lastname: 'Doe',
birthday: new Date(),
semester_credits: 10,
})
.expect(201);
});

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

it('should fail to create a student with same matrix number ', async () => {
await request
.set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64)
.post('/university/student')
.send({
matrix_number: 1,
name: 'John',
lastname: 'Doe',
birthday: new Date(),
semester_credits: 10,
})
.expect(409);
});

it('should fail to create a student with too few semester credits ', async () => {
const res = await request
.set('Authorization', 'Basic ' + basicStudentAuthHeaderBase64)
.post('/university/student')
.send({
matrix_number: 2,
name: 'Jenny',
lastname: 'Dea',
birthday: new Date(),
semester_credits: 2,
})
.expect(400);
expect(res.body)
.to.be.a('string')
.that.equals(
'It is necessary that 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.',
);
});
});
});
36 changes: 36 additions & 0 deletions test/fixtures/04-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: ['student.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;
36 changes: 36 additions & 0 deletions test/fixtures/04-permissions/university.sbvr
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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: 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 8c75383

Please sign in to comment.