Skip to content

Commit

Permalink
Merge pull request #15 from logion-network/feature/human-readable-sign
Browse files Browse the repository at this point in the history
Verify signature against human-readable message.
  • Loading branch information
benoitdevos authored Jul 1, 2024
2 parents 12176d8 + 1de9859 commit 5a6d35a
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 25 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@logion/rest-api-core",
"version": "0.5.2",
"version": "0.6.0-1",
"repository": {
"type": "git",
"url": "git+https://github.com/logion-network/logion-rest-api-core.git"
Expand All @@ -26,7 +26,7 @@
"coverage": "nyc yarn run test"
},
"dependencies": {
"@logion/authenticator": "^0.6.2",
"@logion/authenticator": "^0.7.0-1",
"dinoloop": "^2.4.0",
"express": "^4.18.2",
"express-fileupload": "^1.4.0",
Expand All @@ -39,7 +39,7 @@
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@logion/node-api": "^0.31.0",
"@logion/node-api": "^0.31.2",
"@tsconfig/node16": "^16.1.1",
"@types/express": "^4.17.14",
"@types/express-fileupload": "^1.4.1",
Expand Down
29 changes: 29 additions & 0 deletions src/AuthenticationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function fillInSpecForAuthenticationController(spec: OpenAPIV3.Document):

AuthenticationController.signIn(spec);
AuthenticationController.authenticate(spec);
AuthenticationController.authenticateV2(spec);
}

@injectable()
Expand Down Expand Up @@ -114,6 +115,34 @@ export class AuthenticationController extends ApiController {
return this.toAuthenticateResponseView(tokens);
}

static authenticateV2(spec: OpenAPIV3.Document) {
const operationObject = requireDefined(spec.paths["/api/auth/{sessionId}/authenticate/v2"].post);
operationObject.summary = "Authenticate the given session";
operationObject.description = "<p>The signature's resource is <code>authentication</code>, the operation <code>login</code> and the additional field is <code>sessionId</code><p>";
operationObject.requestBody = getRequestBody({
description: "Authentication data",
view: "AuthenticateRequestView",
});
operationObject.responses = getDefaultResponses("AuthenticateResponseView");
setPathParameters(operationObject, { 'sessionId': "The ID of the session to authenticate" });
}

@HttpPost('/:sessionId/authenticate/v2')
@Async()
async authenticateV2(
authenticateRequest: AuthenticateRequestView,
sessionId: string
): Promise<AuthenticateResponseView> {

const { session, signatures } = await this.sessionAndSignatures(authenticateRequest, sessionId);
const { sessionManager, authenticator } = await this.authenticationService.authenticationSystem();

const signedSession = await sessionManager.signedSessionOrThrowV2(session, signatures);
const tokens = await authenticator.createTokens(signedSession, DateTime.now());

return this.toAuthenticateResponseView(tokens);
}

private async sessionAndSignatures(
authenticateRequest: AuthenticateRequestView,
sessionId: string
Expand Down
159 changes: 147 additions & 12 deletions test/AuthenticationController.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const TOKEN_ALICE = "some-fake-token-for-ALICE";
const TOKEN_BOB = "some-fake-token-for-BOB";
const SESSION_ID = "a4dade1d-f12c-414c-93f7-7f20ce1e2cb8";

describe("AuthenticationController", () => {
describe("AuthenticationController (sign-in)", () => {

it('should sign-in successfully', async () => {
const app = setupApp(AuthenticationController, mockDependenciesForSignIn);
Expand All @@ -55,6 +55,12 @@ describe("AuthenticationController", () => {
});
});

})

describe("AuthenticationController (authentication)", () => {

const version = "V1";

it('should authenticate successfully', async () => {

const authenticateRequest: AuthenticateRequestView = {
Expand All @@ -70,7 +76,7 @@ describe("AuthenticationController", () => {
signedOn: TIMESTAMP,
type: "POLKADOT",
};
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container,true, true));
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container, true, true, version));
await request(app)
.post(`/api/auth/${SESSION_ID}/authenticate`)
.send(authenticateRequest)
Expand Down Expand Up @@ -98,7 +104,7 @@ describe("AuthenticationController", () => {
signedOn: TIMESTAMP,
type: "POLKADOT",
};
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container,false, true));
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container, false, true, version));
await request(app)
.post(`/api/auth/${SESSION_ID}/authenticate`)
.send(authenticateRequest)
Expand All @@ -124,7 +130,7 @@ describe("AuthenticationController", () => {
signedOn: TIMESTAMP,
type: "POLKADOT",
};
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container,true, false));
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container, true, false, version));
await request(app)
.post(`/api/auth/${SESSION_ID}/authenticate`)
.send(authenticateRequest)
Expand All @@ -150,7 +156,7 @@ describe("AuthenticationController", () => {
signedOn: TIMESTAMP,
type: "POLKADOT",
};
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container,false, true));
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container, false, true, version));
await request(app)
.post(`/api/auth/${SESSION_ID}/authenticate`)
.send(authenticateRequest)
Expand All @@ -162,6 +168,119 @@ describe("AuthenticationController", () => {
})


});

describe("AuthenticationController (authentication v2)", () => {

const version = "V2";

it('should authenticate successfully', async () => {

const authenticateRequest: AuthenticateRequestView = {
signatures: {}
};
authenticateRequest.signatures![ALICE.toKey()] = {
signature: "signature-ALICE",
signedOn: TIMESTAMP,
type: "POLKADOT",
};
authenticateRequest.signatures![BOB.toKey()] = {
signature: "signature-BOB",
signedOn: TIMESTAMP,
type: "POLKADOT",
};
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container, true, true, version));
await request(app)
.post(`/api/auth/${SESSION_ID}/authenticate/v2`)
.send(authenticateRequest)
.expect(200)
.expect('Content-Type', /application\/json/)
.then(response => {
expect(response.body.tokens).toBeDefined();
expect(response.body.tokens[ALICE.toKey()].value).toBe(TOKEN_ALICE);
expect(response.body.tokens[BOB.toKey()].value).toBe(TOKEN_BOB);
});
})

it('should fail to authenticate on wrong signature', async () => {

const authenticateRequest: AuthenticateRequestView = {
signatures: {}
};
authenticateRequest.signatures![ALICE.toKey()] = {
signature: "signature-ALICE",
signedOn: TIMESTAMP,
type: "POLKADOT",
};
authenticateRequest.signatures![BOB.toKey()] = {
signature: "signature-BOB",
signedOn: TIMESTAMP,
type: "POLKADOT",
};
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container, false, true, version));
await request(app)
.post(`/api/auth/${SESSION_ID}/authenticate/v2`)
.send(authenticateRequest)
.expect(401)
.expect('Content-Type', /application\/json/)
.then(response => {
expect(response.body.error).toBe("Invalid signature");
});
})

it('should fail to authenticate on missing session', async () => {

const authenticateRequest: AuthenticateRequestView = {
signatures: {}
};
authenticateRequest.signatures![ALICE.toKey()] = {
signature: "signature-ALICE",
signedOn: TIMESTAMP,
type: "POLKADOT",
};
authenticateRequest.signatures![BOB.toKey()] = {
signature: "signature-BOB",
signedOn: TIMESTAMP,
type: "POLKADOT",
};
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container, true, false, version));
await request(app)
.post(`/api/auth/${SESSION_ID}/authenticate/v2`)
.send(authenticateRequest)
.expect(401)
.expect('Content-Type', /application\/json/)
.then(response => {
expect(response.body.error).toBe("Invalid session");
});
})

it('should fail to authenticate on wrong address type', async () => {

const authenticateRequest: AuthenticateRequestView = {
signatures: {}
};
authenticateRequest.signatures![`Unknown:${ ALICE.address }`] = {
signature: "signature-ALICE",
signedOn: TIMESTAMP,
type: "POLKADOT",
};
authenticateRequest.signatures![`Unknown:${ BOB.address }`] = {
signature: "signature-BOB",
signedOn: TIMESTAMP,
type: "POLKADOT",
};
const app = setupApp(AuthenticationController, (container) => mockDependenciesForAuth(container, false, true, version));
await request(app)
.post(`/api/auth/${SESSION_ID}/authenticate/v2`)
.send(authenticateRequest)
.expect(401)
.expect('Content-Type', /application\/json/)
.then(response => {
expect(response.body.error).toBe("Error: Unsupported key format");
});
})


});

function mockDependenciesForSignIn(container: Container): void {
Expand Down Expand Up @@ -191,7 +310,7 @@ function mockDependenciesForSignIn(container: Container): void {
.returns(() => Promise.resolve());
}

function mockDependenciesForAuth(container: Container, verifies: boolean, sessionExists:boolean): void {
function mockDependenciesForAuth(container: Container, verifies: boolean, sessionExists:boolean, version: "V1" | "V2"): void {

const sessionAlice = new Mock<SessionAggregateRoot>();
sessionAlice.setup(instance => instance.createdOn).returns(DateTime.now().toJSDate());
Expand Down Expand Up @@ -238,10 +357,17 @@ function mockDependenciesForAuth(container: Container, verifies: boolean, sessio
type: "POLKADOT",
}
];
sessionManager.setup(instance => instance.signedSessionOrThrow(It.IsAny(), It.IsAny())).returnsAsync({
session: session.object(),
signatures
});
if (version === "V1") {
sessionManager.setup(instance => instance.signedSessionOrThrow(It.IsAny(), It.IsAny())).returnsAsync({
session: session.object(),
signatures
});
} else {
sessionManager.setup(instance => instance.signedSessionOrThrowV2(It.IsAny(), It.IsAny())).returnsAsync({
session: session.object(),
signatures
});
}
const tokens: Token[] = [
{
type: "Polkadot",
Expand All @@ -260,8 +386,17 @@ function mockDependenciesForAuth(container: Container, verifies: boolean, sessio
args => args.session === session.object() && args.signatures === signatures
), It.IsAny())).returnsAsync(tokens);
} else {
sessionManager.setup(instance => instance.signedSessionOrThrow)
.returns(() => { throw new UnauthorizedException({error: "Invalid signature"}) });
if (version === "V1") {
sessionManager.setup(instance => instance.signedSessionOrThrow)
.returns(() => {
throw new UnauthorizedException({ error: "Invalid signature" })
});
} else {
sessionManager.setup(instance => instance.signedSessionOrThrowV2)
.returns(() => {
throw new UnauthorizedException({ error: "Invalid signature" })
});
}
}

const sessionRepository = new Mock<SessionRepository>();
Expand Down
20 changes: 10 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -564,9 +564,9 @@ __metadata:
languageName: node
linkType: hard

"@logion/authenticator@npm:^0.6.2":
version: 0.6.2
resolution: "@logion/authenticator@npm:0.6.2"
"@logion/authenticator@npm:^0.7.0-1":
version: 0.7.0-1
resolution: "@logion/authenticator@npm:0.7.0-1"
dependencies:
"@ethersproject/transactions": ^5.7.0
"@multiversx/sdk-core": ^12.19.1
Expand All @@ -578,13 +578,13 @@ __metadata:
web3-utils: ^4.2.1
peerDependencies:
"@logion/node-api": 0.x
checksum: 46a5fbe4b671255c78c5bf4ff6799f0903783f2c2457023c8862e8360d9e6736cfb3a2a707325bf33f2b47f9ac92a2bdcf80b48c1f4b0fb80659e9a8e8c9d32e
checksum: 004f7f91d75cd7fee80886fce7dc3de6ab9d2e853f3126abb163d67c313dfd91958b8c39a2a71f0d73c1fe6b9e42057394bd90216ceaac333ac75aa100f5cb63
languageName: node
linkType: hard

"@logion/node-api@npm:^0.31.0":
version: 0.31.0
resolution: "@logion/node-api@npm:0.31.0"
"@logion/node-api@npm:^0.31.2":
version: 0.31.2
resolution: "@logion/node-api@npm:0.31.2"
dependencies:
"@polkadot/api": ^11.0.2
"@polkadot/util": ^12.6.2
Expand All @@ -593,7 +593,7 @@ __metadata:
bech32: ^2.0.0
fast-sha256: ^1.3.0
uuid: ^9.0.0
checksum: 6aae4f45477cefcf253354ac26788a72e705d953f9e7a62f1fae2b92bb1ce9331bc850a967a5c5e1e3103dae448134ae2e1fc710a7408cebf88286b71b4a0ea2
checksum: 9d3d41d250db88e49e17da8c3a5dd65271ea54f9ee06c87ac465c344d0e2ec48a4de0c5975745f21d1b8684d682b858d893512eda7a7d1f6d1f81c2518101e2f
languageName: node
linkType: hard

Expand All @@ -602,8 +602,8 @@ __metadata:
resolution: "@logion/rest-api-core@workspace:."
dependencies:
"@istanbuljs/nyc-config-typescript": ^1.0.2
"@logion/authenticator": ^0.6.2
"@logion/node-api": ^0.31.0
"@logion/authenticator": ^0.7.0-1
"@logion/node-api": ^0.31.2
"@tsconfig/node16": ^16.1.1
"@types/express": ^4.17.14
"@types/express-fileupload": ^1.4.1
Expand Down

0 comments on commit 5a6d35a

Please sign in to comment.