Skip to content

Commit

Permalink
Export validate siwf response function (#178)
Browse files Browse the repository at this point in the history
# Problem

Export a part of a function for easier validation when receiving a
payload only.
  • Loading branch information
wilwade authored Oct 2, 2024
1 parent 93caa43 commit 6f17808
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 16 deletions.
2 changes: 1 addition & 1 deletion docs/src/Delegations.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ Staging-Testnet and Production-Mainnet have the same delegation Schema Ids avail
| [`dsnp.reply@v2`](https://spec.dsnp.org/DSNP/Types/Reply.html) | Reply to a content | 18 |
| [`dsnp.tombstone@v2`](https://spec.dsnp.org/DSNP/Types/Tombstone.html) | Mark content for deletion | 16 |
| [`dsnp.update@v2`](https://spec.dsnp.org/DSNP/Types/Update.html) | Update an existing post or reply | 19 |
| [`dsnp.user-attribute-set@v1`](https://spec.dsnp.org/DSNP/Types/UserAttributeSet.html) | Create an authenticated attribute set for a DSNP User | 13 |
| [`dsnp.user-attribute-set@v2`](https://spec.dsnp.org/DSNP/Types/UserAttributeSet.html) | Create an authenticated attribute set for a DSNP User | 20 |
| [`frequency.default-token-address@v1`](https://github.com/frequency-chain/schemas) | List one or more default token receiving addresses | 21 |
1 change: 1 addition & 0 deletions libraries/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ See [Markdown/GitHub Docs](../../docs/src/QuickStart.md) or
| `generateAuthenticationUrl` | Generates the signed request for the authentication flow |
| `getLoginResult` | Fetch and extract the Result of the Login |
| `hasChainSubmissions` | Checks to see if there are any chain submissions in the given result |
| `validateSiwfResponse` | Takes a response payload and validates it |
| `generateSignedRequest` | Generates the signed payload for the authentication flow using a keypair |
| `buildSignedRequest` | Builds the signed request for the authentication flow using the signature and public key |
| `generateEncodedSignedRequest` | Generates the encoded signed payload for the authentication flow using a keypair |
Expand Down
30 changes: 28 additions & 2 deletions libraries/js/src/response.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, it, vi, expect, beforeAll } from 'vitest';
import { ExampleLogin, ExampleNewProvider, ExampleNewUser } from './mocks/index.js';
import { getLoginResult, hasChainSubmissions } from './response.js';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import base64url from 'base64url';
import { ExampleLogin, ExampleNewProvider, ExampleNewUser } from './mocks/index.js';
import { getLoginResult, hasChainSubmissions, validateSiwfResponse } from './response.js';

global.fetch = vi.fn();

Expand Down Expand Up @@ -57,3 +58,28 @@ describe('hasChainSubmissions', () => {
expect(hasChainSubmissions(loginResponse)).toBe(false);
});
});

describe('validateSiwfResponse', () => {
it('can handle a JSON strigified base64url encoded value', async () => {
const example = await ExampleLogin();
await expect(validateSiwfResponse(base64url(JSON.stringify(example)))).to.resolves.toMatchObject(example);
});

it('can handle an object value', async () => {
const example = await ExampleLogin();
await expect(validateSiwfResponse(example)).to.resolves.toMatchObject(example);
});

it('throws on a null value', async () => {
await expect(validateSiwfResponse(null)).to.rejects.toThrowError(
'Response failed to correctly parse or invalid content: null'
);
});

it('throws on a bad string value', async () => {
const value = base64url(JSON.stringify({ foo: 'bad' }));
await expect(validateSiwfResponse(value)).to.rejects.toThrowError(
'Response failed to correctly parse or invalid content: {"foo":"bad"}'
);
});
});
48 changes: 35 additions & 13 deletions libraries/js/src/response.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { cryptoWaitReady } from '@polkadot/util-crypto';
import base64url from 'base64url';
import { SiwfOptions } from './types/general.js';
import { isSiwfResponse, SiwfResponse } from './types/response.js';
import { parseEndpoint } from './util.js';
Expand All @@ -17,6 +18,39 @@ export function hasChainSubmissions(result: SiwfResponse): boolean {
return !!result.payloads.find((x) => !!x.endpoint);
}

/**
* Validate a possible SIWF Response
*
* @param {unknown} response - A possible SIWF Response.
*
* @returns {Promise<SiwfResponse>} The validated response
*/
export async function validateSiwfResponse(response: unknown): Promise<SiwfResponse> {
await cryptoWaitReady();

let body = response;
if (typeof response === 'string') {
try {
body = JSON.parse(base64url.decode(response));
} catch (_e) {
throw new Error(`Response failed to correctly parse: ${response}`);
}
}

// This also validates that userPublicKey is a valid address
if (!isSiwfResponse(body)) {
throw new Error(`Response failed to correctly parse or invalid content: ${JSON.stringify(body)}`);
}

// Validate Payloads
await validatePayloads(body);

// Validate Credentials (if any), but trust DIDs from frequencyAccess
await validateCredentials(body.credentials, ['did:web:frequencyaccess.com', 'did:web:testnet.frequencyaccess.com']);

return body;
}

/**
* Fetch and extract the Result of the Login from Frequency Access
*
Expand All @@ -27,7 +61,6 @@ export function hasChainSubmissions(result: SiwfResponse): boolean {
* @returns {Promise<SiwfResponse>} The parsed and validated response
*/
export async function getLoginResult(authorizationCode: string, options?: SiwfOptions): Promise<SiwfResponse> {
await cryptoWaitReady();
const endpoint = new URL(
`${parseEndpoint(options?.endpoint, '/api/payload')}?authorizationCode=${authorizationCode}`
);
Expand All @@ -39,16 +72,5 @@ export async function getLoginResult(authorizationCode: string, options?: SiwfOp

const body = await response.json();

// This also validates that userPublicKey is a valid address
if (!isSiwfResponse(body)) {
throw new Error(`Response failed to correctly parse or invalid content: ${await response.text()}`);
}

// Validate Payloads
await validatePayloads(body);

// Validate Credentials (if any), but trust DIDs from frequencyAccess
await validateCredentials(body.credentials, ['did:web:frequencyaccess.com', 'did:web:testnet.frequencyaccess.com']);

return body;
return validateSiwfResponse(body);
}

0 comments on commit 6f17808

Please sign in to comment.