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

Bug: Domain only validation doesn't work for custom schemes #205

Merged
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion docs/src/DataStructures/Request.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
```json
{
"signedRequest": "eyJyZXF1ZXN0ZWRTaWduYXR1cmVzIjp7InB1YmxpY0tleSI6eyJlbmNvZGVkVmFsdWUiOiJmNmNMNHdxMUhVTngxMVRjdmRBQk5mOVVOWFhveUg0N21WVXdUNTl0elNGUlc4eURIIiwiZW5jb2RpbmciOiJiYXNlNTgiLCJmb3JtYXQiOiJzczU4IiwidHlwZSI6IlNyMjU1MTkifSwic2lnbmF0dXJlIjp7ImFsZ28iOiJTUjI1NTE5IiwiZW5jb2RpbmciOiJiYXNlMTYiLCJlbmNvZGVkVmFsdWUiOiIweDNlMTdhYzM3Yzk3ZWE3M2E3YzM1ZjBjYTJkZTcxYmY3MmE5NjlkYjhiNjQyYzU3ZTI2N2Q4N2Q1OTA3ZGM4MzVmYTJjODI4MTdlODA2YTQ5NGIyY2E5Y2U5MjJmNDM1NDY4M2U4YzAxMzY5NTNlMGZlNWExODJkMzU0NjQ2Yzg4In0sInBheWxvYWQiOnsiY2FsbGJhY2siOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJwZXJtaXNzaW9ucyI6WzUsNyw4LDksMTBdfX0sInJlcXVlc3RlZENyZWRlbnRpYWxzIjpbeyJ0eXBlIjoiVmVyaWZpZWRHcmFwaEtleUNyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFtZHZteGQ1NHp2ZTVraWZ5Y2dzZHRvYWhzNWVjZjRoYWwydHMzZWV4a2dvY3ljNW9jYTJ5Il19LHsiYW55T2YiOlt7InR5cGUiOiJWZXJpZmllZEVtYWlsQWRkcmVzc0NyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFlNHFvY3poZnRpY2k0ZHpmdmZiZWw3Zm80aDRzcjVncmNvM29vdnd5azZ5NHluZjQ0dHNpIl19LHsidHlwZSI6IlZlcmlmaWVkUGhvbmVOdW1iZXJDcmVkZW50aWFsIiwiaGFzaCI6WyJiY2lxanNwbmJ3cGMzd2p4NGZld2NlazVkYXlzZGpwYmY1eGppbXo1d251NXVqN2UzdnUydXducSJdfV19XX0",
"signedRequest": "eyJyZXF1ZXN0ZWRTaWduYXR1cmVzIjp7InB1YmxpY0tleSI6eyJlbmNvZGVkVmFsdWUiOiJmNmNMNHdxMUhVTngxMVRjdmRBQk5mOVVOWFhveUg0N21WVXdUNTl0elNGUlc4eURIIiwiZW5jb2RpbmciOiJiYXNlNTgiLCJmb3JtYXQiOiJzczU4IiwidHlwZSI6IlNyMjU1MTkifSwic2lnbmF0dXJlIjp7ImFsZ28iOiJTUjI1NTE5IiwiZW5jb2RpbmciOiJiYXNlMTYiLCJlbmNvZGVkVmFsdWUiOiIweDVjODZkMDk2NjAzNTkzZWZmNGMwYzAxM2VjY2E4NDdiZTUzMTczZTNhYTU2YTNiNWE3NzM0NDliNjE3NTA3MWNlYmNmNDM2YmVhZDcyMDgxNmU2YzE1YzMyZmExZTE0MWRkMzYzNDJjYjQ0ZmVkZjE1ODMzYTc5N2VhMjM5NjgzIn0sInBheWxvYWQiOnsiY2FsbGJhY2siOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJwZXJtaXNzaW9ucyI6WzUsNyw4LDksMTBdfX0sInJlcXVlc3RlZENyZWRlbnRpYWxzIjpbeyJ0eXBlIjoiVmVyaWZpZWRHcmFwaEtleUNyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFtZHZteGQ1NHp2ZTVraWZ5Y2dzZHRvYWhzNWVjZjRoYWwydHMzZWV4a2dvY3ljNW9jYTJ5Il19LHsiYW55T2YiOlt7InR5cGUiOiJWZXJpZmllZEVtYWlsQWRkcmVzc0NyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFlNHFvY3poZnRpY2k0ZHpmdmZiZWw3Zm80aDRzcjVncmNvM29vdnd5azZ5NHluZjQ0dHNpIl19LHsidHlwZSI6IlZlcmlmaWVkUGhvbmVOdW1iZXJDcmVkZW50aWFsIiwiaGFzaCI6WyJiY2lxanNwbmJ3cGMzd2p4NGZld2NlazVkYXlzZGpwYmY1eGppbXo1d251NXVqN2UzdnUydXducSJdfV19XX0",
"mode": "dark"
}
```
2 changes: 1 addition & 1 deletion docs/src/DataStructures/RequestUrl.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
```json
"https://testnet.frequencyaccess.com/siwa/start?signedRequest=eyJyZXF1ZXN0ZWRTaWduYXR1cmVzIjp7InB1YmxpY0tleSI6eyJlbmNvZGVkVmFsdWUiOiJmNmNMNHdxMUhVTngxMVRjdmRBQk5mOVVOWFhveUg0N21WVXdUNTl0elNGUlc4eURIIiwiZW5jb2RpbmciOiJiYXNlNTgiLCJmb3JtYXQiOiJzczU4IiwidHlwZSI6IlNyMjU1MTkifSwic2lnbmF0dXJlIjp7ImFsZ28iOiJTUjI1NTE5IiwiZW5jb2RpbmciOiJiYXNlMTYiLCJlbmNvZGVkVmFsdWUiOiIweDNlMTdhYzM3Yzk3ZWE3M2E3YzM1ZjBjYTJkZTcxYmY3MmE5NjlkYjhiNjQyYzU3ZTI2N2Q4N2Q1OTA3ZGM4MzVmYTJjODI4MTdlODA2YTQ5NGIyY2E5Y2U5MjJmNDM1NDY4M2U4YzAxMzY5NTNlMGZlNWExODJkMzU0NjQ2Yzg4In0sInBheWxvYWQiOnsiY2FsbGJhY2siOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJwZXJtaXNzaW9ucyI6WzUsNyw4LDksMTBdfX0sInJlcXVlc3RlZENyZWRlbnRpYWxzIjpbeyJ0eXBlIjoiVmVyaWZpZWRHcmFwaEtleUNyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFtZHZteGQ1NHp2ZTVraWZ5Y2dzZHRvYWhzNWVjZjRoYWwydHMzZWV4a2dvY3ljNW9jYTJ5Il19LHsiYW55T2YiOlt7InR5cGUiOiJWZXJpZmllZEVtYWlsQWRkcmVzc0NyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFlNHFvY3poZnRpY2k0ZHpmdmZiZWw3Zm80aDRzcjVncmNvM29vdnd5azZ5NHluZjQ0dHNpIl19LHsidHlwZSI6IlZlcmlmaWVkUGhvbmVOdW1iZXJDcmVkZW50aWFsIiwiaGFzaCI6WyJiY2lxanNwbmJ3cGMzd2p4NGZld2NlazVkYXlzZGpwYmY1eGppbXo1d251NXVqN2UzdnUydXducSJdfV19XX0&mode=dark"
"https://testnet.frequencyaccess.com/siwa/start?signedRequest=eyJyZXF1ZXN0ZWRTaWduYXR1cmVzIjp7InB1YmxpY0tleSI6eyJlbmNvZGVkVmFsdWUiOiJmNmNMNHdxMUhVTngxMVRjdmRBQk5mOVVOWFhveUg0N21WVXdUNTl0elNGUlc4eURIIiwiZW5jb2RpbmciOiJiYXNlNTgiLCJmb3JtYXQiOiJzczU4IiwidHlwZSI6IlNyMjU1MTkifSwic2lnbmF0dXJlIjp7ImFsZ28iOiJTUjI1NTE5IiwiZW5jb2RpbmciOiJiYXNlMTYiLCJlbmNvZGVkVmFsdWUiOiIweDVjODZkMDk2NjAzNTkzZWZmNGMwYzAxM2VjY2E4NDdiZTUzMTczZTNhYTU2YTNiNWE3NzM0NDliNjE3NTA3MWNlYmNmNDM2YmVhZDcyMDgxNmU2YzE1YzMyZmExZTE0MWRkMzYzNDJjYjQ0ZmVkZjE1ODMzYTc5N2VhMjM5NjgzIn0sInBheWxvYWQiOnsiY2FsbGJhY2siOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJwZXJtaXNzaW9ucyI6WzUsNyw4LDksMTBdfX0sInJlcXVlc3RlZENyZWRlbnRpYWxzIjpbeyJ0eXBlIjoiVmVyaWZpZWRHcmFwaEtleUNyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFtZHZteGQ1NHp2ZTVraWZ5Y2dzZHRvYWhzNWVjZjRoYWwydHMzZWV4a2dvY3ljNW9jYTJ5Il19LHsiYW55T2YiOlt7InR5cGUiOiJWZXJpZmllZEVtYWlsQWRkcmVzc0NyZWRlbnRpYWwiLCJoYXNoIjpbImJjaXFlNHFvY3poZnRpY2k0ZHpmdmZiZWw3Zm80aDRzcjVncmNvM29vdnd5azZ5NHluZjQ0dHNpIl19LHsidHlwZSI6IlZlcmlmaWVkUGhvbmVOdW1iZXJDcmVkZW50aWFsIiwiaGFzaCI6WyJiY2lxanNwbmJ3cGMzd2p4NGZld2NlazVkYXlzZGpwYmY1eGppbXo1d251NXVqN2UzdnUydXducSJdfV19XX0&mode=dark"
```
31 changes: 23 additions & 8 deletions libraries/js/src/mocks/payloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,52 @@ import {
serializeItemActionsPayloadHex,
} from '../util.js';

function generateLoginMessage(account: string, issued: Date, expires: Date, domain: string) {
return `${domain} wants you to sign in with your Frequency account:\n${account}\n\n\n\nURI: https://testnet.frequencyaccess.com/signin/confirm\nNonce: N6rLwqyz34oUxJEXJ\nIssued At: ${issued.toISOString()}\nExpiration Time: ${expires.toISOString()}`;
function generateLoginMessage(account: string, issued: Date, expires: Date, uri: URL) {
return `${uri.hostname} wants you to sign in with your Frequency account:\n${account}\n\n\n\nURI: ${uri}\nNonce: N6rLwqyz34oUxJEXJ\nIssued At: ${issued.toISOString()}\nExpiration Time: ${expires.toISOString()}`;
}

// Setup now so that it is consistent for the entire test run
const now = Date.now();

const loginMessageGood = (domain: string) =>
const loginMessageUrl = (url: string) =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allows to build test cases with different urls

generateLoginMessage(
ExampleUserKey.public,
new Date(now - 24 * 60 * 60 * 1000),
new Date(now + 24 * 60 * 60 * 1000),
domain
new URL(url)
);

const loginMessageGood = () => loginMessageUrl('https://your-app.com/signin/callback');

const loginMessageExpired = () =>
generateLoginMessage(
ExampleUserKey.public,
new Date(now - 2 * 24 * 60 * 60 * 1000),
new Date(now - 24 * 60 * 60 * 1000),
'localhost'
new URL('https://your-app.com/signin/callback')
);

export const ExamplePayloadLoginGood = (domain: string): SiwfResponsePayloadLogin => ({
export const ExamplePayloadLoginUrl = (url: string): SiwfResponsePayloadLogin => ({
signature: {
algo: 'SR25519',
encoding: 'base16',
encodedValue: u8aToHex(ExampleUserKey.keyPair().sign(loginMessageUrl(url))),
},
type: 'login',
payload: {
message: loginMessageUrl(url),
},
});

export const ExamplePayloadLoginGood = (): SiwfResponsePayloadLogin => ({
signature: {
algo: 'SR25519',
encoding: 'base16',
encodedValue: u8aToHex(ExampleUserKey.keyPair().sign(loginMessageGood(domain))),
encodedValue: u8aToHex(ExampleUserKey.keyPair().sign(loginMessageGood())),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

domain will always match the URI in the test cases

},
type: 'login',
payload: {
message: loginMessageGood(domain),
message: loginMessageGood(),
},
});

Expand All @@ -59,6 +73,7 @@ export const ExamplePayloadLoginExpired = (): SiwfResponsePayloadLogin => ({
},
});

// TODO: This test case is broken because now the domain is extracted from the URL, so isn't localhost
export const ExamplePayloadLoginStatic: SiwfResponsePayloadLogin = {
signature: {
algo: 'SR25519',
Expand Down
132 changes: 125 additions & 7 deletions libraries/js/src/payloads.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ExamplePayloadGrantDelegation,
ExamplePayloadLoginGood,
ExamplePayloadLoginStatic,
ExamplePayloadLoginUrl,
ExamplePayloadPublicGraphKey,
} from './mocks/payloads.js';
import { ExampleUserPublicKey } from './mocks/index.js';
Expand Down Expand Up @@ -62,9 +63,57 @@ Issued At: 2024-10-10T18:40:37.344099626Z`,
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginGood('localhost')],
payloads: [ExamplePayloadLoginGood()],
},
'localhost'
'your-app.com'
)
).resolves.toBeUndefined();
});

it('Can verify a Generated Login Payload: app scheme', async () => {
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('example://login')],
},
'example://login'
)
).resolves.toBeUndefined();
});

it('Can verify a Generated Login Payload: https://example.com/login', async () => {
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('https://example.com/login')],
},
'https://example.com/login'
)
).resolves.toBeUndefined();
});

it('Can verify a Generated Login Payload: www.example.com/login', async () => {
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('http://www.example.com/login')],
},
'www.example.com/login'
)
).resolves.toBeUndefined();
});

it('Can verify a Generated Login Payload: localhost:3030/login/path', async () => {
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('localhost:3030/login/path')],
},
'localhost:3030/login/path'
)
).resolves.toBeUndefined();
});
Expand All @@ -74,21 +123,90 @@ Issued At: 2024-10-10T18:40:37.344099626Z`,
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginGood('badhost')],
payloads: [ExamplePayloadLoginUrl('http://badhost')],
},
'localhost'
)
).rejects.toThrowError('Message does not match expected domain. Message: badhost Expected: localhost');
).rejects.toThrowError('Message does not match expected domain. Domain: badhost Expected: localhost');

await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginGood('localhost')],
payloads: [ExamplePayloadLoginGood()],
},
'betterhost'
)
).rejects.toThrowError('Message does not match expected domain. Message: localhost Expected: betterhost');
).rejects.toThrowError('Message does not match expected domain. Domain: your-app.com Expected: betterhost');
});

it('Will fail to verify a Generated Login Payload with an incorrect app scheme', async () => {
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('notexample://login')],
},
'example://login'
)
).rejects.toThrowError(
'Message does not match expected domain. Domain scheme mismatch. Scheme: notexample Expected: example'
);
});

it('Will fail to verify a Generated Login Payload with an incorrect https scheme', async () => {
// Check the scheme
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('http://example.com/login')],
},
'https://example.com/login'
)
).rejects.toThrowError(
'Message does not match expected domain. Domain scheme mismatch. Scheme: http Expected: https'
);

// Check the domain
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('https://www.examples.com/login')],
},
'https://www.example.com/login'
)
).rejects.toThrowError(
'Message does not match expected domain. Domain: www.examples.com Expected: www.example.com'
);

// Check the path
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('https://www.example.com/logins')],
},
'https://www.example.com/login'
)
).rejects.toThrowError(
'Message does not match expected domain. Domain path mismatch. Path: logins Expected: login'
);
});

it('Will fail to verify a Generated Login Payload with an incorrect www scheme', async () => {
await expect(
validatePayloads(
{
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginUrl('http://www.examples.com/login')],
},
'www.example.com/login'
)
).rejects.toThrowError(
'Message does not match expected domain. Domain: www.examples.com Expected: www.example.com'
);
});

it('Can verify a Static Login Payload', async () => {
Expand All @@ -98,7 +216,7 @@ Issued At: 2024-10-10T18:40:37.344099626Z`,
userPublicKey: ExampleUserPublicKey,
payloads: [ExamplePayloadLoginStatic],
},
'localhost'
'testnet.frequencyaccess.com'
)
).resolves.toBeUndefined();
});
Expand Down
Loading