Skip to content

Commit

Permalink
Updates functions, tests and some message fields
Browse files Browse the repository at this point in the history
  • Loading branch information
w4ll3 authored and wyc committed Jan 28, 2022
1 parent adcd039 commit 9e8b63f
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 61 deletions.
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ indent_style = tab
indent_style = tab
indent_size = 4
max_line_length = 80
quote_type = single

[test/**.json]
indent_style = space
indent_size = 4
max_line_length = 80
quote_type = single
6 changes: 3 additions & 3 deletions examples/notepad/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Helmet from 'helmet';
import Morgan from 'morgan';
import Path from 'path';
import FileStore from 'session-file-store';
import { ErrorTypes, SiweMessage, generateNonce } from 'siwe';
import { ErrorTypes, generateNonce, SiweMessage } from 'siwe';
const FileStoreStore = FileStore(Session);

config();
Expand Down Expand Up @@ -90,7 +90,7 @@ app.get('/api/me', async (req, res) => {

app.post('/api/sign_in', async (req, res) => {
try {
const { ens } = req.body;
const { ens, signature } = req.body;
if (!req.body.message) {
res.status(422).json({ message: 'Expected signMessage object as body.' });
return;
Expand All @@ -114,7 +114,7 @@ app.post('/api/sign_in', async (req, res) => {

await infuraProvider.ready;

const fields: SiweMessage = await message.validate(infuraProvider);
const fields: SiweMessage = await message.validate(signature, infuraProvider);

if (fields.nonce !== req.session.nonce) {
res.status(422).json({
Expand Down
8 changes: 3 additions & 5 deletions examples/notepad/src/providers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import WalletConnect from '@walletconnect/web3-provider';
import { ethers } from 'ethers';
import Mousetrap from 'mousetrap';
import { SignatureType, SiweMessage } from 'siwe';
import { SiweMessage } from 'siwe';

declare global {
interface Window {
Expand Down Expand Up @@ -88,23 +88,21 @@ const signIn = async (connector: Providers) => {
uri: document.location.origin,
version: '1',
statement: 'SIWE Notepad Example',
type: SignatureType.PERSONAL_SIGNATURE,
nonce,
});

/**
* Generates the message to be signed and uses the provider to ask for a signature
*/
const signature = await provider.getSigner().signMessage(message.signMessage());
message.signature = signature;
const signature = await provider.getSigner().signMessage(message.prepareMessage());

/**
* Calls our sign_in endpoint to validate the message, if successful it will
* save the message in the session and allow the user to store his text
*/
fetch(`/api/sign_in`, {
method: 'POST',
body: JSON.stringify({ message, ens }),
body: JSON.stringify({ message, ens, signature }),
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
}).then(async (res) => {
Expand Down
56 changes: 35 additions & 21 deletions lib/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
var parsingPositive: object = require('../test/parsing_positive.json');
var validationPositive: object = require('../test/validation_positive.json');
var validationNegative: object = require('../test/validation_negative.json');
import { SiweMessage } from "./client";
import { Wallet } from "ethers";
import { Wallet } from 'ethers';
import { SiweMessage } from './client';

describe(`Message Generation`, () => {
test.concurrent.each(Object.entries(parsingPositive))('Generates message successfully: %s', (_, test) => {
const msg = new SiweMessage(test.fields);
expect(msg.toMessage()).toBe(test.message);
});
test.concurrent.each(Object.entries(parsingPositive))(
'Generates message successfully: %s',
(_, test) => {
const msg = new SiweMessage(test.fields);
expect(msg.toMessage()).toBe(test.message);
}
);
});

describe(`Message Validation`, () => {
test.concurrent.each(Object.entries(validationPositive))('Validates message successfully: %s', async (_, test_fields) => {
const msg = new SiweMessage(test_fields);
await expect(msg.validate()).resolves.not.toThrow();
});
test.concurrent.each(Object.entries(validationNegative))('Fails to validate message: %s', async (_, test_fields) => {
const msg = new SiweMessage(test_fields);
await expect(msg.validate()).rejects.toThrow();
});
test.concurrent.each(Object.entries(validationPositive))(
'Validates message successfully: %s',
async (_, test_fields) => {
const msg = new SiweMessage(test_fields);
await expect(
msg.validate(test_fields.signature)
).resolves.not.toThrow();
}
);
test.concurrent.each(Object.entries(validationNegative))(
'Fails to validate message: %s',
async (_, test_fields) => {
const msg = new SiweMessage(test_fields);
await expect(msg.validate(test_fields.signature)).rejects.toThrow();
}
);
});

describe(`Round Trip`, () => {
let wallet = Wallet.createRandom();
test.concurrent.each(Object.entries(parsingPositive))('Generates a Successfully Verifying message: %s', async (_, test) => {
const msg = new SiweMessage(test.fields);
msg.address = wallet.address;
msg.signature = await wallet.signMessage(msg.toMessage());
await expect(msg.validate()).resolves.not.toThrow();
});
})
test.concurrent.each(Object.entries(parsingPositive))(
'Generates a Successfully Verifying message: %s',
async (_, test) => {
const msg = new SiweMessage(test.fields);
msg.address = wallet.address;
const signature = await wallet.signMessage(msg.toMessage());
await expect(msg.validate(signature)).resolves.not.toThrow();
}
);
});
81 changes: 54 additions & 27 deletions lib/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { randomStringForEntropy } from '@stablelib/random';
// TODO: Figure out how to get types from this lib:
import { Contract, ethers, utils } from 'ethers';
Expand All @@ -17,8 +16,11 @@ export enum ErrorTypes {
MALFORMED_SESSION = 'Malformed session.',
}

/**
/**@deprecated
* Possible signature types that this library supports.
*
* This enum will be removed in future releases. And signature type will be
* infered from version.
*/
export enum SignatureType {
/**EIP-191 signature scheme */
Expand Down Expand Up @@ -60,9 +62,19 @@ export class SiweMessage {
* resolved as part of authentication by the relying party. They are
* expressed as RFC 3986 URIs separated by `\n- `. */
resources?: Array<string>;
/**Signature of the message signed by the wallet. */
/**@deprecated
* Signature of the message signed by the wallet.
*
* This field will be removed in future releases, an additional parameter
* was added to the validate function were the signature goes to validate
* the message.
*/
signature?: string;
/**Type of sign message to be generated. */
/**@deprecated Type of sign message to be generated.
*
* This field will be removed in future releases and will rely on the
* message version
*/
type?: SignatureType;

/**
Expand Down Expand Up @@ -119,7 +131,7 @@ export class SiweMessage {
this.nonce = generateNonce();
}

const chainField = `Chain ID: ` + this.chainId || "1";
const chainField = `Chain ID: ` + this.chainId || '1';

const nonceField = `Nonce: ${this.nonce}`;

Expand Down Expand Up @@ -164,16 +176,33 @@ export class SiweMessage {
return [prefix, suffix].join('\n\n');
}

/**
/** @deprecated
* signMessage method is deprecated, use prepareMessage instead.
*
* This method parses all the fields in the object and creates a sign
* message according with the type defined.
* @returns {string} Returns a message ready to be signed according with the
* type defined in the object.
*/
signMessage(): string {
console &&
console.warn &&
console.warn(
'signMessage method is deprecated, use prepareMessage instead.'
);
return this.prepareMessage();
}

/**
* This method parses all the fields in the object and creates a sign
* message according with the type defined.
* @returns {string} Returns a message ready to be signed according with the
* type defined in the object.
*/
prepareMessage(): string {
let message: string;
switch (this.type) {
case SignatureType.PERSONAL_SIGNATURE: {
switch (this.version) {
case '1': {
message = this.toMessage();
break;
}
Expand All @@ -195,33 +224,32 @@ export class SiweMessage {
* @returns {Promise<SiweMessage>} This object if valid.
*/
async validate(
signature: string = this.signature,
provider?: ethers.providers.Provider | any
): Promise<SiweMessage> {
return new Promise<SiweMessage>(async (resolve, reject) => {
const message = this.signMessage();
const message = this.prepareMessage();
try {
let missing: Array<string> = [];
if (!message) {
missing.push('`message`');
}

if (!this.signature) {
if (!signature) {
missing.push('`signature`');
}
if (!this.address) {
missing.push('`address`');
}
if (missing.length > 0) {
throw new Error(
`${ErrorTypes.MALFORMED_SESSION
`${
ErrorTypes.MALFORMED_SESSION
} missing: ${missing.join(', ')}.`
);
}

const addr = ethers.utils.verifyMessage(
message,
this.signature
);
const addr = ethers.utils.verifyMessage(message, signature);

if (addr !== this.address) {
try {
Expand All @@ -239,17 +267,16 @@ export class SiweMessage {
}
const parsedMessage = new SiweMessage(message);

if (
parsedMessage.expirationTime
) {
const exp = new Date(parsedMessage.expirationTime).getTime();
if (parsedMessage.expirationTime) {
const exp = new Date(
parsedMessage.expirationTime
).getTime();
if (isNaN(exp)) {
throw new Error(`${ErrorTypes.MALFORMED_SESSION} invalid expiration date.`)
throw new Error(
`${ErrorTypes.MALFORMED_SESSION} invalid expiration date.`
);
}
if (
new Date().getTime() >=
exp
) {
if (new Date().getTime() >= exp) {
throw new Error(ErrorTypes.EXPIRED_MESSAGE);
}
}
Expand Down Expand Up @@ -295,13 +322,13 @@ export const checkContractWalletSignature = async (
* This method leverages a native CSPRNG with support for both browser and Node.js
* environments in order generate a cryptographically secure nonce for use in the
* SiweMessage in order to prevent replay attacks.
*
*
* 96 bits has been chosen as a number to sufficiently balance size and security considerations
* relative to the lifespan of it's usage.
*
*
* @returns cryptographically generated random nonce with 96 bits of entropy encoded with
* an alphanumeric character set.
*/
export const generateNonce = (): string => {
return randomStringForEntropy(96);
}
};
10 changes: 5 additions & 5 deletions test/validation_positive.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"example message": {
"domain": "login.xyz",
"address": "0x6Da01670d8fc844e736095918bbE11fE8D564163",
"address": "0x9D85ca56217D2bb651b00f15e694EB7E713637D4",
"statement": "Sign-In With Ethereum Example Statement",
"uri": "https://login.xyz",
"version": "1",
"nonce": "rmplqh1gf",
"issuedAt": "2022-01-05T14:31:43.954Z",
"nonce": "bTyXgcQxn2htgkjJn",
"issuedAt": "2022-01-27T17:09:38.578Z",
"chainId": "1",
"expirationTime": "2022-01-07T14:31:43.952Z",
"signature": "0x2e8420fc1b722bf4941f5a0464f98172a758ceda5039f622e425fb69fd19b20e444bba7c9a8a8d7e2b5e453553efe7c9460be5d211abe473fc146d51bb04d0cb1b"
"expirationTime": "2100-01-07T14:31:43.952Z",
"signature": "0xdc35c7f8ba2720df052e0092556456127f00f7707eaa8e3bbff7e56774e7f2e05a093cfc9e02964c33d86e8e066e221b7d153d27e5a2e97ccd5ca7d3f2ce06cb1b"
}
}

0 comments on commit 9e8b63f

Please sign in to comment.