Skip to content

Commit

Permalink
tests: multiple tests implemented by simulating actual access token
Browse files Browse the repository at this point in the history
  • Loading branch information
artursudnik committed Jun 15, 2021
1 parent 37fb482 commit 74c14c5
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 35 deletions.
11 changes: 8 additions & 3 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/origin-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@
"shx": "0.3.3",
"chai": "4.3.0",
"@types/chai": "4.2.15",
"ts-node": "9.1.1"
"ts-node": "9.1.1",
"iam-client-lib": "3.0.0-alpha.20",
"jsonwebtoken": "8.5.1"
},
"files": [
"dist",
Expand Down
260 changes: 229 additions & 31 deletions packages/origin-backend/test/did-user.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@ import { DatabaseService } from '@energyweb/origin-backend-utils';
import { HttpStatus, INestApplication } from '@nestjs/common';
import { expect } from 'chai';
import request from 'supertest';
import { IAM, setCacheClientOptions, setChainConfig, ENSNamespaceTypes } from 'iam-client-lib';

import { OrganizationService } from '../src/pods/organization/organization.service';
import { UserService } from '../src/pods/user';
import { TUserBaseEntity, UserService } from '../src/pods/user';
import { bootstrapTestInstance } from './origin-backend';
import jwt from 'jsonwebtoken';
import { UserStatus } from '@energyweb/origin-backend-core';

describe('DID user e2e tests', function () {
this.timeout(5000);

describe('DID user e2e tests', () => {
let app: INestApplication;
let databaseService: DatabaseService;
let organizationService: OrganizationService;
let userService: UserService;
let iam: IAM;
let identityToken: string, did: string, didDocument: {};

before(async () => {
({
Expand All @@ -25,49 +32,180 @@ describe('DID user e2e tests', () => {
} = await bootstrapTestInstance());

await app.init();
});

beforeEach(async () => {
await databaseService.truncate('user', 'platform_organization', 'organization_invitation');
setCacheClientOptions(73799, {
url: 'https://volta-identitycache.energyweb.org/',
cacheServerSupportsAuth: true
});

setChainConfig(73799, {
rpcUrl: 'https://volta-rpc.energyweb.org'
});

expect(process.env.TEST_DID_USER_PRIVATE_KEY).to.exist;

iam = new IAM({
rpcUrl: 'https://volta-rpc.energyweb.org',
privateKey: process.env.TEST_DID_USER_PRIVATE_KEY
});

// this is equivalent of generating an identity token by signing transaction with Metamask or other wallet
({ identityToken, did, didDocument } = await iam.initializeConnection());

expect(identityToken).to.exist;
expect(did).to.exist;
expect(didDocument).to.exist;
});

after(async () => {
await app.close();
await iam?.closeConnection();
await app?.close();
});

describe('Anonymous user, without having a valid accessToken', function () {
it('should not be able to register as DID user', async function () {
this.skip(); // TODO: implement test
await request(app.getHttpServer())
.post('/user/register-did')
.send({
titleSelect: 'Mr',
titleInput: '',
firstName: 'a',
lastName: '1',
did: 'sdfsdf',
telephone: '123',
email: '[email protected]',
title: 'Mr'
})
.expect(HttpStatus.UNAUTHORIZED);
});
});

describe('Existing DID', function () {
describe('when not registered on Origin', function () {
it('should be able to login using auth/login-did endpoint', function () {
this.skip(); // TODO: implement test
it('should be able to login using auth/login-did endpoint', async function () {
const accessToken = await loginDidUser(app, identityToken);

expect(accessToken).to.exist;
});

it('should not be able to request guarded endpoints', function () {
this.skip(); // TODO: implement test
describe('when logged in as DID user', function () {
let accessToken: string;

before(async function () {
accessToken = generateAccessToken(did, [
'role1.roles.app-test2.apps.artur.iam.ewc',
'organizationadmin.roles.artur.iam.ewc'
]);
});

after(async function () {
databaseService.truncate('user');
});

it('should not be able to request endpoint covered with only JWT guard', async function () {
await request(app.getHttpServer())
.get('/user/me')
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatus.UNAUTHORIZED);
});

it('should be able to register an account on Origin using user/register-did endpoint', async function () {
await request(app.getHttpServer())
.post('/user/register-did')
.set('Authorization', `Bearer ${accessToken}`)
.send({
titleSelect: 'Mr',
titleInput: '',
firstName: 'DID',
lastName: did,
did,
telephone: '123',
email: '[email protected]',
title: 'Mr'
})
.expect(HttpStatus.CREATED);
});

it('access token should contain verifiedRoles field containing on-chain roles', async function () {
const accessToken = await loginDidUser(app, identityToken).catch((err) => {
throw new Error('precondition failed');
});

const accessTokenDecoded = jwt.verify(accessToken, process.env.JWT_SECRET) as {
did: string;
verifiedRoles: { name: string; namespace: string }[];
};

expect(accessTokenDecoded).to.contain.keys(['did', 'verifiedRoles']);
expect(accessTokenDecoded.verifiedRoles).to.be.an('array');

const onChainRoles = (await getDidRoles(iam, did)).sort(),
accessTokenRoles = accessTokenDecoded.verifiedRoles
.map((r) => r.namespace)
.sort();

accessTokenRoles.forEach((accTokenRole) =>
expect(onChainRoles).to.include(accTokenRole)
);

// TODO: implement check if all expected on-chain roles are included in the access token
});
});
});

describe('when registered on Origin and not activated yet', function () {
let accessToken: string, userId: string;

before(async function () {
databaseService.truncate('user');

it('should be able to register an account on Origin using user/register-did endpoint', function () {
this.skip(); // TODO: implement test
accessToken = generateAccessToken(did, [
'role1.roles.app-test2.apps.artur.iam.ewc',
'organizationadmin.roles.artur.iam.ewc'
]);

const res = await request(app.getHttpServer())
.post('/user/register-did')
.set('Authorization', `Bearer ${accessToken}`)
.send({
titleSelect: 'Mr',
titleInput: '',
firstName: 'DID',
lastName: did,
did,
telephone: '123',
email: '[email protected]',
title: 'Mr'
})
.expect(HttpStatus.CREATED);

userId = res.body.id;
});

describe('Origin REST API access token', function () {
it('should contain verifiedRoles field containing on-chain roles', function () {
this.skip(); // TODO: implement test
});
it('should be able to log in', async function () {
expect(accessToken).to.exist;
});

describe('then, user table record', function () {
it('should be able to request endpoint covered with only JWT guard', async function () {
const response = await request(app.getHttpServer())
.get('/user/me')
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatus.OK);
});

describe('corresponding user table record', function () {
let userRecord: TUserBaseEntity;

before(async () => {
userRecord = await userService.findByDid(did);
});

it('should be created', function () {
this.skip(); // TODO: implement test
expect(userRecord).to.exist;
});

it('should have did field set to on-chain did', function () {
this.skip(); // TODO: implement test
expect(userRecord.did).eq(did);
});

it('should have password field set to null', function () {
Expand All @@ -77,23 +215,83 @@ describe('DID user e2e tests', () => {
it('should have rights record set according to on-chain roles', function () {
this.skip(); // TODO: implement test
});
});
});

describe('when registered on Origin', function () {
it('should be able to log in', function () {
this.skip(); // TODO: implement test
it('should have rights field updated to reflect on-chain roles on every REST API request', function () {
this.skip(); // TODO: implement test
});
});

it('should be able to request guarded endpoints', function () {
this.skip(); // TODO: implement test
});
describe('after set active', function () {
before(async () => {
const user = await userService.findByDid(did);
user.status = UserStatus.Active;
await userService.update(user.id, user);
});

describe('user rights field', function () {
it('should be updated to reflect on-chain roles on every REST API request', function () {
this.skip(); // TODO: implement test
it('should be able to request endpoint covered with JWT and active user guards', async function () {
const response = await request(app.getHttpServer())
.get(`/user/${userId}`)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatus.OK);
});

it('should be able to request endpoint covered with JWT, active user and roles guards', async function () {
const response = await request(app.getHttpServer())
.post(`/invitation`)
.set('Authorization', `Bearer ${accessToken}`)
.send({
email: '[email protected]',
role: 4
})
.expect(HttpStatus.OK);
});
});
});
});
});

/**
* Generates an access token with exactly the same payload as an access token returned by
* the passport-did-auth strategy when requested with a valid DID identity token
*/
function generateAccessToken(did: string, roles: string[]) {
const payload = {
did,
verifiedRoles: roles.map((role) => {
const name = role.split('.')[0];
return {
name,
namespace: role
};
})
};

return jwt.sign(payload, process.env.JWT_SECRET);
}

async function loginDidUser(app: any, identityToken: string): Promise<string> {
const res = await request(app.getHttpServer())
.post('/auth/login-did')
.send({ identityToken })
.expect(HttpStatus.OK);

expect(res.body).to.contain.keys(['accessToken']);

return res.body.accessToken;
}

/**
* Returns an array of roles for a given DID,
* extracted from claims (IAM.getUserClaims)
*/
async function getDidRoles(iam: IAM, did: string): Promise<string[]> {
const userClaims = await iam.getUserClaims({ did });

return userClaims
.filter((claim) => !!claim.claimType) // getting only claims with claimType property
.map((claim) => claim.claimType)
.filter((claimType) => {
const arr = claimType.split('.');
return arr.length > 1 && arr[1] === ENSNamespaceTypes.Roles;
});
}

0 comments on commit 74c14c5

Please sign in to comment.