Skip to content

Commit

Permalink
fixes tests and adds back ENABLE_HTTPS_FOR_DEV environment variable
Browse files Browse the repository at this point in the history
  • Loading branch information
kezike committed Apr 20, 2024
1 parent acc1696 commit 26c9f95
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 77 deletions.
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
**/*.env
**/*.env
node_modules
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CRED_STATUS_SERVICE=mongodb
CRED_STATUS_DID_SEED=z1AackbUm8U69ohKnihoRRFkXcXJd4Ra1PkAboQ2ZRy1ngB
PORT=4008 # default port is 4008
ENABLE_ACCESS_LOGGING=true
ENABLE_HTTPS_FOR_DEV=false
ERROR_LOG_FILE=logs/error.log
ALL_LOG_FILE=logs/all.log
CONSOLE_LOG_LEVEL=silly # default is silly, i.e. log everything - see the README for allowed levels
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ This service provides support for managing credential status in a variety of dat
| `CRED_STATUS_DID_SEED` | seed used to deterministically generate DID | string | yes |
| `PORT` | HTTP port on which to run the express app | number | no (default: `4008`) |
| `ENABLE_ACCESS_LOGGING` | whether to enable access logging (see [Logging](#logging)) | boolean | no (default: `true`) |
| `ENABLE_HTTPS_FOR_DEV` | whether to enable HTTPS in a development instance of the app | boolean | no (default: `true`) |
| `ERROR_LOG_FILE` | log file for all errors (see [Logging](#logging)) | string | no |
| `ALL_LOG_FILE` | log file for everything (see [Logging](#logging)) | string | no |
| `CONSOLE_LOG_LEVEL` | console log level (see [Logging](#logging)) | `error` \| `warn`\| `info` \| `http` \| `verbose` \| `debug` \| `silly` | no (default: `silly`) |
Expand Down Expand Up @@ -210,7 +211,7 @@ NOTE: CURL can get a bit clunky if you want to experiment more (e.g., by changin

### Revoke

Revocation is fully explained in the Bitstring Status List specification and our implemenations thereof, but effectively, it amounts to POSTing an object to the revocation endpoint, like so:
Revocation and suspension are fully explained in the [Bitstring Status List](https://www.w3.org/TR/vc-bitstring-status-list/) specification and our implemenations thereof, but effectively, it amounts to POSTing an object to the revocation endpoint, like so:

```
{credentialId: '23kdr', credentialStatus: [{type: 'BitstringStatusListCredential', status: 'revoked'}]}
Expand Down
24 changes: 19 additions & 5 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { build } from './src/app.js'
import { getConfig, setConfig } from './src/config.js';
import { build } from './src/app.js';
import { getConfig } from './src/config.js';
import http from 'http';
import https from 'https';
import logger from './src/utils/logger.js';

const run = async () => {
await setConfig()
const { port } = getConfig();
const { port, enableHttpsForDev } = getConfig();
const app = await build();
http.createServer(app).listen(port, () => console.log(`Server running on port ${port}`));

if (enableHttpsForDev) {
https
.createServer(
{
key: fs.readFileSync('server-dev-only.key'),
cert: fs.readFileSync('server-dev-only.cert')
},
app
).listen(port, () => logger.info(`Server running on port ${port} with https`));
} else {
http
.createServer(app).listen(port, () => logger.info(`Server running on port ${port} with http`));
}
};

run();
100 changes: 73 additions & 27 deletions src/app.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { expect } from 'chai';
import sinon from 'sinon';
import request from 'supertest';
import { getUnsignedVC, getUnsignedVCWithStatus, getValidStatusUpdateBody, getInvalidStatusUpdateBody } from './test-fixtures/fixtures.js';
import {
validCredentialId,
invalidCredentialId,
invalidCredentialIdErrorMessage,
getUnsignedVC,
getUnsignedVCWithStatus,
getValidStatusUpdateBody,
getInvalidStatusUpdateBody
} from './test-fixtures/fixtures.js';
import status from './status.js';
import { build } from './app.js';

const allocateEndpoint = '/credentials/status/allocate';
const updateEndpoint = '/credentials/status';
const missingCredIdErrorMessage = 'Unable to find credential with given ID';
const emptyStatusManagerStub = {};

describe('api', () => {
Expand All @@ -20,20 +27,20 @@ describe('api', () => {
.get('/');

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.eql(200);
expect(response.body.message).to.eql('status-service-db server status: ok.');
expect(response.status).to.equal(200);
expect(response.body.message).to.equal('status-service-db server status: ok.');
});
});

describe('GET /unknown', () => {
describe('GET /unknown/path', () => {
it('unknown endpoint returns 404', async () => {
await status.initializeStatusManager(emptyStatusManagerStub);
const app = await build();

const response = await request(app)
.get('/unknown');
.get('/unknown/path');

expect(response.status).to.eql(404);
expect(response.status).to.equal(404);
}, 10000);
});

Expand All @@ -46,13 +53,13 @@ describe('api', () => {
.post(allocateEndpoint);

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.eql(400);
expect(response.status).to.equal(400);
});

it('returns updated credential', async () => {
const unsignedVCWithStatus = getUnsignedVCWithStatus();
const allocateStatus = sinon.fake.returns(unsignedVCWithStatus);
const statusManagerStub = { allocateStatus };
const allocateSupportedStatuses = sinon.fake.returns(unsignedVCWithStatus);
const statusManagerStub = { allocateSupportedStatuses };
await status.initializeStatusManager(statusManagerStub);
const app = await build();

Expand All @@ -61,7 +68,7 @@ describe('api', () => {
.send(getUnsignedVC());

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.eql(200);
expect(response.status).to.equal(200);
expect(response.body).to.eql(unsignedVCWithStatus);
});

Expand All @@ -76,7 +83,7 @@ describe('api', () => {
.send(getUnsignedVCWithStatus());

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.eql(200);
expect(response.status).to.equal(200);
expect(response.body).to.eql(getUnsignedVCWithStatus());
});
});
Expand All @@ -90,39 +97,78 @@ describe('api', () => {
.post(updateEndpoint);

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.eql(400);
expect(response.status).to.equal(400);
});

it('returns update from revoked credential', async () => {
const revokeCredential = sinon.fake.returns({
code: 200,
message: 'Credential successfully revoked.'
});
const statusManagerStub = { revokeCredential };
await status.initializeStatusManager(statusManagerStub);
const app = await build();

const response = await request(app)
.post(updateEndpoint)
.send(getValidStatusUpdateBody(validCredentialId, 'revoked'));

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.equal(200);
expect(response.body.message).to.equal('Credential successfully revoked.');
});

it('returns update from suspended credential', async () => {
const suspendCredential = sinon.fake.returns({
code: 200,
message: 'Credential successfully suspended.'
});
const statusManagerStub = { suspendCredential };
await status.initializeStatusManager(statusManagerStub);
const app = await build();

const response = await request(app)
.post(updateEndpoint)
.send(getValidStatusUpdateBody(validCredentialId, 'suspended'));

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.equal(200);
expect(response.body.message).to.equal('Credential successfully suspended.');
});

it('returns update from status manager', async () => {
const updateStatus = sinon.fake.returns({ code: 200, message: 'Credential status successfully updated.' });
const statusManagerStub = { updateStatus };
it('returns update from unsuspended credential', async () => {
const unsuspendCredential = sinon.fake.returns({
code: 200,
message: 'Credential successfully unsuspended.'
});
const statusManagerStub = { unsuspendCredential };
await status.initializeStatusManager(statusManagerStub);
const app = await build();

const response = await request(app)
.post(updateEndpoint)
.send(getValidStatusUpdateBody());
.send(getValidStatusUpdateBody(validCredentialId, 'unsuspended'));

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.eql(200);
expect(response.body.message).to.eql('Credential status successfully updated.');
expect(response.status).to.equal(200);
expect(response.body.message).to.equal('Credential successfully unsuspended.');
});

it('returns 404 for unknown cred id', async () => {
// const allocateStatus = sinon.fake.returns(getUnsignedVCWithStatus())
const updateStatus = sinon.fake.rejects(missingCredIdErrorMessage);
const statusManagerStub = { updateStatus };
it('returns 404 for unknown credential id', async () => {
const missingCredentialError = new Error(invalidCredentialIdErrorMessage);
missingCredentialError.code = 404;
const revokeCredential = sinon.fake.rejects(missingCredentialError);
const statusManagerStub = { revokeCredential };
await status.initializeStatusManager(statusManagerStub);
const app = await build();

const response = await request(app)
.post(updateEndpoint)
.send(getInvalidStatusUpdateBody());
.send(getInvalidStatusUpdateBody(invalidCredentialId, 'revoked'));

expect(response.header['content-type']).to.have.string('json');
expect(response.status).to.eql(404);
console.log(response.body.message);
expect(response.body.message).to.contain('An error occurred in status-service-db: Credential ID not found.');
expect(response.status).to.equal(404);
expect(response.body.message).to.contain('Unable to find credential with ID');
});
});
});
24 changes: 10 additions & 14 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ export function setConfig() {
}

function getBooleanValue(value) {
value = value?.toLocaleLowerCase();
if (
value === true ||
value === 1 ||
value === 'true' ||
value === '1' ||
value === 'yes' ||
value === 'y'
) {
return true;
} else if (
value === false ||
value === 0 ||
value === 'false' ||
value === '0' ||
value === 'no' ||
Expand All @@ -31,22 +28,21 @@ function getBooleanValue(value) {
return true;
}

function getGeneralEnvs() {
const env = process.env;
function getGeneralEnvs(env) {
return {
port: env.PORT ? parseInt(env.PORT) : defaultPort,
credStatusService: env.CRED_STATUS_SERVICE,
credStatusDidSeed: env.CRED_STATUS_DID_SEED,
consoleLogLevel: env.CONSOLE_LOG_LEVEL?.toLocaleLowerCase() || defaultConsoleLogLevel,
logLevel: env.LOG_LEVEL?.toLocaleLowerCase() || defaultLogLevel,
consoleLogLevel: env.CONSOLE_LOG_LEVEL?.toLocaleLowerCase() ?? defaultConsoleLogLevel,
logLevel: env.LOG_LEVEL?.toLocaleLowerCase() ?? defaultLogLevel,
enableAccessLogging: getBooleanValue(env.ENABLE_ACCESS_LOGGING),
enableHttpsForDev: getBooleanValue(env.ENABLE_HTTPS_FOR_DEV),
errorLogFile: env.ERROR_LOG_FILE,
allLogFile: env.ALL_LOG_FILE
};
}

function getMongoDbEnvs() {
const env = process.env;
function getMongoDbEnvs(env) {
return {
statusCredSiteOrigin: env.STATUS_CRED_SITE_ORIGIN,
credStatusDatabaseUrl: env.CRED_STATUS_DB_URL,
Expand All @@ -64,16 +60,16 @@ function getMongoDbEnvs() {
}

function parseConfig() {
const env = process.env
const env = process.env;
let serviceSpecificEnvs;
switch (env.CRED_STATUS_SERVICE) {
case 'mongodb':
serviceSpecificEnvs = getMongoDbEnvs();
serviceSpecificEnvs = getMongoDbEnvs(env);
break;
default:
throw new Error('Encountered unsupported credential status service');
throw new Error(`Encountered unsupported credential status service: ${env.CRED_STATUS_SERVICE}`);
}
const generalEnvs = getGeneralEnvs();
const generalEnvs = getGeneralEnvs(env);
const config = Object.freeze({
...generalEnvs,
...serviceSpecificEnvs
Expand Down
2 changes: 1 addition & 1 deletion src/middleware/errorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const errorHandler = (error, request, response, next) => {
// for more detail

const code = error.code | 500;
const message = `An error occurred in status-service-db: ${error.message || 'unknown error.'} See the logs for full details. If you are using docker compose, view the logs with 'docker compose logs', and just the status service logs with: 'docker compose logs status-service-db'`;
const message = `An error occurred in status-service-db: ${error.message ?? 'unknown error.'} See the logs for full details. If you are using docker compose, view the logs with 'docker compose logs', and just the status service logs with: 'docker compose logs status-service-db'`;
const errorResponse = {code, message};
response.header('Content-Type', 'application/json');
return response.status(error.code).json(errorResponse);
Expand Down
12 changes: 6 additions & 6 deletions src/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async function initializeStatusManager(statusManager) {
STATUS_LIST_MANAGER = await createMongoDbStatusManager();
break;
default:
throw new Error('Encountered unsupported credential status service');
throw new Error(`Encountered unsupported credential status service: ${credStatusService}`);
}
}

Expand All @@ -86,20 +86,20 @@ async function updateStatus(credentialId, credentialStatus) {
switch (credentialStatus) {
case 'revoked':
await statusManager.revokeCredential(credentialId);
return { code: 200, message: 'Credential status successfully revoked.' };
return { code: 200, message: 'Credential successfully revoked.' };
case 'suspended':
await statusManager.suspendCredential(credentialId);
return { code: 200, message: 'Credential status successfully suspended.' };
return { code: 200, message: 'Credential successfully suspended.' };
case 'unsuspended':
await statusManager.unsuspendCredential(credentialId);
return { code: 200, message: 'Credential status successfully unsuspended.' };
return { code: 200, message: 'Credential successfully unsuspended.' };
default:
return { code: 400, message: `Unsupported credential status: "${credentialStatus}"` };
}
} catch (error) {
return {
code: error.code || 500,
message: error.message ||
code: error.code ?? 500,
message: error.message ??
`Unable to apply status "${credentialStatus}" to credential with ID "${credentialId}".`
};
}
Expand Down
17 changes: 13 additions & 4 deletions src/test-fixtures/.env.testing
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# PORT=4008 #default port is 4008

# the CRED_STATUS_* values are used to instantiate the status list manager
CRED_STATUS_REPO_OWNER=jchartrand
CRED_STATUS_REPO_NAME=status-test-three
CRED_STATUS_META_REPO_NAME=status-test-meta-three
CRED_STATUS_ACCESS_TOKEN=
CRED_STATUS_SERVICE=mongodb
STATUS_CRED_SITE_ORIGIN=https://credentials.example.edu
CRED_STATUS_DB_URL=mongodb+srv://user:[email protected]?retryWrites=false
CRED_STATUS_DB_HOST=domain.mongodb.net # ignored if CRED_STATUS_DB_URL is configured
CRED_STATUS_DB_PORT=27017 # ignored if CRED_STATUS_DB_URL is configured
CRED_STATUS_DB_USER=testuser # ignored if CRED_STATUS_DB_URL is configured
CRED_STATUS_DB_PASS=testpass # ignored if CRED_STATUS_DB_URL is configured
CRED_STATUS_DB_NAME=
STATUS_CRED_TABLE_NAME=
USER_CRED_TABLE_NAME=
CONFIG_TABLE_NAME=
EVENT_TABLE_NAME=
CRED_EVENT_TABLE_NAME=
CRED_STATUS_DID_SEED=z1AackbUm8U69ohKnihoRRFkXcXJd4Ra1PkAboQ2ZRy1ngB

ERROR_LOG_FILE=logs/error.log
Expand Down
Loading

0 comments on commit 26c9f95

Please sign in to comment.