Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support optional scheme being provided in msg header #195

Merged
merged 3 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/siwe-parser/lib/abnf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isEIP55Address, parseIntegerNumber } from "./utils";

const GRAMMAR = `
sign-in-with-ethereum =
domain %s" wants you to sign in with your Ethereum account:" LF
[ scheme "://" ] domain %s" wants you to sign in with your Ethereum account:" LF
address LF
LF
[ statement LF ]
Expand Down Expand Up @@ -161,6 +161,7 @@ class GrammarApi {
}

export class ParsedMessage {
scheme: string | null;
domain: string;
address: string;
statement: string | null;
Expand All @@ -179,6 +180,19 @@ export class ParsedMessage {
parser.ast = new apgLib.ast();
const id = apgLib.ids;

const scheme = function (state, chars, phraseIndex, phraseLength, data) {
const ret = id.SEM_OK;
if (state === id.SEM_PRE && phraseIndex === 0) {
sbihel marked this conversation as resolved.
Show resolved Hide resolved
data.scheme = apgLib.utils.charsToString(
chars,
phraseIndex,
phraseLength
);
}
return ret;
};
parser.ast.callbacks.scheme = scheme;

const domain = function (state, chars, phraseIndex, phraseLength, data) {
const ret = id.SEM_OK;
if (state === id.SEM_PRE) {
Expand Down
5 changes: 4 additions & 1 deletion packages/siwe-parser/lib/parsers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ describe("Successfully parses with ABNF Client", () => {
(test_name, test) => {
const parsedMessage = new ParsedMessage(test.message);
for (const [field, value] of Object.entries(test.fields)) {
if (typeof value === "object") {
if (value === null) {
expect(parsedMessage[field]).toBeUndefined();
}
sbihel marked this conversation as resolved.
Show resolved Hide resolved
else if (typeof value === "object") {
expect(parsedMessage[field]).toStrictEqual(value);
} else {
expect(parsedMessage[field]).toBe(value);
Expand Down
3 changes: 3 additions & 0 deletions packages/siwe/lib/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe(`Message verification without suppressExceptions`, () => {
.verify({
signature: test_fields.signature,
time: (test_fields as any).time || test_fields.issuedAt,
scheme: (test_fields as any).scheme,
domain: (test_fields as any).domainBinding,
nonce: (test_fields as any).matchNonce,
})
Expand Down Expand Up @@ -85,6 +86,7 @@ describe(`Message verification without suppressExceptions`, () => {
.verify({
signature: test_fields.signature,
time: (test_fields as any).time || test_fields.issuedAt,
scheme: (test_fields as any).scheme,
domain: (test_fields as any).domainBinding,
nonce: (test_fields as any).matchNonce,
})
Expand All @@ -109,6 +111,7 @@ describe(`Message verification with suppressExceptions`, () => {
{
signature: test_fields.signature,
time: (test_fields as any).time || test_fields.issuedAt,
scheme: (test_fields as any).scheme,
domain: (test_fields as any).domainBinding,
nonce: (test_fields as any).matchNonce,
},
Expand Down
23 changes: 20 additions & 3 deletions packages/siwe/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
} from './utils';

export class SiweMessage {
/**RFC 3986 URI scheme for the authority that is requesting the signing. */
scheme?: string;
/**RFC 4501 dns authority that is requesting the signing. */
domain: string;
/**Ethereum address performing the signing conformant to capitalization
Expand Down Expand Up @@ -69,6 +71,7 @@ export class SiweMessage {
constructor(param: string | Partial<SiweMessage>) {
if (typeof param === 'string') {
const parsedMessage = new ParsedMessage(param);
this.scheme = parsedMessage.scheme;
this.domain = parsedMessage.domain;
this.address = parsedMessage.address;
this.statement = parsedMessage.statement;
Expand All @@ -82,6 +85,7 @@ export class SiweMessage {
this.chainId = parsedMessage.chainId;
this.resources = parsedMessage.resources;
} else {
this.scheme = param?.scheme;
this.domain = param.domain;
this.address = param.address;
this.statement = param?.statement;
Expand Down Expand Up @@ -113,8 +117,8 @@ export class SiweMessage {
toMessage(): string {
/** Validates all fields of the object */
this.validateMessage();

const header = `${this.domain} wants you to sign in with your Ethereum account:`;
const headerPrefx = this.scheme ? `${this.scheme}://${this.domain}` : this.domain;
const header = `${headerPrefx} wants you to sign in with your Ethereum account:`;
const uriField = `URI: ${this.uri}`;
let prefix = [header, this.address].join('\n');
const versionField = `Version: ${this.version}`;
Expand Down Expand Up @@ -246,7 +250,20 @@ export class SiweMessage {
});
}

const { signature, domain, nonce, time } = params;
const { signature, scheme, domain, nonce, time } = params;

/** Scheme for domain binding */
if (scheme && scheme !== this.scheme) {
fail({
success: false,
data: this,
error: new SiweError(
SiweErrorType.SCHEME_MISMATCH,
scheme,
this.scheme
),
});
}

/** Domain binding */
if (domain && domain !== this.domain) {
Expand Down
11 changes: 9 additions & 2 deletions packages/siwe/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export interface VerifyParams {
/** Signature of the message signed by the wallet */
signature: string;

/** RFC 3986 URI scheme for the authority that is requesting the signing. */
scheme?: string;

/** RFC 4501 dns authority that is requesting the signing. */
domain?: string;

Expand All @@ -17,6 +20,7 @@ export interface VerifyParams {

export const VerifyParamsKeys: Array<keyof VerifyParams> = [
'signature',
'scheme',
'domain',
'nonce',
'time',
Expand Down Expand Up @@ -63,8 +67,8 @@ export class SiweError {
this.received = received;
}

/** Type of the error. */
type: SiweErrorType | string;
/** Type of the error. */
type: SiweErrorType | string;

/** Expected value or condition to pass. */
expected?: string;
Expand All @@ -83,6 +87,9 @@ export enum SiweErrorType {
/** `domain` is not a valid authority or is empty. */
INVALID_DOMAIN = 'Invalid domain.',

/** `scheme` don't match the scheme provided for verification. */
SCHEME_MISMATCH = 'Scheme does not match provided scheme for verification.',

/** `domain` don't match the domain provided for verification. */
DOMAIN_MISMATCH = 'Domain does not match provided domain for verification.',

Expand Down
28 changes: 28 additions & 0 deletions test/parsing_positive.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,5 +216,33 @@
"nonce": "15050747",
"issuedAt": "2022-06-30T14:08:51.382Z"
}
},
"domain contains optional scheme": {
"message": "https://example.com wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ExampleOrg Terms of Service: https://example.com/tos\n\nURI: https://example.com/login\nVersion: 1\nChain ID: 1\nNonce: 32891756\nIssued At: 2021-09-30T16:25:24Z",
"fields": {
"scheme": "https",
"domain": "example.com",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"statement": "I accept the ExampleOrg Terms of Service: https://example.com/tos",
"uri": "https://example.com/login",
"version": "1",
"chainId": 1,
"nonce": "32891756",
"issuedAt": "2021-09-30T16:25:24Z"
}
},
"scheme is not parsed from elsehwere in message": {
"message": "localhost:3030 wants you to sign in with your Ethereum account:\n0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\n\nI accept the ExampleOrg Terms of Service: http://localhost:3030/tos\n\nURI: http://localhost:3030/login\nVersion: 1\nChain ID: 1\nNonce: 32891756\nIssued At: 2021-09-30T16:25:24Z",
"fields": {
"scheme": null,
"domain": "localhost:3030",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"statement": "I accept the ExampleOrg Terms of Service: http://localhost:3030/tos",
"uri": "http://localhost:3030/login",
"version": "1",
"chainId": 1,
"nonce": "32891756",
"issuedAt": "2021-09-30T16:25:24Z"
}
}
}
Loading