+
+
+```
+
+In production use the method that handles the bid response must use the `paf-lib`
+functions to build the audit log from the OpenRTB response. The audit log JSON
+returned from `paf-lib` can then set in the auditLog attribute of the advert
+element before the new instance of `OKA.Controller` is created and the bind method
+called with the id of the advert.
+
+The bind method will insert a OneKey icon under the advert which when pressed
+will open the Audit Viewer.
+
+## Configuration Options
+
+The constructor of the `OKA.Controller` class takes two parameters.
+
+- brandName: provides the name used whenever an entity that is responsible for
+ providing the audit log needs to be named. This will typically be the name
+ of the publisher who initiated the opportunity to display an advertisement.
+- logoUrls: an array of string URLs that relate to logos that should appear in
+ the header of the audit viewer related to the brand of the entity that is
+ responsible for providing the audit log. This might include the brand logo
+ of the publisher and advertiser, or their suppliers.
+
+# Overview
+
+## Performance
+
+The module will be loaded after all the other content on the page. Therefore,
+the position of the script tag within the page is not material and is likely
+best placed at the end of the page and marked as lower priority.
+
+The module is implemented to defer any high-cost activity, such as the fetching
+of public keys needed for verification, until the point the user engages with
+the module. As such the audit viewer module does not know if the audit log is
+valid or contains suspicious activity or violations until the user interacts
+with the OneKey icon under the advert.
+
+The tapping of the OneKey icon under the advert will initiate all activity. A
+single parent Promise is used to commence verification which only resolves when
+all the identifies are fetched and verification has been completed. The module
+displays an in-progress status page whilst this background activity is being
+carried out.
+
+## Developers
+
+The approach taken to localization, and other aspects of the module design, are
+identical to the Consent Management Platform (CMP) and are not described in this
+readme.
+
+The jest tests of the audit log model save their mocked audit logs to the
+`assets/mocks` folder. These are then used if available at build time to add mock
+audit logs into the bundle that can selected by using the file name without the
+`.json` extension. For example; ‘all-good.json’ would be referred to as ‘all-good’
+when adding the attribute to the advert element.
+
+The `rollup.config.js` contains some additional features to bundle the mock audit
+logs for the purposes of demonstration and testing.
+
+The resulting bundle is a umd format bundle that contains the OKA.Controller
+public class.
+
+### Cryptography
+
+Modules `ec-key and` and `ecdsa-secp256r1` are used for common cryptography
+functions across the repository.
+
+`ec-key only` to convert PEM format public keys into JWK format.
+
+`ecdsa-secp256r1` is used across the project in Node and browser code. Prior to
+use in the audit module only the Node version was being used. The audit module
+requires the browser version. A bug in the current NPM module relating to the
+decoding of signatures in the web browser needs to be fixed by the package
+maintainer before the official package can be used. A fork of the package has
+been made and added to the repository as a sub module where the bug has been
+fixed. Once confirmed working in Prebid the maintainer can be contacted to apply
+the fix to the `ecdsa-secp256r1` package.
diff --git a/paf-mvp-audit/assets/images/advert.jpg b/paf-mvp-audit/assets/images/advert.jpg
deleted file mode 100644
index 0f18d9f8..00000000
Binary files a/paf-mvp-audit/assets/images/advert.jpg and /dev/null differ
diff --git a/paf-mvp-audit/assets/mocks/README.md b/paf-mvp-audit/assets/mocks/README.md
new file mode 100644
index 00000000..f3f512bc
--- /dev/null
+++ b/paf-mvp-audit/assets/mocks/README.md
@@ -0,0 +1,4 @@
+Place mock audit logs in this folder for use with UI testing. See model.test.ts for details of how these are generated.
+
+Tests that create mock audit logs for specific functionality output these mock audit logs to this folder. These files
+will therefore change when tests are run and should not be committed back to the repository.
\ No newline at end of file
diff --git a/paf-mvp-audit/assets/mocks/all-good.json b/paf-mvp-audit/assets/mocks/all-good.json
new file mode 100644
index 00000000..7dd5dc0f
--- /dev/null
+++ b/paf-mvp-audit/assets/mocks/all-good.json
@@ -0,0 +1 @@
+{"auditLog":{"version":"0.1","data":{"identifiers":[{"version":"0.1","type":"paf_browser_id","value":"9fe76ebe-d762-4b57-896d-66f11cc059b7","source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"pha2z3aBEteN1IEqCZifJ6mkEGtRkzMqycXDFflWhMo9dioHhwFaETDGInITg1mYNde7Z9pC4qrKpHIULpfvTQ=="},"persisted":false}],"preferences":{"version":"0.1","data":{"use_browsing_for_personalization":true},"source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"ZzaSr9b7Www2lcpfMavU7sVygj9VPRYyCb+T5jUn11aLSI2xhULTsp9ZHboTAtb2q5r6O51dz0mRBaIGDO/e/w=="}}},"seed":{"version":"0.1","transaction_ids":["570aaed6-ab27-4217-b33a-08e8dfa729ad"],"publisher":"automobiles.example","source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"yx/1En7s8jEwXMbNnaJ74XNDJW8R7PRxSXwHKqbGWBTC/bBc7cEIVSqhB6xcP746eaILPTnQU7dwezGDHI4K6w=="}},"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","transmissions":[{"version":"0.1","receiver":"automobiles.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"united-states.example","signature":"ZIxN3KESshlzJOYURNNnozGegQIXUi8N/dmVVPsGptyybgP3xZtarb4DCXJ4yxgIGikGogd8KD27Ev1wkdih5g=="},"children":[{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"ford.example","signature":"6FheTs+8RSz6TxFDH8tbxfHyboaGdI2kWjUC1mRW3eKLV1tXVXmB9NZOGKn2Ig9Bg0QAvp28QGdWc7UicO2O0w=="},"children":[{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"Rh2g7zE80yJwlWLfP3zlqnJlAH1mq9KMUGHReQ3iyzWWUqmSy+h0cdtdplQK+oiscu5Gh9L01qmjMLoKYBNahw=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"0dRKA7bEiKDbA8MJyhWlAVDcp+oTvNVxxDAM3bXQk1RvzXqiwnjCdtbZMUsnn8CFUQcbCOdotwhVbu+JorKUNA=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"cXizbFHXtMqI7osN+WEBnqKwAHvEulGUab7nHR2TCU7XRetyQlT/b29tdGaKghoXilEoI8lAuwW7jv8WRAy1/w=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"duesenberg.example","signature":"4+pii/My8c8qG8XEq7VRhQ4PhltlU+clsyMjFLG1LMa0NnZ/ux5jL5/gAQ4ZKuUv2UYO7TNViRijJJW8WaFuCQ=="},"children":[{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"1ZB9jIrGvpdCtFCyYWuYNCB6CmeOnoS0bWw1GHwZ0tIunD7aSq45Iog31j9b3ikhw8rkh3H8VmL5M5KqKixNgA=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"jeep.example","signature":"4PjsTPqFszhE2XSjcdJ7VMGgc7qOC6UYWtUXSqT6fjcOEJiSO0arx9Bk/+J8OIOqxbWM1rm9wsUptghEfP901Q=="},"children":[{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"yBiJMAKa9TbB1iPO4DluOWNm+esEH52ZFEp9RaZFEEq0VVUKC9fVcEw96MIX0EHWZzJealkQ3bVTPzH60aCQuw=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"oldsmobile.example","signature":"H+M01TrM2B+N3Fe5jBAC9DnfsRkAWH6XwJ9RtSvyD9HsV01XMGdIpU9gZIU7tK/4a7rP77BYZrMTnnRp2jvk6w=="},"children":[{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"rGda/hfIZrazdLm3Y5bE+A2UPXsPFHPgSxdvmMIIKqyWJ08IAel6U/UElOMigQw8Z3Edg3DaElkd6HkHY/168Q=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"chevrolet.example","signature":"MDR9ovQ1LnEGl0ICMS2G49lGKMsPHkBt9t4SdFqjwr37w6+jn1RPlSm45f/qfxiTKw/LcOZsCwnPPerA60gyuQ=="},"children":[{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"hql6bJiXUR7DHhUhI+Abt/ZIJEbIiT/akW77DKB0t8qy5PURrHpVUZYL06DDjosgjE8kLxOaPg6qh86l4PXXNg=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"Chrysler.example","signature":"YZQ5k/dQCuCg4bOLxygwBsGLlneZCu5Vei5Z9/ONsY47R38Sm7Av6/788ivbePKKTD4JDym2ycj/CWA3CYSz1w=="},"children":[{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"lb0WEpBqpjGGzpSNtKKzDtg77GDuNg6yR7HF7uFn8ZqycCdxR0aFV/i0SOqhBbqr5ccACSfo9CbMfpaSElEoZg=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"tesla.example","signature":"8LjrhOKW15xEzK8LEyQrzTWn4sg5o/M5sBig9G81OVmxfhBRYdqXT64H7u7QNMAeaF/F6ruHvSNd3630IPgXzQ=="},"children":[{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"DAjvsO1VVVEYmsDFUo41g4iNZE3jl1OME37FdjqhgYyHm2Oh4TRt6lM0jLvaDTvDpZcoB0aWvw1MA9qRXqi4lg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"zQpGTi/jkNUE/wHD7e0TyZESSB8GAM0+CROJgzNayFl+6FUjpZ/1nDH4y6oyqBFdFsuYRWmLMPbSZWL5zPoQAQ=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"6NP+LLck5erA7rKQZUSTQwuyN2s6q/e24259edDdpcicm9G4q3QOgkM9OVJ+Pso+z7dOP9y+imHuUOy2178huw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"hGR4fiIQ8pNbxgVo55UTGGN4ZDT3iB8mg7T7GX++y/GNntJwMJpAflHvOWBc30i+jy4SCme4xfXzJ+pXyo0Z2Q=="},"children":[]}]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"ford.example","signature":"6FheTs+8RSz6TxFDH8tbxfHyboaGdI2kWjUC1mRW3eKLV1tXVXmB9NZOGKn2Ig9Bg0QAvp28QGdWc7UicO2O0w=="},"children":[{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"Rh2g7zE80yJwlWLfP3zlqnJlAH1mq9KMUGHReQ3iyzWWUqmSy+h0cdtdplQK+oiscu5Gh9L01qmjMLoKYBNahw=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"0dRKA7bEiKDbA8MJyhWlAVDcp+oTvNVxxDAM3bXQk1RvzXqiwnjCdtbZMUsnn8CFUQcbCOdotwhVbu+JorKUNA=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"cXizbFHXtMqI7osN+WEBnqKwAHvEulGUab7nHR2TCU7XRetyQlT/b29tdGaKghoXilEoI8lAuwW7jv8WRAy1/w=="},"children":[]}]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"Rh2g7zE80yJwlWLfP3zlqnJlAH1mq9KMUGHReQ3iyzWWUqmSy+h0cdtdplQK+oiscu5Gh9L01qmjMLoKYBNahw=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"0dRKA7bEiKDbA8MJyhWlAVDcp+oTvNVxxDAM3bXQk1RvzXqiwnjCdtbZMUsnn8CFUQcbCOdotwhVbu+JorKUNA=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"cXizbFHXtMqI7osN+WEBnqKwAHvEulGUab7nHR2TCU7XRetyQlT/b29tdGaKghoXilEoI8lAuwW7jv8WRAy1/w=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"duesenberg.example","signature":"4+pii/My8c8qG8XEq7VRhQ4PhltlU+clsyMjFLG1LMa0NnZ/ux5jL5/gAQ4ZKuUv2UYO7TNViRijJJW8WaFuCQ=="},"children":[{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"1ZB9jIrGvpdCtFCyYWuYNCB6CmeOnoS0bWw1GHwZ0tIunD7aSq45Iog31j9b3ikhw8rkh3H8VmL5M5KqKixNgA=="},"children":[]}]},{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"1ZB9jIrGvpdCtFCyYWuYNCB6CmeOnoS0bWw1GHwZ0tIunD7aSq45Iog31j9b3ikhw8rkh3H8VmL5M5KqKixNgA=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"jeep.example","signature":"4PjsTPqFszhE2XSjcdJ7VMGgc7qOC6UYWtUXSqT6fjcOEJiSO0arx9Bk/+J8OIOqxbWM1rm9wsUptghEfP901Q=="},"children":[{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"yBiJMAKa9TbB1iPO4DluOWNm+esEH52ZFEp9RaZFEEq0VVUKC9fVcEw96MIX0EHWZzJealkQ3bVTPzH60aCQuw=="},"children":[]}]},{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"yBiJMAKa9TbB1iPO4DluOWNm+esEH52ZFEp9RaZFEEq0VVUKC9fVcEw96MIX0EHWZzJealkQ3bVTPzH60aCQuw=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"oldsmobile.example","signature":"H+M01TrM2B+N3Fe5jBAC9DnfsRkAWH6XwJ9RtSvyD9HsV01XMGdIpU9gZIU7tK/4a7rP77BYZrMTnnRp2jvk6w=="},"children":[{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"rGda/hfIZrazdLm3Y5bE+A2UPXsPFHPgSxdvmMIIKqyWJ08IAel6U/UElOMigQw8Z3Edg3DaElkd6HkHY/168Q=="},"children":[]}]},{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"rGda/hfIZrazdLm3Y5bE+A2UPXsPFHPgSxdvmMIIKqyWJ08IAel6U/UElOMigQw8Z3Edg3DaElkd6HkHY/168Q=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"chevrolet.example","signature":"MDR9ovQ1LnEGl0ICMS2G49lGKMsPHkBt9t4SdFqjwr37w6+jn1RPlSm45f/qfxiTKw/LcOZsCwnPPerA60gyuQ=="},"children":[{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"hql6bJiXUR7DHhUhI+Abt/ZIJEbIiT/akW77DKB0t8qy5PURrHpVUZYL06DDjosgjE8kLxOaPg6qh86l4PXXNg=="},"children":[]}]},{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"hql6bJiXUR7DHhUhI+Abt/ZIJEbIiT/akW77DKB0t8qy5PURrHpVUZYL06DDjosgjE8kLxOaPg6qh86l4PXXNg=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"Chrysler.example","signature":"YZQ5k/dQCuCg4bOLxygwBsGLlneZCu5Vei5Z9/ONsY47R38Sm7Av6/788ivbePKKTD4JDym2ycj/CWA3CYSz1w=="},"children":[{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"lb0WEpBqpjGGzpSNtKKzDtg77GDuNg6yR7HF7uFn8ZqycCdxR0aFV/i0SOqhBbqr5ccACSfo9CbMfpaSElEoZg=="},"children":[]}]},{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"lb0WEpBqpjGGzpSNtKKzDtg77GDuNg6yR7HF7uFn8ZqycCdxR0aFV/i0SOqhBbqr5ccACSfo9CbMfpaSElEoZg=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"tesla.example","signature":"8LjrhOKW15xEzK8LEyQrzTWn4sg5o/M5sBig9G81OVmxfhBRYdqXT64H7u7QNMAeaF/F6ruHvSNd3630IPgXzQ=="},"children":[{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"DAjvsO1VVVEYmsDFUo41g4iNZE3jl1OME37FdjqhgYyHm2Oh4TRt6lM0jLvaDTvDpZcoB0aWvw1MA9qRXqi4lg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"zQpGTi/jkNUE/wHD7e0TyZESSB8GAM0+CROJgzNayFl+6FUjpZ/1nDH4y6oyqBFdFsuYRWmLMPbSZWL5zPoQAQ=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"6NP+LLck5erA7rKQZUSTQwuyN2s6q/e24259edDdpcicm9G4q3QOgkM9OVJ+Pso+z7dOP9y+imHuUOy2178huw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"hGR4fiIQ8pNbxgVo55UTGGN4ZDT3iB8mg7T7GX++y/GNntJwMJpAflHvOWBc30i+jy4SCme4xfXzJ+pXyo0Z2Q=="},"children":[]}]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"DAjvsO1VVVEYmsDFUo41g4iNZE3jl1OME37FdjqhgYyHm2Oh4TRt6lM0jLvaDTvDpZcoB0aWvw1MA9qRXqi4lg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"zQpGTi/jkNUE/wHD7e0TyZESSB8GAM0+CROJgzNayFl+6FUjpZ/1nDH4y6oyqBFdFsuYRWmLMPbSZWL5zPoQAQ=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"6NP+LLck5erA7rKQZUSTQwuyN2s6q/e24259edDdpcicm9G4q3QOgkM9OVJ+Pso+z7dOP9y+imHuUOy2178huw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"570aaed6-ab27-4217-b33a-08e8dfa729ad","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"hGR4fiIQ8pNbxgVo55UTGGN4ZDT3iB8mg7T7GX++y/GNntJwMJpAflHvOWBc30i+jy4SCme4xfXzJ+pXyo0Z2Q=="},"children":[]}]},"resolver":[["automobiles.example",{"version":"0.1","name":"automobiles","type":"operator","dpo_email":"dpo@automobiles.example","privacy_policy_url":"https://automobiles.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjK+95EWohsODnU4t+dkBPzcSOrg8\nuXT/q6yUzCYlvhWcsLgObRhwpwyRVeljOGjrranSHnJ54P6ZdElWa6trxw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-t.example",{"version":"0.1","name":"Model T","type":"vendor","dpo_email":"dpo@model-t.example","privacy_policy_url":"https://model-t.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERyrSna5wACWBulJvs6haDjAZX5nK\nku/r0G14bORLoHSuNoH7QyjHKD+YyWOs3b4kiN2SQu+u/6x6sL6Dk1uMog==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["ford.example",{"version":"0.1","name":"Ford","type":"vendor","dpo_email":"dpo@ford.example","privacy_policy_url":"https://ford.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4Nn/EFPcN5AYQRBLy2K3/TGaowVc\nh+znQEdmJVkPie2ZoKtKU8C9fxSnRvMQp2oBDshUjbV1+8geshgUbMPsxw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["mustang.example",{"version":"0.1","name":"Mustang","type":"vendor","dpo_email":"dpo@mustang.example","privacy_policy_url":"https://mustang.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+UKlyV8u/H/1TwNxpQG7OxVWT59x\nmb2PwLBDqqKac1gJlZJUg+wse+YvnSSUlSzs1NhB+FxP2pWC/Tx0NPySLA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-18.example",{"version":"0.1","name":"Model 18","type":"vendor","dpo_email":"dpo@model-18.example","privacy_policy_url":"https://model-18.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4uOmfWvxFmahFS678G4cPY1gTmsk\nk1Umo7YUag8RKHFL/1w9m19LhNJTr7uPr3yRisb+lxbaln2zKoXaZJrrEA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["united-states.example",{"version":"0.1","name":"United States","type":"vendor","dpo_email":"dpo@united-states.example","privacy_policy_url":"https://united-states.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXdoADNAsQDSPuGA3S2GdffDudxEJ\nKk+xjPNF0Zc9KDERPutouu6af4ZQTcaqWiXtWtRe6o/rt77Mf3vZlpML2A==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-sj.example",{"version":"0.1","name":"Model SJ","type":"vendor","dpo_email":"dpo@model-sj.example","privacy_policy_url":"https://model-sj.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE94eYeTPqVb5kyhTjkU7OgJrFjuHu\nQpb0hgt1N88KnTlnkJYd7lXwmlTPgpD+/ygsKr7HllAjntgLGbMnRQRpbw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["duesenberg.example",{"version":"0.1","name":"Duesenberg","type":"vendor","dpo_email":"dpo@duesenberg.example","privacy_policy_url":"https://duesenberg.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESn7S2MhYrYQ9WKUN19kSE2Nh0fBK\nXUFlmGHIKwS/K5SupHunoYlB7RWZGx5A7dsfL/1ii4K3zLObFvS6wpwpow==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["mb.example",{"version":"0.1","name":"MD","type":"vendor","dpo_email":"dpo@mb.example","privacy_policy_url":"https://mb.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfeHtS/X4HcS/Y46OQktE8E7FWwxN\njR+P3Pb07IDwBpG/XPPxA6HJnc1Q8IZcLgkvRXTGI8SB2wzf2eSsIcDM0w==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["jeep.example",{"version":"0.1","name":"Jeep","type":"vendor","dpo_email":"dpo@jeep.example","privacy_policy_url":"https://jeep.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqUuc+5QEjtkdYpjdim4vrwaTWPHT\n1tF3RybWRo2sYd6ZzOFB7LnXwkvi0qheUl58eE20N4rL/J43aCe+jiIDAw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["rocket-88.example",{"version":"0.1","name":"\"Rocket 88\"","type":"vendor","dpo_email":"dpo@rocket-88.example","privacy_policy_url":"https://rocket-88.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6F1qASsO0EFEBi4/P3l74jSqf1rJ\n6Jci32o/aFsLJwk8TxJihxlw/UlYFfFjTTky2WqWBZQYVZ8O55YvHsa1/g==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["oldsmobile.example",{"version":"0.1","name":"Oldsmobile","type":"vendor","dpo_email":"dpo@oldsmobile.example","privacy_policy_url":"https://oldsmobile.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3M2ODO1Q3izdu7SnsQLUv8G8eVsw\nviADDddnYVg6n+wBcYu0NbiAxSysDNki1aEk+UHj3atPC1vXsMZqQvhDuw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["corvette.example",{"version":"0.1","name":"Corvette","type":"vendor","dpo_email":"dpo@corvette.example","privacy_policy_url":"https://corvette.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZnaZ39DkR0GPKXzxoKM0D6HOD5CA\n45kEaoi7MdnOhydhJDSI7aJ39Ykoda+c/Qd7ox8zVlVZaiGXdUpbd30CEg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["chevrolet.example",{"version":"0.1","name":"Chevrolet","type":"vendor","dpo_email":"dpo@chevrolet.example","privacy_policy_url":"https://chevrolet.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExoAg1UX+pswDoqyrj9vzHB2CnDZ0\n+9LLoRdtNfKJYKVnys2kCFn7/EZ5JDlMBynlEbYixgjX9AphCezXef4/+Q==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["minivan.example",{"version":"0.1","name":"Minivan","type":"vendor","dpo_email":"dpo@minivan.example","privacy_policy_url":"https://minivan.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERcMnpNFqYNwxhCGVlqzeCcgliDIm\n227POSAEMUZMGHrMSJxnY4+eukVxuA/9ydCL8B4RRL+tB1bsB33NpEMbNw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["Chrysler.example",{"version":"0.1","name":"Chrysler","type":"vendor","dpo_email":"dpo@Chrysler.example","privacy_policy_url":"https://chrysler.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEymYHDK4hSUHi5TqjlW0SI9sT9KIA\nYiaPmUB1mKU3lnEJrhN+LZpOcbRTLErPR+d2RlgwES9gvMM7QY/0yI6fAQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-s.example",{"version":"0.1","name":"Model S","type":"vendor","dpo_email":"dpo@model-s.example","privacy_policy_url":"https://model-s.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELtLSTDMNoU1M+GC6t+rMgJIEdHvi\nplj9PxQkXSD14kc4Zhc0hBpiKQL4k3T1TZydbsI0RVjc/HTjsvPUENGZQg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["tesla.example",{"version":"0.1","name":"Tesla","type":"vendor","dpo_email":"dpo@tesla.example","privacy_policy_url":"https://tesla.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEinqpBLvxb/FwTyT/bE1ZiVn142B\n+9y6iZWyMXEZqN/7PpyAPwBjuBk3HhJ+PjSsHxYK4DAO7r/itFY4VL+z5A==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-x.example",{"version":"0.1","name":"Model X","type":"vendor","dpo_email":"dpo@model-x.example","privacy_policy_url":"https://model-x.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExiIi5EXaFq4tafmWbNlO/A4nGZSP\nyxuWlafOfF5BjzzMCrwB9wiVMlxkS7wm+eIFnFe4BFHo0q19ngwPHYW89A==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-3.example",{"version":"0.1","name":"Model 3","type":"vendor","dpo_email":"dpo@model-3.example","privacy_policy_url":"https://model-3.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqzNvrFV0vwta3FIxjgtrZrS91847\n1B86W5lIztoubWuGm3BoXnEf0SPL9ZLA56es84CGH+OFgYiJXL09nZAiQg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-y.example",{"version":"0.1","name":"Model Y","type":"vendor","dpo_email":"dpo@model-y.example","privacy_policy_url":"https://model-y.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQV+/oqwYcW8eBa2AcmykrEsF2fK6\nOAjM+RniKQuJP2+8IJ8M96Wr0WDx0UdEm0ejR43O5UzESrqEZJ7M4QWrhA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}]]}
\ No newline at end of file
diff --git a/paf-mvp-audit/assets/mocks/corrupt-signature.json b/paf-mvp-audit/assets/mocks/corrupt-signature.json
new file mode 100644
index 00000000..20aa4478
--- /dev/null
+++ b/paf-mvp-audit/assets/mocks/corrupt-signature.json
@@ -0,0 +1 @@
+{"auditLog":{"version":"0.1","data":{"identifiers":[{"version":"0.1","type":"paf_browser_id","value":"2692801f-ad56-4cec-9c9a-99a4dd433022","source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"pNY3y6KhAeoOuhmW6us0TVu0/C0HmSH8aGAIgE6GkRXT8TepcKzFTsA88KeATplJYBF2fFkLBY51EYjxWwhi/w=="},"persisted":false}],"preferences":{"version":"0.1","data":{"use_browsing_for_personalization":true},"source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"MbYnvvNmcIPvhmZ/CY2trOIABH/QG8jhExMMsqYFnQPQc79N7nLuJs5dVD7q93QR21PX51lYmF/z+6gqYomMTA=="}}},"seed":{"version":"0.1","transaction_ids":["584ad050-32c0-4d35-b3df-f86888772b75"],"publisher":"automobiles.example","source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"Od/fwq5b6EbbRN8I0oBB0sSHun+eiMAHt5tH9XfiTDv+z0maexRzeOB0Y+8DmNwW765rpIcK08nSJbyyXcx+aA=="}},"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","transmissions":[{"version":"0.1","receiver":"automobiles.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"united-states.example","signature":"JK9kPcw/5MLTebcH6NorAh2b3EKKp1uDcNlll1uE3OIy/mWWxglZqu256IvwU0mk82qi7pih7u3wQ29GeBXX4Q=="},"children":[{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"ford.example","signature":"+fnRduJwGiJnblaOom2K/SIm/Yq2WjQAh/7gTxYLss4QmghEHqpZzYnqR1Y/SpcHnl7qPoOz+COvh/tHN5Opzg=="},"children":[{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"nltmMaxd5p30EDM87DEHWBE3W3wdRuS7Qv7VX82m1QoNEUsITvixcKhiGM60JuiCtImUhT6o3f8i2v7i6XppcA=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"qnsTMxwntQB56MkgguGYoYs2IrY7p67cZPxiD2LG8I/JaQJL8aliMKCmcb5Y6gq7Xg+6eJFmgWi1Ex/PMwuzag=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"q7SgyMCUXffMGcE560f7KLBR9SI0ccISQ5k7/IHVWq3ajEzac4SL4rXj2Ny/8iicf6uhnqMz3Y7z9exfXMh9hA=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"duesenberg.example","signature":"Qib/O4ch+nPMhyFrvLWGOOUUxBPnRfKalpF20Q/lXyrjlxAjUicV53GhxFL36LdvYUHrUwOwBUA2tZHMHmqObA=="},"children":[{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"ILm/Cqvq9mxU8NKDBbJlLC7rkR1Hl1tEjyuZeS+zdc4Im2uo+y+xQ8ZRf44SgIf+w+XpaM/5d57eMfFbH65e6Q=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"jeep.example","signature":"fx28PYB+gCNjC6IJ51P+UYzi68O4sQCYfPsSMfF5wSF4ED1GaHdrEjCF28hLosmlebP+QPRFLb6To7EHdtskrw=="},"children":[{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"kl5MQc2mdnghW9U8Dmgc+vkxBzuCT724Y4KOM6Kiqstbkj8mX5w5F6YTAezt4B89X6pDMTYPU1mTCk3aORdOlQ=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"oldsmobile.example","signature":"WfOd+AfE1883pHrGJeN98TW9uC3CnPIDMIcFJZKuJLR5OXsR4lEBtV4MyQiJAgsiIskc7Z5Eeqtp0mKwETg/9Q=="},"children":[{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"VBL7VLQMZRhGKdXHTVxy6SgXbWkpLqxoOGFMW4Fh/xQN0G0C9B7x8R4MD8Tn3KJS0q2eA72hvz7TTey9aYa1WA=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"chevrolet.example","signature":"xTedYAr8uRrS1+dbIC5vVSDLpu2Uazz6Oex0Q/3gYClS0GUA64GYHoCfGHuCNIpYO+2dFMeIMU0eZAcHil4U/Q=="},"children":[{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"u4aDnD7kX57cNRX1m23g6j86QVQo6r6sarc9Anl4H4MPTW2NMP0nkWsIjL7ChG76LC7nVcNttjIC2iPC9WnX0A=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"Chrysler.example","signature":"L/Ku2k5FoRswTN5EBDBCSPp1TG64ku23mojU7ZQQkJgX+WG2b3Og9Xjs6gdH6bXbQVXHL38ovZexiCS1ZNlFHw=="},"children":[{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"0eTzBzi3TtcxmNWRYHmErxSnHhD8xfafpY8WUAZ6GeBq26okO0hd3YH4b605WGLSviCzxX9sOqdZhQR36xdG3wA=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"tesla.example","signature":"ImSJ1n/1QlS++9nU2XmrJ2pW93B7uQ3gevwpC3fLPRou7XQrCojrj/uiVwGMGeVNUGvffYja8g0lQ/xBvbgzHg=="},"children":[{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"ME5EkUS1L8IU/xq1t63UzElyORoTrN7pZTiPnmY+8MsJH+jXIoeMCvX0qSy4pxx64QgyYTZmh4ldyOZQVsCUdw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"hPw4tgKM5vbfdC+5eYVGmgLaLv1ZooUm7im49k9wadVJzcXjJcrXoewYxLQcMpqrZ3+XEekRVKGivp9zGJSSRg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"V+Ws9m42zFeCBLwMwVJKSjxTv19RAEvhdtoHrzOC6ozdC0u/Y7OwSigbxh0kuN2An8g1vivq5avsYwbesy0O+Q=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"nLlxuEbe3NAvO4EwazKpy16gOJVegQnjkCdNks1ATEtxcTkjVl8u/guKnZG9RWkm7z6RgT23dmWaLAtbPzYwQg=="},"children":[]}]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"ford.example","signature":"+fnRduJwGiJnblaOom2K/SIm/Yq2WjQAh/7gTxYLss4QmghEHqpZzYnqR1Y/SpcHnl7qPoOz+COvh/tHN5Opzg=="},"children":[{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"nltmMaxd5p30EDM87DEHWBE3W3wdRuS7Qv7VX82m1QoNEUsITvixcKhiGM60JuiCtImUhT6o3f8i2v7i6XppcA=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"qnsTMxwntQB56MkgguGYoYs2IrY7p67cZPxiD2LG8I/JaQJL8aliMKCmcb5Y6gq7Xg+6eJFmgWi1Ex/PMwuzag=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"q7SgyMCUXffMGcE560f7KLBR9SI0ccISQ5k7/IHVWq3ajEzac4SL4rXj2Ny/8iicf6uhnqMz3Y7z9exfXMh9hA=="},"children":[]}]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"nltmMaxd5p30EDM87DEHWBE3W3wdRuS7Qv7VX82m1QoNEUsITvixcKhiGM60JuiCtImUhT6o3f8i2v7i6XppcA=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"qnsTMxwntQB56MkgguGYoYs2IrY7p67cZPxiD2LG8I/JaQJL8aliMKCmcb5Y6gq7Xg+6eJFmgWi1Ex/PMwuzag=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"q7SgyMCUXffMGcE560f7KLBR9SI0ccISQ5k7/IHVWq3ajEzac4SL4rXj2Ny/8iicf6uhnqMz3Y7z9exfXMh9hA=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"duesenberg.example","signature":"Qib/O4ch+nPMhyFrvLWGOOUUxBPnRfKalpF20Q/lXyrjlxAjUicV53GhxFL36LdvYUHrUwOwBUA2tZHMHmqObA=="},"children":[{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"ILm/Cqvq9mxU8NKDBbJlLC7rkR1Hl1tEjyuZeS+zdc4Im2uo+y+xQ8ZRf44SgIf+w+XpaM/5d57eMfFbH65e6Q=="},"children":[]}]},{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"ILm/Cqvq9mxU8NKDBbJlLC7rkR1Hl1tEjyuZeS+zdc4Im2uo+y+xQ8ZRf44SgIf+w+XpaM/5d57eMfFbH65e6Q=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"jeep.example","signature":"fx28PYB+gCNjC6IJ51P+UYzi68O4sQCYfPsSMfF5wSF4ED1GaHdrEjCF28hLosmlebP+QPRFLb6To7EHdtskrw=="},"children":[{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"kl5MQc2mdnghW9U8Dmgc+vkxBzuCT724Y4KOM6Kiqstbkj8mX5w5F6YTAezt4B89X6pDMTYPU1mTCk3aORdOlQ=="},"children":[]}]},{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"kl5MQc2mdnghW9U8Dmgc+vkxBzuCT724Y4KOM6Kiqstbkj8mX5w5F6YTAezt4B89X6pDMTYPU1mTCk3aORdOlQ=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"oldsmobile.example","signature":"WfOd+AfE1883pHrGJeN98TW9uC3CnPIDMIcFJZKuJLR5OXsR4lEBtV4MyQiJAgsiIskc7Z5Eeqtp0mKwETg/9Q=="},"children":[{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"VBL7VLQMZRhGKdXHTVxy6SgXbWkpLqxoOGFMW4Fh/xQN0G0C9B7x8R4MD8Tn3KJS0q2eA72hvz7TTey9aYa1WA=="},"children":[]}]},{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"VBL7VLQMZRhGKdXHTVxy6SgXbWkpLqxoOGFMW4Fh/xQN0G0C9B7x8R4MD8Tn3KJS0q2eA72hvz7TTey9aYa1WA=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"chevrolet.example","signature":"xTedYAr8uRrS1+dbIC5vVSDLpu2Uazz6Oex0Q/3gYClS0GUA64GYHoCfGHuCNIpYO+2dFMeIMU0eZAcHil4U/Q=="},"children":[{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"u4aDnD7kX57cNRX1m23g6j86QVQo6r6sarc9Anl4H4MPTW2NMP0nkWsIjL7ChG76LC7nVcNttjIC2iPC9WnX0A=="},"children":[]}]},{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"u4aDnD7kX57cNRX1m23g6j86QVQo6r6sarc9Anl4H4MPTW2NMP0nkWsIjL7ChG76LC7nVcNttjIC2iPC9WnX0A=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"Chrysler.example","signature":"L/Ku2k5FoRswTN5EBDBCSPp1TG64ku23mojU7ZQQkJgX+WG2b3Og9Xjs6gdH6bXbQVXHL38ovZexiCS1ZNlFHw=="},"children":[{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"0eTzBzi3TtcxmNWRYHmErxSnHhD8xfafpY8WUAZ6GeBq26okO0hd3YH4b605WGLSviCzxX9sOqdZhQR36xdG3wA=="},"children":[]}]},{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"0eTzBzi3TtcxmNWRYHmErxSnHhD8xfafpY8WUAZ6GeBq26okO0hd3YH4b605WGLSviCzxX9sOqdZhQR36xdG3wA=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"tesla.example","signature":"ImSJ1n/1QlS++9nU2XmrJ2pW93B7uQ3gevwpC3fLPRou7XQrCojrj/uiVwGMGeVNUGvffYja8g0lQ/xBvbgzHg=="},"children":[{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"ME5EkUS1L8IU/xq1t63UzElyORoTrN7pZTiPnmY+8MsJH+jXIoeMCvX0qSy4pxx64QgyYTZmh4ldyOZQVsCUdw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"hPw4tgKM5vbfdC+5eYVGmgLaLv1ZooUm7im49k9wadVJzcXjJcrXoewYxLQcMpqrZ3+XEekRVKGivp9zGJSSRg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"V+Ws9m42zFeCBLwMwVJKSjxTv19RAEvhdtoHrzOC6ozdC0u/Y7OwSigbxh0kuN2An8g1vivq5avsYwbesy0O+Q=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"nLlxuEbe3NAvO4EwazKpy16gOJVegQnjkCdNks1ATEtxcTkjVl8u/guKnZG9RWkm7z6RgT23dmWaLAtbPzYwQg=="},"children":[]}]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"ME5EkUS1L8IU/xq1t63UzElyORoTrN7pZTiPnmY+8MsJH+jXIoeMCvX0qSy4pxx64QgyYTZmh4ldyOZQVsCUdw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"hPw4tgKM5vbfdC+5eYVGmgLaLv1ZooUm7im49k9wadVJzcXjJcrXoewYxLQcMpqrZ3+XEekRVKGivp9zGJSSRg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"V+Ws9m42zFeCBLwMwVJKSjxTv19RAEvhdtoHrzOC6ozdC0u/Y7OwSigbxh0kuN2An8g1vivq5avsYwbesy0O+Q=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"584ad050-32c0-4d35-b3df-f86888772b75","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"nLlxuEbe3NAvO4EwazKpy16gOJVegQnjkCdNks1ATEtxcTkjVl8u/guKnZG9RWkm7z6RgT23dmWaLAtbPzYwQg=="},"children":[]}]},"resolver":[["automobiles.example",{"version":"0.1","name":"automobiles","type":"operator","dpo_email":"dpo@automobiles.example","privacy_policy_url":"https://automobiles.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXuSY2yvJItZcJC+cNljOACoHqauc\nENWgmkurlwxFjacWU+5PwcgbuHnC7JnNQCugNodp4noM96qKK7/5LZy42A==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-t.example",{"version":"0.1","name":"Model T","type":"vendor","dpo_email":"dpo@model-t.example","privacy_policy_url":"https://model-t.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7jz9LZaReKQiZHdR3Szzl7PnnUdi\n6cknS+Y+TSzn4DtnXNKvrDF/Guy482Dzb/Ml2Xn2RU9oFhixpgEVPshUfQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["ford.example",{"version":"0.1","name":"Ford","type":"vendor","dpo_email":"dpo@ford.example","privacy_policy_url":"https://ford.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEp3Amg4bk37EPhParuh+sIkNnZTl/\nUksZN23ilZH6AyJo9SbpkMj+EsY9hWqWBlYb1X0BkVhnu2mLOAT6dmzVXQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["mustang.example",{"version":"0.1","name":"Mustang","type":"vendor","dpo_email":"dpo@mustang.example","privacy_policy_url":"https://mustang.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Lkzslxpw0bJIkJ6xOlOksh2s6d/\ntnn5m34s+jSLUO4VY3edEoHwTNYU+8maZHt2gIJJC/S/YWoxUZrglCFcCw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-18.example",{"version":"0.1","name":"Model 18","type":"vendor","dpo_email":"dpo@model-18.example","privacy_policy_url":"https://model-18.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUwLb0RBCm2oS65hqicblhSPtIyu2\nOISSUJrzSZXlffR0t2thoVsdlLIurJOieL4vcMvVn35ungAXZ0m1q2BQLg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["united-states.example",{"version":"0.1","name":"United States","type":"vendor","dpo_email":"dpo@united-states.example","privacy_policy_url":"https://united-states.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEELH1BmEHXSKvBjxlfJ8G8WaMLtXl\nJSSvnrR/eQXafoEQXIMrO+5X9RxqpdubbXDejBEi2FUV9KJfBQhavs1MpQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-sj.example",{"version":"0.1","name":"Model SJ","type":"vendor","dpo_email":"dpo@model-sj.example","privacy_policy_url":"https://model-sj.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHxmJ1LlAHLBJ3WH+Z951NxfXoG7L\ncaNhAhvXW9Nce/rnWxSacCWs1OZtGN/gSVviGJr4pCvM1/ZienhcTjgdXw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["duesenberg.example",{"version":"0.1","name":"Duesenberg","type":"vendor","dpo_email":"dpo@duesenberg.example","privacy_policy_url":"https://duesenberg.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUMF3CBsvarwx+JJB6TOt9LWa0Nsr\n4HE4Y/HHKghjialNCDzauJ88CHBNmFJdvyumyA3DnuRnQBowouDQ2OoGIg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["mb.example",{"version":"0.1","name":"MD","type":"vendor","dpo_email":"dpo@mb.example","privacy_policy_url":"https://mb.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL4woLvdcDVYfplQH/zsUm18Okx7X\n/cyISauwAnCpXPN5RqiK8+ACf02U3BIw0JblLHJETjGSaZ1/ornzO+BAEw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["jeep.example",{"version":"0.1","name":"Jeep","type":"vendor","dpo_email":"dpo@jeep.example","privacy_policy_url":"https://jeep.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEO2AFA4cTHkFqRN22erA3+UYdHqmV\n/L8zpywyXad72IsOwSk1mlUggiwDwGnVQOhUMILVDOK2JPkiWRmGbtJ76g==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["rocket-88.example",{"version":"0.1","name":"\"Rocket 88\"","type":"vendor","dpo_email":"dpo@rocket-88.example","privacy_policy_url":"https://rocket-88.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnFGYHACtC2Rxz3ZXJg72iHBl5zPu\nyk+v2+afmXPLsoePHlZ1FUcdAo64XMQaVfdgSyRxUpw6d2umt56ra8zd+A==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["oldsmobile.example",{"version":"0.1","name":"Oldsmobile","type":"vendor","dpo_email":"dpo@oldsmobile.example","privacy_policy_url":"https://oldsmobile.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAromuPxKezf6zWcP7oObREtVASLh\niKx+UhHABrfGlGsWEDHOJ7hTr8D6aDkpGPjTBEu8N+RwS8oZ1lFWvlrpkQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["corvette.example",{"version":"0.1","name":"Corvette","type":"vendor","dpo_email":"dpo@corvette.example","privacy_policy_url":"https://corvette.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQHB52L6+Z1NZ1X4Y7ydM8c9mVIsc\nRF9ASOndD/sEPzhsQ78OwbdgBQpMloKKGlWDN8yn0i3i6wByoph8jarhmA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["chevrolet.example",{"version":"0.1","name":"Chevrolet","type":"vendor","dpo_email":"dpo@chevrolet.example","privacy_policy_url":"https://chevrolet.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQWK2Uf3SEm+iEYkqdjjz3ipcT9Sx\nJKkctXEtKmyvRFvDhL2CsO7PvvOPGkPXtaBs00KK/Y1y3PtuirTgs1BZkA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["minivan.example",{"version":"0.1","name":"Minivan","type":"vendor","dpo_email":"dpo@minivan.example","privacy_policy_url":"https://minivan.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjWO6mYutah0nIzPOtpfyPZCuq1EF\nGOgbXx1Kcb40x0EjFag5nhyBJJhl9kT18fK2pachx/LoCMigvOvSPpRANA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["Chrysler.example",{"version":"0.1","name":"Chrysler","type":"vendor","dpo_email":"dpo@Chrysler.example","privacy_policy_url":"https://chrysler.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmzdUOUKcEM9+GZE0Vm0Yy7hHsZ8L\nakAHXdgV10IftoQKqMT0LQa9MAhU3GhuNppgk/yb+A4q4YgZyxZHvBHDvg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-s.example",{"version":"0.1","name":"Model S","type":"vendor","dpo_email":"dpo@model-s.example","privacy_policy_url":"https://model-s.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvPyJjRst/Ri/ZyFigyc7lBGnDTKI\ni4d4wDRi+xhSxWuQYlbqFQ/VvZK0Se30MA4so31wLIRRokpOGzQhn+2drg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["tesla.example",{"version":"0.1","name":"Tesla","type":"vendor","dpo_email":"dpo@tesla.example","privacy_policy_url":"https://tesla.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHBe1UnF+4/AO2LE97iMIEoi4+GzS\nADYnwenG3LEcG5YQpaE+uSw4HsgCRwCkfHqrtO86/mgfyQdtGRlHmxS5Cg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-x.example",{"version":"0.1","name":"Model X","type":"vendor","dpo_email":"dpo@model-x.example","privacy_policy_url":"https://model-x.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ0f3JJJcyerbhiRMTqaoyyuM9qmf\nFxfwaDfjvlhLO4QuzCeNXGor519tEqtoJCKErssRNdkz+VyKa7+jNT03uQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-3.example",{"version":"0.1","name":"Model 3","type":"vendor","dpo_email":"dpo@model-3.example","privacy_policy_url":"https://model-3.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECrmDTG/ivCqNkMHP94oC72qPnbYu\nI/Hbmr5qzq3Uqqj/YU8Q8BX95iiTAkwPWES3SjIHILUU2C+eHu06Y2Anrw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-y.example",{"version":"0.1","name":"Model Y","type":"vendor","dpo_email":"dpo@model-y.example","privacy_policy_url":"https://model-y.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEev7a9tpJkXKPeDmlDNUtYmIHVwHl\nowAPWq/b166rCFnjcDjEGk+P/UanHA9Y/8Ey3TUHY8nEi4ny9rQTD+dW0g==\n-----END PUBLIC KEY-----\n","start":1656959907}]}]]}
\ No newline at end of file
diff --git a/paf-mvp-audit/assets/mocks/identity-not-found.json b/paf-mvp-audit/assets/mocks/identity-not-found.json
new file mode 100644
index 00000000..b52761c1
--- /dev/null
+++ b/paf-mvp-audit/assets/mocks/identity-not-found.json
@@ -0,0 +1 @@
+{"auditLog":{"version":"0.1","data":{"identifiers":[{"version":"0.1","type":"paf_browser_id","value":"6364981f-29ff-4a06-94ae-33f3b2bc8025","source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"qk7X1kLz8iro8tS33uFYpTKvmuQELcqxY658oCyEIdZApLkrlO+IY7SSWJo4UNm8QpbWkPwQ5T0UY+0hZr/V6A=="},"persisted":false}],"preferences":{"version":"0.1","data":{"use_browsing_for_personalization":true},"source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"IfanjH9i3ks/eBVpaUn6QDaJh0lAHdkUIcaYXLFsm8vF9yoJlssydqhCYvgejTRxVe9m1/5vL/NaYGMf4Muj+Q=="}}},"seed":{"version":"0.1","transaction_ids":["f280eccf-12de-4651-ba4e-7ae0f284ddb4"],"publisher":"automobiles.example","source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"EzMS0qeTFlo/S+4a4uCsum46t9FTklP4t/Xnzm6ZnyT3cOUZX8ngAXMkHpdduGzbO+63ZQwtKii2xoD2vLd3Ag=="}},"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","transmissions":[{"version":"0.1","receiver":"automobiles.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"united-states.example","signature":"6Z7naQK2Kgzbp9xsDabeT5S+/GAy++T8XaIMZ682BY9ImXMBHYY+dabgO3KObS3BKgz/oa7nnZy2MHZ+fuNCOQ=="},"children":[{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"ford.example","signature":"ELVxhJFO+sYkKX80wNNis22zbcJEPC9pLR5sX9yEndBuhjqjfFc763kqtn6URpEJvq+RMoR6G2Y+7JaQj/hd+w=="},"children":[{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"KJVOM1pl15mSkg6Mzt7eCaA6itFXlltFS6QefoRt8uerPzPfBMLT576lrP9vF0eKydHxD0SsKtE6tbtZzn1s8w=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"doG1EVvcpBgrKAiJ+rhObgHO/FJYQUkExK/+27JYtY5fyTlx6YWhgMw/1gSEU12THlrLh5OuUK58dlKV+GovIQ=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"Jx64rTji+0DL3AW2GTKZs6v8+BQnLLgHwe2sWajOMOuz8tDyKa7HCrtbOMbOT6J9XvzwHbI2W2v2ojWdmbZu/g=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"duesenberg.example","signature":"FD5eshhLDP3CVkiro04DydpwCFgPA4ykVlELoZXFXrhdMR0/Gj0b5PAGvP0+qqLHvl8x9NTBptAm6V/ViL6XBw=="},"children":[{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"e0uJuvczEu+mQnl5xGjADXCA78NzbwPOze6v6ZisTRJWx1EqB/A31nDzNCtdF96izKAOQJe17+/wK3QnN2RSqg=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"jeep.example","signature":"VPiTchCTV/J8X/fEPnQMV0zYuoClKDC70Byei0Ddq63llLNVmBDxpkU9Txj+pgdOW6RPHj4O7v0o+VGsQB7eCw=="},"children":[{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"N0K6cXhptxEZuuikQMFTVRWDHFPqAc1tVYx15Dr7EHFQLZO/SGOHVnlNac69bmIcASJc5D60rvfa+dDvhTHw2g=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"oldsmobile.example","signature":"5n9p4HH+R45POuPdVcK2AdXYuZjOnmXfy7Vgz4c5dH2o9ZqSbBB0zVpJHkylMq+yzkLVBiOPW/11hzQtD0YQ9Q=="},"children":[{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"0CImWAOWk4QeqbhJP+iayfWlds0qso6kZxg+QXeB/5F7GmcAFV1Mcw3vldUYZhKnMt+U8WQPwt84pRQCgt1UHA=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"chevrolet.example","signature":"GBjSID9dtBIb4pdc4ZQEZx4mArkAo80sU5v7Dq7t3heVh6gs530SZpkchCSnJCQ8jq7udRwdZFS6tjS66Lv3WQ=="},"children":[{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"97wZuKBh/SaQCZurj+J9iRiiJAr3YBQirB2ATzUP0vCLmjwj7Y1UQ2xWAB8Kj8Kgjj8XfUr+eZOsmm8VuJWuCA=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"Chrysler.example","signature":"1C5gmEAg/ZNYTuB8X8pAOedTvpHdhIznflXFxhclbux4GvdI/0kRVYPpx8vnky4zKas9zwzYIt5ri9QdfQebiw=="},"children":[{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"CszoDyAMbMIyXVNHbLADBLBnD0X3CXUhBcw1UhukToXBAnZhJntiUZcJ66KkMJI/mTlw+ZJS+rkbE7Gka3ubIg=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"tesla.example","signature":"ciBTWj0sWYdAPFnVMorZ3TDmbO+YLOBGPzgUJTSHxQYuR4wTCVC9Y1r0NNfl/riLcf4gWa/91IFq24RTyT9urA=="},"children":[{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"hcaG88rR7MBeQZSOmTW22B7XfKUzQVI/Fs31jkKyZ1oZLoCxyOpU4p6zA39o/xaESDrjX86pBo7OsGr7US1ckg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"IyNstY7/9PZbevalyPE6Fn9Ffe4xxQqIqi+luO+W/lGFK2RcNGB6JbuG4zC5E5vXVzLER3DxsP8ZRMU9z2r4Ew=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"XA62sK5lNmN70M6MIlzyMkf9M7RFZ2Q1/l2GrEeH7PTzQWGaBkc0wxrY8qpG1i2TEQXrkJeorzVAcWJz7Cg40A=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"J5mc3DTKxktI+wyymc9CTJIhQb3sOfHF8tMWYAvrYx5iXwrOMFrG9PNblmexNlML2Z+nD6h5WdSyAfSI3VH9wg=="},"children":[]}]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"ford.example","signature":"ELVxhJFO+sYkKX80wNNis22zbcJEPC9pLR5sX9yEndBuhjqjfFc763kqtn6URpEJvq+RMoR6G2Y+7JaQj/hd+w=="},"children":[{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"KJVOM1pl15mSkg6Mzt7eCaA6itFXlltFS6QefoRt8uerPzPfBMLT576lrP9vF0eKydHxD0SsKtE6tbtZzn1s8w=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"doG1EVvcpBgrKAiJ+rhObgHO/FJYQUkExK/+27JYtY5fyTlx6YWhgMw/1gSEU12THlrLh5OuUK58dlKV+GovIQ=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"Jx64rTji+0DL3AW2GTKZs6v8+BQnLLgHwe2sWajOMOuz8tDyKa7HCrtbOMbOT6J9XvzwHbI2W2v2ojWdmbZu/g=="},"children":[]}]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"KJVOM1pl15mSkg6Mzt7eCaA6itFXlltFS6QefoRt8uerPzPfBMLT576lrP9vF0eKydHxD0SsKtE6tbtZzn1s8w=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"doG1EVvcpBgrKAiJ+rhObgHO/FJYQUkExK/+27JYtY5fyTlx6YWhgMw/1gSEU12THlrLh5OuUK58dlKV+GovIQ=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"Jx64rTji+0DL3AW2GTKZs6v8+BQnLLgHwe2sWajOMOuz8tDyKa7HCrtbOMbOT6J9XvzwHbI2W2v2ojWdmbZu/g=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"duesenberg.example","signature":"FD5eshhLDP3CVkiro04DydpwCFgPA4ykVlELoZXFXrhdMR0/Gj0b5PAGvP0+qqLHvl8x9NTBptAm6V/ViL6XBw=="},"children":[{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"e0uJuvczEu+mQnl5xGjADXCA78NzbwPOze6v6ZisTRJWx1EqB/A31nDzNCtdF96izKAOQJe17+/wK3QnN2RSqg=="},"children":[]}]},{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"e0uJuvczEu+mQnl5xGjADXCA78NzbwPOze6v6ZisTRJWx1EqB/A31nDzNCtdF96izKAOQJe17+/wK3QnN2RSqg=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"jeep.example","signature":"VPiTchCTV/J8X/fEPnQMV0zYuoClKDC70Byei0Ddq63llLNVmBDxpkU9Txj+pgdOW6RPHj4O7v0o+VGsQB7eCw=="},"children":[{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"N0K6cXhptxEZuuikQMFTVRWDHFPqAc1tVYx15Dr7EHFQLZO/SGOHVnlNac69bmIcASJc5D60rvfa+dDvhTHw2g=="},"children":[]}]},{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"N0K6cXhptxEZuuikQMFTVRWDHFPqAc1tVYx15Dr7EHFQLZO/SGOHVnlNac69bmIcASJc5D60rvfa+dDvhTHw2g=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"oldsmobile.example","signature":"5n9p4HH+R45POuPdVcK2AdXYuZjOnmXfy7Vgz4c5dH2o9ZqSbBB0zVpJHkylMq+yzkLVBiOPW/11hzQtD0YQ9Q=="},"children":[{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"0CImWAOWk4QeqbhJP+iayfWlds0qso6kZxg+QXeB/5F7GmcAFV1Mcw3vldUYZhKnMt+U8WQPwt84pRQCgt1UHA=="},"children":[]}]},{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"0CImWAOWk4QeqbhJP+iayfWlds0qso6kZxg+QXeB/5F7GmcAFV1Mcw3vldUYZhKnMt+U8WQPwt84pRQCgt1UHA=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"chevrolet.example","signature":"GBjSID9dtBIb4pdc4ZQEZx4mArkAo80sU5v7Dq7t3heVh6gs530SZpkchCSnJCQ8jq7udRwdZFS6tjS66Lv3WQ=="},"children":[{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"97wZuKBh/SaQCZurj+J9iRiiJAr3YBQirB2ATzUP0vCLmjwj7Y1UQ2xWAB8Kj8Kgjj8XfUr+eZOsmm8VuJWuCA=="},"children":[]}]},{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"97wZuKBh/SaQCZurj+J9iRiiJAr3YBQirB2ATzUP0vCLmjwj7Y1UQ2xWAB8Kj8Kgjj8XfUr+eZOsmm8VuJWuCA=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"Chrysler.example","signature":"1C5gmEAg/ZNYTuB8X8pAOedTvpHdhIznflXFxhclbux4GvdI/0kRVYPpx8vnky4zKas9zwzYIt5ri9QdfQebiw=="},"children":[{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"CszoDyAMbMIyXVNHbLADBLBnD0X3CXUhBcw1UhukToXBAnZhJntiUZcJ66KkMJI/mTlw+ZJS+rkbE7Gka3ubIg=="},"children":[]}]},{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"CszoDyAMbMIyXVNHbLADBLBnD0X3CXUhBcw1UhukToXBAnZhJntiUZcJ66KkMJI/mTlw+ZJS+rkbE7Gka3ubIg=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"tesla.example","signature":"ciBTWj0sWYdAPFnVMorZ3TDmbO+YLOBGPzgUJTSHxQYuR4wTCVC9Y1r0NNfl/riLcf4gWa/91IFq24RTyT9urA=="},"children":[{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"hcaG88rR7MBeQZSOmTW22B7XfKUzQVI/Fs31jkKyZ1oZLoCxyOpU4p6zA39o/xaESDrjX86pBo7OsGr7US1ckg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"IyNstY7/9PZbevalyPE6Fn9Ffe4xxQqIqi+luO+W/lGFK2RcNGB6JbuG4zC5E5vXVzLER3DxsP8ZRMU9z2r4Ew=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"XA62sK5lNmN70M6MIlzyMkf9M7RFZ2Q1/l2GrEeH7PTzQWGaBkc0wxrY8qpG1i2TEQXrkJeorzVAcWJz7Cg40A=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"J5mc3DTKxktI+wyymc9CTJIhQb3sOfHF8tMWYAvrYx5iXwrOMFrG9PNblmexNlML2Z+nD6h5WdSyAfSI3VH9wg=="},"children":[]}]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"hcaG88rR7MBeQZSOmTW22B7XfKUzQVI/Fs31jkKyZ1oZLoCxyOpU4p6zA39o/xaESDrjX86pBo7OsGr7US1ckg=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"IyNstY7/9PZbevalyPE6Fn9Ffe4xxQqIqi+luO+W/lGFK2RcNGB6JbuG4zC5E5vXVzLER3DxsP8ZRMU9z2r4Ew=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"XA62sK5lNmN70M6MIlzyMkf9M7RFZ2Q1/l2GrEeH7PTzQWGaBkc0wxrY8qpG1i2TEQXrkJeorzVAcWJz7Cg40A=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"f280eccf-12de-4651-ba4e-7ae0f284ddb4","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"J5mc3DTKxktI+wyymc9CTJIhQb3sOfHF8tMWYAvrYx5iXwrOMFrG9PNblmexNlML2Z+nD6h5WdSyAfSI3VH9wg=="},"children":[]}]},"resolver":[["automobiles.example",{"version":"0.1","name":"automobiles","type":"operator","dpo_email":"dpo@automobiles.example","privacy_policy_url":"https://automobiles.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjSrpfj3KFSZwYjhFJlaM21hMs8w7\nXBsGPMV5MJQrPR93KPU58En7M1hHb+HqgmKEEsj1TdnuTO7EXckAkSEvhA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-t.example",{"version":"0.1","name":"Model T","type":"vendor","dpo_email":"dpo@model-t.example","privacy_policy_url":"https://model-t.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHlFZK1AQqvGOJSlODlNc3ihlkM/f\nqCVs6ZtwB9vUOELnzDttEYtnMjlpzjzhFV+mL97CkEY6T3cjhH7e3eCJNw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["ford.example",{"version":"0.1","name":"Ford","type":"vendor","dpo_email":"dpo@ford.example","privacy_policy_url":"https://ford.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEddKyCcj3JNZj5BvEpitPFJmigUDL\nTZxpzdANp4mupH7TrhWdcOhsr1JaXI64Dr5/Ahw96d39qGB4JV0oumRhaQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["mustang.example",{"version":"0.1","name":"Mustang","type":"vendor","dpo_email":"dpo@mustang.example","privacy_policy_url":"https://mustang.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE596bSHGtXlqSxWxjy5qAD4xbPbro\nsQpaFICLr/cvzuJTzq8nLjSt07IczouaQfgHgbpNjtJShqrwculrQIkKEw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-18.example",{"version":"0.1","name":"Model 18","type":"vendor","dpo_email":"dpo@model-18.example","privacy_policy_url":"https://model-18.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEm30BVi0eSqMsg+DcO09PePo6rJS0\nemYciGevqFD98p3V5IpBz+XFX+1I8zXPPXVp/jSDx9M7InJn6nkG+fVFNw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["united-states.example",{"version":"0.1","name":"United States","type":"vendor","dpo_email":"dpo@united-states.example","privacy_policy_url":"https://united-states.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3Yfy3D1vW16RkSvnHBfX+kSEiVVf\n9a5KDc8yscmN4y5p7sEMtQT4ZfWyVMjNAirrO0hrthl49CtUXZKIPrPg/g==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-sj.example",{"version":"0.1","name":"Model SJ","type":"vendor","dpo_email":"dpo@model-sj.example","privacy_policy_url":"https://model-sj.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3tpRPoLnMyC9O34fH+xhLZojs++i\ney3IZsrss7BsdpaT40ozODRlOOJ5SmA5dNDz1aoG/lYSh/nKIbp1w4DP0A==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["duesenberg.example",{"version":"0.1","name":"Duesenberg","type":"vendor","dpo_email":"dpo@duesenberg.example","privacy_policy_url":"https://duesenberg.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEosIr2s4SSEIDfTFJjSD9SfMjxV8W\nLIZOnWKptCdwn4icfgchL/t8uq33lvhaAuMuvDiEoR5oUolFM6xmkUziwQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["mb.example",{"version":"0.1","name":"MD","type":"vendor","dpo_email":"dpo@mb.example","privacy_policy_url":"https://mb.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAkQpiWabGdGbMtxINArs05OCBrW1\namJtrAJwZleoz5n/o9AxZf9U6YLGirEKZ4ez9/SyvumMeSc+dvs8RvAn5A==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["jeep.example",{"version":"0.1","name":"Jeep","type":"vendor","dpo_email":"dpo@jeep.example","privacy_policy_url":"https://jeep.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOAVTg3SAA72Od5oaXFMg5dxlQ0eF\nAI+gWOZRfFwbtzzeN8sk42M9JoNsZkrVj75C+bZMZVawqThvI34fPnut0g==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["rocket-88.example",{"version":"0.1","name":"\"Rocket 88\"","type":"vendor","dpo_email":"dpo@rocket-88.example","privacy_policy_url":"https://rocket-88.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8yBAFAyk6UsxEzpRhfq1kyZ4sm7o\ncLIOqITnbM5CV/abTz07mokjRqKlQuigm1w2lYCEptlfk7RaV8oDg+WoUQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["oldsmobile.example",{"version":"0.1","name":"Oldsmobile","type":"vendor","dpo_email":"dpo@oldsmobile.example","privacy_policy_url":"https://oldsmobile.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE203PN2FdixLn37BXtyKPbMhO7x2M\ndylGSqPmM1eV/ZFCyzDcFtnT0xbPDKmKiVfrKVaKwc/6e7cJAHC1s7A4cw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["corvette.example",{"version":"0.1","name":"Corvette","type":"vendor","dpo_email":"dpo@corvette.example","privacy_policy_url":"https://corvette.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjC7+Ynap57OqBfL+ombhLPP4RFIl\n8zwILNr7e/WjUTX4/+K7UlvIBug4vQtzyEbXkFJAq6goCz9m+jAxVwYkbQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["chevrolet.example",{"version":"0.1","name":"Chevrolet","type":"vendor","dpo_email":"dpo@chevrolet.example","privacy_policy_url":"https://chevrolet.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEm2JQtL8nY6TLlIqky6yzyuZwwXzZ\nDUd3OTOV9xCXigsRiYw4jFhhlJWDIseZ1piNj44cMuaZ+KlToGlw0bVQQQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["Chrysler.example",{"version":"0.1","name":"Chrysler","type":"vendor","dpo_email":"dpo@Chrysler.example","privacy_policy_url":"https://chrysler.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcOyAU1kA94IhKRxIDrjbplSraKXP\npJuloCJDqVsmqcLjivKGwoM0hcBClA3MkqWzV1zzcOx7bNMzBl5X3hMzMw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-s.example",{"version":"0.1","name":"Model S","type":"vendor","dpo_email":"dpo@model-s.example","privacy_policy_url":"https://model-s.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdc8UtBOdIiFDQhfW2FPgvW6/LpSg\nHrliQk3OzU0cebFjaQlPfPaV1wOCoHyNOT7ZtxDHwCMwf/EMZC9ZgkWxTA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["tesla.example",{"version":"0.1","name":"Tesla","type":"vendor","dpo_email":"dpo@tesla.example","privacy_policy_url":"https://tesla.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ9FvCtRz7fitnGbjJN0VCdXV5hjZ\neIWguP5jK4pPAeqyuELY81ZEd7CZRkgH+yUubXV8pGXQ1ANqdO+anDfC2Q==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-x.example",{"version":"0.1","name":"Model X","type":"vendor","dpo_email":"dpo@model-x.example","privacy_policy_url":"https://model-x.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkH13wRMqJwQNbhHkC+R6lQMS0Hcj\nbcO3LC5n0yRe3eT8WcESmeWNxUuxXSpYQZWmWhcxNrq43MUbVD4JtCxlhQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-3.example",{"version":"0.1","name":"Model 3","type":"vendor","dpo_email":"dpo@model-3.example","privacy_policy_url":"https://model-3.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1SZyPPK9pMwnf0sBiSKg3Z1CHeG3\nZR3ge7sQzKAK23LujQyQi69Jp0BTUFV0PF+bk2QXS2PRO4/v0/DIAv2yow==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-y.example",{"version":"0.1","name":"Model Y","type":"vendor","dpo_email":"dpo@model-y.example","privacy_policy_url":"https://model-y.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExt2416ucKJ5Or1HwIEDrNWg6ULSf\n1McILhKD0XAwiiGQKtsI+0rHZJKkxaN3QqhXaltQVnaz4//uetb+WxeW6Q==\n-----END PUBLIC KEY-----\n","start":1656959907}]}]]}
\ No newline at end of file
diff --git a/paf-mvp-audit/assets/mocks/result-error.json b/paf-mvp-audit/assets/mocks/result-error.json
new file mode 100644
index 00000000..d9960daa
--- /dev/null
+++ b/paf-mvp-audit/assets/mocks/result-error.json
@@ -0,0 +1 @@
+{"auditLog":{"version":"0.1","data":{"identifiers":[{"version":"0.1","type":"paf_browser_id","value":"1510d2cd-406e-41aa-bcd9-59007c48edae","source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"cowrRCpvWEZf/XQPQ1gTwg0F5VQP30ksXsBIlrzL5cFNadeCQMBRJ4Rpyft4Z9hRIA9Sv5D9Pnob6LkCagSzlg=="},"persisted":false}],"preferences":{"version":"0.1","data":{"use_browsing_for_personalization":true},"source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"jMhrg3JaDNfj+L7t2slPQAEK2LL7Qi/STC2ZSHtMetrTJT7AW0NCblJhTWKXiCrPxFvD8U/lvNG1UohSel3YQw=="}}},"seed":{"version":"0.1","transaction_ids":["da4020cb-30a9-4ad8-acbd-db8bb74d59b5"],"publisher":"automobiles.example","source":{"domain":"automobiles.example","timestamp":1656959907,"signature":"HP5YweUnfzQbj7yHgxCObZH7ZkcByGKTElOlvaI7oFLRylCA79nLndXCbuBv6XsWCZbgkWYo3zD8BbAdaTn83g=="}},"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","transmissions":[{"version":"0.1","receiver":"automobiles.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"united-states.example","signature":"yLKpWhQ9zXDGo6+gUPrPbkg2ytNTj92KG9uIeWwDX2cIRj8ezu5Jha6It2CoELC0gM22D3KJeKv5tJdAHOok1A=="},"children":[{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"ford.example","signature":"BmAyeneYBRv9lawHqW8Wwmu4Srto97BI8J/9hIkc1a4k291rF+/2eubK/JYK4sJiDgval/O/n5EFv1W+/ZuitA=="},"children":[{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"1CXCCsF0k+VOxSu3JCHTxwH5fEG2TvWmhsUe6o6/K5+gTpm19m1RYbqJ8HDDS/Unk3oLwDAquAldPN3NukpG6w=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"zOYKjgkFyn4aSnBmqRgjSPDrMeldK8IaqfLK9DuaUGkFnNQR4JIaJX4gyHm4D95nXUyEiHgLqxUHlSJ+RtLCbw=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"EgPIZ6mcgHK09Vq+j13HQgY61/ytCi9J/JB/gz5da6H8KLMxfvuFwLKZg++NNX4eut7rQcZs0KHPZEBQI9bHQA=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"duesenberg.example","signature":"vPSxa45S7eZT1tu5aa8okkQWYfsBaCxF4a/1H5egqcyuU2QcBwUgf9aN2MCB0TVjWAAqeNA8Ar3S7r9GLGx07g=="},"children":[{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"cZYIC1jvKEjdz6PMmG8UiXStc+LWGeSNu39JynCgjo+TXwIqSYqO5VWIZ+QBCuxmHWDCyYZyJ+wykOClSgrG5g=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"jeep.example","signature":"qb2m0vlqyqru4atNf6Solr5FS6JG8fLq1jXpDWVto9iFgOT2hrOtMmavDx79yb3mSOzR/UBuEsFGLqjnjp6NRA=="},"children":[{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"zIsQQfKojm7/Sl078qwjhNSKrmQej/OYI0y0Rxa2I7/WtzV/z+qH0v0us7Kp9N7TNp43yYS/zywtsJCrZVWCEA=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"oldsmobile.example","signature":"1DRbZF7WJ4bq+LQPego6zNZIT8v+HHLbhf9e9xuWHa/f6+MmzDh14KCQfZT5qzUB+GYHU31mlXgPnKFdOMAcDg=="},"children":[{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"Bz+g9eIFWO/aZlidhj3ErjSXDa0kCTrp3LZKIxqBRyxEwU+h/sNajcoY7MdbXR8yQ8Q7HowO3trvE6evPguRMQ=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"chevrolet.example","signature":"neOGC7iWbmNMlPJ6/lsMBcJQAe+xhXhJOoyHDlsUaxGq3SEyaVXscua4sbDYPTto7y0gorQidbgOmc8edfyGKA=="},"children":[{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"w7nKIGDXtH9PVuZoYXwHrVvIrU9nQReQ0fRzR0HwwOil0mnFKNy2mPUpOcmhUngnp8EjaNVZrfBZbO+9OUiPAw=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"Chrysler.example","signature":"UAG/3msNxY+YbA/iTkCGRWYMs0R8W/F09UA2x/BVNPwkQS/uk2M/WqvsEgN1cs3YCMSXXVEimbxJBtgYxJuGDQ=="},"children":[{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"error_cannot_process","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"JPzoSU7TKsw2DqmVeDpk2GsLfNltM6QUuUAwdnf4bk+adS/nPRbgdoKuMs7I8IBwib1ukT+HCMaQzEUwRaYZRw=="},"children":[]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"tesla.example","signature":"e54gD+PVr41yClGC/WfEpS8GrbXyYfZwOXSStk01z7qEfNOESSKbPYnGQQ7w3gSQyBR9JkDr1/lXdylabYLpHA=="},"children":[{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"yflGvX6s0xJHpRrxzwPWgLPCIpq385Z4wwB2ord8wf71tFnsL5Ns8VNL6r7EC27AuQqK6Jr8a0mjL+exUT07wA=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"lNaNONtxbXfHAclHBNqNkqjqAf1pAL8NnQFr32HMFoEPtiyhgIgHqi1/WaoX2h5daiuN+veO6VA5P6BBsx+yrQ=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"SshRevZFPUg7V5KsRDBhfqkARvLqwRz+RIky3ot6eXN2o7/xLv+OjUSQv0C3RaEkPUTLO8pSvKwtsefpJYe7Jw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"jgwqBPUaIjS1nrAQbmy3Xdq4WWgwv37Cy0PTPzLiOZfOjDqEOJ+dhB10xeMpzD8ETnH+91C34ldLxlj/C8LmtQ=="},"children":[]}]}]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"ford.example","signature":"BmAyeneYBRv9lawHqW8Wwmu4Srto97BI8J/9hIkc1a4k291rF+/2eubK/JYK4sJiDgval/O/n5EFv1W+/ZuitA=="},"children":[{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"1CXCCsF0k+VOxSu3JCHTxwH5fEG2TvWmhsUe6o6/K5+gTpm19m1RYbqJ8HDDS/Unk3oLwDAquAldPN3NukpG6w=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"zOYKjgkFyn4aSnBmqRgjSPDrMeldK8IaqfLK9DuaUGkFnNQR4JIaJX4gyHm4D95nXUyEiHgLqxUHlSJ+RtLCbw=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"EgPIZ6mcgHK09Vq+j13HQgY61/ytCi9J/JB/gz5da6H8KLMxfvuFwLKZg++NNX4eut7rQcZs0KHPZEBQI9bHQA=="},"children":[]}]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-t.example","signature":"1CXCCsF0k+VOxSu3JCHTxwH5fEG2TvWmhsUe6o6/K5+gTpm19m1RYbqJ8HDDS/Unk3oLwDAquAldPN3NukpG6w=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mustang.example","signature":"zOYKjgkFyn4aSnBmqRgjSPDrMeldK8IaqfLK9DuaUGkFnNQR4JIaJX4gyHm4D95nXUyEiHgLqxUHlSJ+RtLCbw=="},"children":[]},{"version":"0.1","receiver":"ford.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-18.example","signature":"EgPIZ6mcgHK09Vq+j13HQgY61/ytCi9J/JB/gz5da6H8KLMxfvuFwLKZg++NNX4eut7rQcZs0KHPZEBQI9bHQA=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"duesenberg.example","signature":"vPSxa45S7eZT1tu5aa8okkQWYfsBaCxF4a/1H5egqcyuU2QcBwUgf9aN2MCB0TVjWAAqeNA8Ar3S7r9GLGx07g=="},"children":[{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"cZYIC1jvKEjdz6PMmG8UiXStc+LWGeSNu39JynCgjo+TXwIqSYqO5VWIZ+QBCuxmHWDCyYZyJ+wykOClSgrG5g=="},"children":[]}]},{"version":"0.1","receiver":"duesenberg.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-sj.example","signature":"cZYIC1jvKEjdz6PMmG8UiXStc+LWGeSNu39JynCgjo+TXwIqSYqO5VWIZ+QBCuxmHWDCyYZyJ+wykOClSgrG5g=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"jeep.example","signature":"qb2m0vlqyqru4atNf6Solr5FS6JG8fLq1jXpDWVto9iFgOT2hrOtMmavDx79yb3mSOzR/UBuEsFGLqjnjp6NRA=="},"children":[{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"zIsQQfKojm7/Sl078qwjhNSKrmQej/OYI0y0Rxa2I7/WtzV/z+qH0v0us7Kp9N7TNp43yYS/zywtsJCrZVWCEA=="},"children":[]}]},{"version":"0.1","receiver":"jeep.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"mb.example","signature":"zIsQQfKojm7/Sl078qwjhNSKrmQej/OYI0y0Rxa2I7/WtzV/z+qH0v0us7Kp9N7TNp43yYS/zywtsJCrZVWCEA=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"oldsmobile.example","signature":"1DRbZF7WJ4bq+LQPego6zNZIT8v+HHLbhf9e9xuWHa/f6+MmzDh14KCQfZT5qzUB+GYHU31mlXgPnKFdOMAcDg=="},"children":[{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"Bz+g9eIFWO/aZlidhj3ErjSXDa0kCTrp3LZKIxqBRyxEwU+h/sNajcoY7MdbXR8yQ8Q7HowO3trvE6evPguRMQ=="},"children":[]}]},{"version":"0.1","receiver":"oldsmobile.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"rocket-88.example","signature":"Bz+g9eIFWO/aZlidhj3ErjSXDa0kCTrp3LZKIxqBRyxEwU+h/sNajcoY7MdbXR8yQ8Q7HowO3trvE6evPguRMQ=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"chevrolet.example","signature":"neOGC7iWbmNMlPJ6/lsMBcJQAe+xhXhJOoyHDlsUaxGq3SEyaVXscua4sbDYPTto7y0gorQidbgOmc8edfyGKA=="},"children":[{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"w7nKIGDXtH9PVuZoYXwHrVvIrU9nQReQ0fRzR0HwwOil0mnFKNy2mPUpOcmhUngnp8EjaNVZrfBZbO+9OUiPAw=="},"children":[]}]},{"version":"0.1","receiver":"chevrolet.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"corvette.example","signature":"w7nKIGDXtH9PVuZoYXwHrVvIrU9nQReQ0fRzR0HwwOil0mnFKNy2mPUpOcmhUngnp8EjaNVZrfBZbO+9OUiPAw=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"Chrysler.example","signature":"UAG/3msNxY+YbA/iTkCGRWYMs0R8W/F09UA2x/BVNPwkQS/uk2M/WqvsEgN1cs3YCMSXXVEimbxJBtgYxJuGDQ=="},"children":[{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"error_cannot_process","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"JPzoSU7TKsw2DqmVeDpk2GsLfNltM6QUuUAwdnf4bk+adS/nPRbgdoKuMs7I8IBwib1ukT+HCMaQzEUwRaYZRw=="},"children":[]}]},{"version":"0.1","receiver":"Chrysler.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"error_cannot_process","details":"mocked","source":{"timestamp":1656959907,"domain":"minivan.example","signature":"JPzoSU7TKsw2DqmVeDpk2GsLfNltM6QUuUAwdnf4bk+adS/nPRbgdoKuMs7I8IBwib1ukT+HCMaQzEUwRaYZRw=="},"children":[]},{"version":"0.1","receiver":"united-states.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"tesla.example","signature":"e54gD+PVr41yClGC/WfEpS8GrbXyYfZwOXSStk01z7qEfNOESSKbPYnGQQ7w3gSQyBR9JkDr1/lXdylabYLpHA=="},"children":[{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"yflGvX6s0xJHpRrxzwPWgLPCIpq385Z4wwB2ord8wf71tFnsL5Ns8VNL6r7EC27AuQqK6Jr8a0mjL+exUT07wA=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"lNaNONtxbXfHAclHBNqNkqjqAf1pAL8NnQFr32HMFoEPtiyhgIgHqi1/WaoX2h5daiuN+veO6VA5P6BBsx+yrQ=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"SshRevZFPUg7V5KsRDBhfqkARvLqwRz+RIky3ot6eXN2o7/xLv+OjUSQv0C3RaEkPUTLO8pSvKwtsefpJYe7Jw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"jgwqBPUaIjS1nrAQbmy3Xdq4WWgwv37Cy0PTPzLiOZfOjDqEOJ+dhB10xeMpzD8ETnH+91C34ldLxlj/C8LmtQ=="},"children":[]}]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-s.example","signature":"yflGvX6s0xJHpRrxzwPWgLPCIpq385Z4wwB2ord8wf71tFnsL5Ns8VNL6r7EC27AuQqK6Jr8a0mjL+exUT07wA=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-x.example","signature":"lNaNONtxbXfHAclHBNqNkqjqAf1pAL8NnQFr32HMFoEPtiyhgIgHqi1/WaoX2h5daiuN+veO6VA5P6BBsx+yrQ=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-3.example","signature":"SshRevZFPUg7V5KsRDBhfqkARvLqwRz+RIky3ot6eXN2o7/xLv+OjUSQv0C3RaEkPUTLO8pSvKwtsefpJYe7Jw=="},"children":[]},{"version":"0.1","receiver":"tesla.example","contents":[{"transaction_id":"da4020cb-30a9-4ad8-acbd-db8bb74d59b5","content_id":null}],"status":"success","details":"mocked","source":{"timestamp":1656959907,"domain":"model-y.example","signature":"jgwqBPUaIjS1nrAQbmy3Xdq4WWgwv37Cy0PTPzLiOZfOjDqEOJ+dhB10xeMpzD8ETnH+91C34ldLxlj/C8LmtQ=="},"children":[]}]},"resolver":[["automobiles.example",{"version":"0.1","name":"automobiles","type":"operator","dpo_email":"dpo@automobiles.example","privacy_policy_url":"https://automobiles.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOL1QleeC1jMvE5ZWRg1MaICFQpHf\naoqDmEmLbp8LOM48zkoIRXeBqhylMUHNDZmsnLNwHyHU0zZKUssTM1Rymw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-t.example",{"version":"0.1","name":"Model T","type":"vendor","dpo_email":"dpo@model-t.example","privacy_policy_url":"https://model-t.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnd2n/Uxuou5IVpVVbm4E92PpeRqN\n9lnEsFavBZGErvSzl9YaYuuFZJ+s1lqBodhhSQT6r6lXw+5BMM+AgD3xxg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["ford.example",{"version":"0.1","name":"Ford","type":"vendor","dpo_email":"dpo@ford.example","privacy_policy_url":"https://ford.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESelOEmGd/09uvUL2PmSHoFFGfXLW\nJCOcz2aXJECpKmmyU35gSDF9wgBXQDhf1vgJgzinfw+Gd/urDxaa4MDy4w==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["mustang.example",{"version":"0.1","name":"Mustang","type":"vendor","dpo_email":"dpo@mustang.example","privacy_policy_url":"https://mustang.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhGeUm8wdcAe/+Fz9zyin+W6q/x+c\n90y1tuKd3D3uff3CsPgiBD0iKoNWCXGoRt5Yd0a4uotq1TEVq3baErz+BA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-18.example",{"version":"0.1","name":"Model 18","type":"vendor","dpo_email":"dpo@model-18.example","privacy_policy_url":"https://model-18.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDJDRy1cMJkXdDVT2UawWaag1Qp1V\nUm4RENmqM0lE3g3g0iPpgM15nF9zstIRYRalT0LVbogpnaa2uPhgxY9PoA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["united-states.example",{"version":"0.1","name":"United States","type":"vendor","dpo_email":"dpo@united-states.example","privacy_policy_url":"https://united-states.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEomRv5+qPayaFoHPrCH3GcRqIZlOj\noUaY/OxLMUQ8+kXwihYDUwfihcXzfRBPgWkThFAC7zYi8Hqqw83QqYu77A==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-sj.example",{"version":"0.1","name":"Model SJ","type":"vendor","dpo_email":"dpo@model-sj.example","privacy_policy_url":"https://model-sj.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5a8i71NuVOr++9G22MLLv1B/wEsM\niQZZAA5bvqomPQrn2z95DUjsWuPcjMii9zzrxc0cCjjkV5zNm+Ru9KcMUg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["duesenberg.example",{"version":"0.1","name":"Duesenberg","type":"vendor","dpo_email":"dpo@duesenberg.example","privacy_policy_url":"https://duesenberg.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy7awiLeawqTNlHD/6RSJK9WiYzhA\no2EBfF3e8Hyeyj+Sg/ZdB3jzKkhzjHfsbSuSQVs+9lAtfYqfTO8lvWhygw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["mb.example",{"version":"0.1","name":"MD","type":"vendor","dpo_email":"dpo@mb.example","privacy_policy_url":"https://mb.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEChdmKs/1N7aLIZmIUe18iH74u7El\n7C3n2B5B+gXrx7ekIGPZK6j7G198VpSu6GBnLTdu/t0ilc8BvLkU8yrr3Q==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["jeep.example",{"version":"0.1","name":"Jeep","type":"vendor","dpo_email":"dpo@jeep.example","privacy_policy_url":"https://jeep.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfmpSTxy+DmpEPaAvdzCOL7+dOeNy\nLli+2cC8rVRVtdbcXDKgf+KtJkEBY8yFKI7F3z7ZsC+zAcNjNQQ54fplvg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["rocket-88.example",{"version":"0.1","name":"\"Rocket 88\"","type":"vendor","dpo_email":"dpo@rocket-88.example","privacy_policy_url":"https://rocket-88.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtSAC7YAAT9bXIXwoyNfODg3ITDFx\nD3rxidK5xQAuSQF06q6crBoL706wEGxvtGuWKFoFU46vPd9tNemn7jTntw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["oldsmobile.example",{"version":"0.1","name":"Oldsmobile","type":"vendor","dpo_email":"dpo@oldsmobile.example","privacy_policy_url":"https://oldsmobile.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEs7X+/qTIN/vRp8KGPAjwvyXuU7io\n8/+Ql6cFWdJrY5ICnHv7Mqhqb+R7xGdpA/sEuFGVE6DkbXmIjegtGMkXMw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["corvette.example",{"version":"0.1","name":"Corvette","type":"vendor","dpo_email":"dpo@corvette.example","privacy_policy_url":"https://corvette.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9DX++2QRW3M2Gr5a4w3ekFfIwiaR\ne30g1AprJDERPFQwfPUAMJbpEUZUWMxZsvoMK7u7pYSHBqyC4XJZU/T2fg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["chevrolet.example",{"version":"0.1","name":"Chevrolet","type":"vendor","dpo_email":"dpo@chevrolet.example","privacy_policy_url":"https://chevrolet.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbCAjrm5RI+tAINivtzP2Og6aGNyM\nqxx79W8IjVtMvRNKMkX6+u042xF3C+fbe/9iws04VwgcFHWgAqy2HlF+RQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["minivan.example",{"version":"0.1","name":"Minivan","type":"vendor","dpo_email":"dpo@minivan.example","privacy_policy_url":"https://minivan.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkwtivkjB4f3vusg1eGSdVyuhwn16\nbxyieRk2cLHmD4423ctiY+9U/CU2jKwb+esz+0YbGV9xcMZC5RxfJe6X1Q==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["Chrysler.example",{"version":"0.1","name":"Chrysler","type":"vendor","dpo_email":"dpo@Chrysler.example","privacy_policy_url":"https://chrysler.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES2eRZcVsysfftuHLPoQeoAnDnxP7\ns6/ZPJfTzzPRFIjK/DiZzsF/uL/3JLNfl+gsUlSMYjqR0Sz+IxAh4DVJcw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-s.example",{"version":"0.1","name":"Model S","type":"vendor","dpo_email":"dpo@model-s.example","privacy_policy_url":"https://model-s.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1IUV+hKfrxALNzMmPt64+RBzCpqs\nFgTdwlvcbM/MNOqRnznsRpxcoNVXoyoxc8PTkxZksAAHh2y+jx0atoNgfA==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["tesla.example",{"version":"0.1","name":"Tesla","type":"vendor","dpo_email":"dpo@tesla.example","privacy_policy_url":"https://tesla.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBhBmtU+HztNiX3ypFoT2NVXs/T4S\nsKwTKsrL+Bv0RSOjrotNABmISsXwVCfsgyFXq6+y+TiEQwY3NngJUca+zw==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-x.example",{"version":"0.1","name":"Model X","type":"vendor","dpo_email":"dpo@model-x.example","privacy_policy_url":"https://model-x.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+u52x45Laj6TH86bK0kl0EBfI2Bf\nrEmudwfYewjCo2Dw8JjxM84qRN6b/MM2m+6ui7N6+QtHXcIo9eOUMbaMzQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-3.example",{"version":"0.1","name":"Model 3","type":"vendor","dpo_email":"dpo@model-3.example","privacy_policy_url":"https://model-3.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWlMl8n7l150vltgibpEBuITEEI/w\nqh4cMUx9IqQ8c1tuHNn7SUcnnJhnmSEUXgSh38rVxlAbq6uygv25FoixVg==\n-----END PUBLIC KEY-----\n","start":1656959907}]}],["model-y.example",{"version":"0.1","name":"Model Y","type":"vendor","dpo_email":"dpo@model-y.example","privacy_policy_url":"https://model-y.example/privacy.html","keys":[{"key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfZ+q+/Dh9cIiEgqNZaSVqwm/u9ml\nUbcNDmeiTwR8zwQH7Sfhqj7kASw3gNdx2JPF60kMeSSG3Tk7ENKhzTIOhQ==\n-----END PUBLIC KEY-----\n","start":1656959907}]}]]}
\ No newline at end of file
diff --git a/paf-mvp-audit/index.html b/paf-mvp-audit/index.html
new file mode 100644
index 00000000..ae2fadce
--- /dev/null
+++ b/paf-mvp-audit/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+ OK UI Audit Sample Implementation
+
+
+
+
+
+
Narrow Advert
+
+
+
+
+
+
+
Narrow Advert
+
+
+
+
Wide Advert
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/jest.config.js b/paf-mvp-audit/jest.config.js
new file mode 100644
index 00000000..792cb88b
--- /dev/null
+++ b/paf-mvp-audit/jest.config.js
@@ -0,0 +1,13 @@
+const rootConfig = require('../jest.config');
+
+const path = require('path');
+
+/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
+module.exports = {
+ ...rootConfig,
+ testEnvironment: 'jsdom',
+ globals: {
+ ...rootConfig.globals,
+ 'mock-audit-log-path': path.join(__dirname, './assets/mocks'),
+ },
+};
diff --git a/paf-mvp-audit/package.json b/paf-mvp-audit/package.json
index 517b8601..48a3a89d 100644
--- a/paf-mvp-audit/package.json
+++ b/paf-mvp-audit/package.json
@@ -7,11 +7,13 @@
},
"description": "The output from this project is a single JavaScript resource file that contains the entire Audit log viewer.",
"version": "0.1.0",
- "main": "./src/main.ts",
"directories": {
"test": "tests"
},
"keywords": [],
"author": "",
- "license": "Apache-2.0"
+ "license": "Apache-2.0",
+ "devDependencies": {
+ "ec-key": "^0.0.4"
+ }
}
diff --git a/paf-mvp-audit/rollup.config.js b/paf-mvp-audit/rollup.config.js
index 80688978..9ff254db 100644
--- a/paf-mvp-audit/rollup.config.js
+++ b/paf-mvp-audit/rollup.config.js
@@ -6,54 +6,273 @@ import { nodeResolve } from '@rollup/plugin-node-resolve';
// Needed to minimize the resulting bundle.
import { terser } from 'rollup-plugin-terser';
-// Used to get the locales for embedding into the bundle.
-import yaml from '@rollup/plugin-yaml';
-
// HTML templates used to add language text.
import postHTML from 'rollup-plugin-posthtml-template';
+// Reduces the size of the HTML.
+import minifyHTML from 'rollup-plugin-minify-html-literals';
+import { defaultShouldMinify } from 'minify-html-literals';
+
// Embed the CSS into the bundle.
import { string } from 'rollup-plugin-string';
-export default {
- input: './src/main.ts',
- plugins: [
- string({ include: ['**/*.css', '**/*.svg'] }),
- postHTML({ template: true }),
- yaml(),
- nodeResolve(),
- commonjs(),
- typescript({
- tsconfig: '../tsconfig.json'
- })
- ],
- treeshake: true,
- output: [
- {
- file: './dist/ok-audit.js',
- format: 'iife',
- sourcemap: true
- },
- {
- file: './dist/ok-audit.min.js',
- format: 'iife',
- sourcemap: true,
- plugins: [
- terser()
- ]
- },
- {
- file: '../paf-mvp-demo-express/public/assets/ok-audit.js',
- format: 'iife',
- sourcemap: true
- },
- {
- file: '../paf-mvp-demo-express/public/assets/ok-audit.min.js',
- format: 'iife',
- sourcemap: true,
- plugins: [
- terser()
- ]
- }
- ]
+// Used to set the locale for each of the bundles.
+import replace from '@rollup/plugin-replace';
+
+// Needed for crypto and http functions of the audit viewer.
+import json from '@rollup/plugin-json';
+import globals from 'rollup-plugin-node-globals';
+import nodePolyfills from 'rollup-plugin-node-polyfills';
+
+// Used to get the locales available for each of the bundles.
+import * as fs from 'fs';
+import * as path from 'path';
+import * as yaml from 'js-yaml';
+
+// File name prefix for the bundle files.
+const namePrefix = 'ok-ui-audit';
+
+// The name of the root namespace.
+const globalName = 'OKA';
+
+const DEV = process.env.ROLLUP_WATCH;
+
+// Options to pass to terser.
+const terserOptions = {
+ toplevel: true,
+ format: {
+ comments: false
+ },
+ compress: true,
+ mangle: true
};
+
+// Get mock audit log JSON.
+function getMockAuditLogs() {
+ const mocks = new Map();
+ const directory = './assets/mocks';
+ fs.readdirSync(directory).forEach(mockFile => {
+ if (path.extname(mockFile) === '.json') {
+ const mockName = mockFile.split('.')[0];
+ mocks[mockName] = JSON.parse(fs.readFileSync(path.join(directory, mockFile), 'utf8'));
+ }
+ });
+ return mocks;
+}
+
+// Adds the SVG images to the local. Could be expanded to support other images in the future.
+function addImages(locale) {
+ const directory = './src/images';
+ fs.readdirSync(directory).forEach(imageFile => {
+ if (path.extname(imageFile) === '.svg') {
+ const imageName = imageFile.split('.')[0];
+ locale[imageName] = fs.readFileSync(path.join(directory, imageFile), 'utf8')
+ }
+ });
+ return locale;
+}
+
+// Converts the list of strings to paragraph tags.
+function toHtml(list) { return `
${list.join('
')}
`; }
+
+// Converts the list of strings to a text block.
+function toText(list) { return list.join('\r\n'); }
+
+// Converts the source locale into one with HTML properties.
+function addHTML(locale) {
+ locale.emailBodyText = toText(locale.emailBody);
+ locale.participantsIntroHTML = toHtml(locale.participantsIntro);
+ locale.participantsFooterHTML = toHtml(locale.participantsFooter);
+ locale.advertGoodBodyHTML = toHtml(locale.advertGoodBody);
+ locale.advertSuspiciousBodyHTML = toHtml(locale.advertSuspiciousBody);
+ locale.advertViolationBodyHTML = toHtml(locale.advertViolationBody);
+ locale.advertInProgressBodyHTML = toHtml(locale.advertInProgressBody);
+ locale.downloadBodyHTML = toHtml(locale.downloadBody);
+ delete locale['emailBody'];
+ delete locale['participantsIntro'];
+ delete locale['participantsFooter'];
+ delete locale['advertGoodBody'];
+ delete locale['advertSuspiciousBody'];
+ delete locale['advertViolationBody'];
+ delete locale['advertInProgressBody'];
+ delete locale['downloadBody'];
+ return locale;
+}
+
+// Converts the source locale into one with HTML and image properties.
+function buildLocale(locale) {
+ locale = addHTML(locale);
+ locale = addImages(locale);
+ return locale;
+}
+
+// Gets all the locale files as an array.
+function getLocaleFiles() {
+ const directory = './src/locales';
+ const localeFiles = [];
+ fs.readdirSync(directory).forEach(localeFile => {
+ if (path.extname(localeFile) === '.yaml') {
+ localeFiles.push(localeFile);
+ }
+ });
+ return localeFiles;
+}
+
+// Get the locale and the text as JSON.
+async function getLocales() {
+ const directory = './src/locales';
+ const locales = new Map();
+ getLocaleFiles().forEach(localeFile => {
+ const language = localeFile.split('.')[0];
+ const locale = buildLocale(yaml.load(fs.readFileSync(path.join(directory, localeFile), 'utf8')));
+ const text = JSON.stringify(locale);
+ locales.set(language, text);
+ });
+ if (locales.size == 0) {
+ throw 'No locale files found. Check the "/src/locales" folder.';
+ }
+ return locales;
+}
+
+// Gets the available locale codes for use with the loader.
+function getLocaleCodes() {
+ const locales = [];
+ getLocaleFiles().forEach(localeFile => {
+ const locale = localeFile.split('.')[0];
+ locales.push('\'' + locale.toLowerCase() + '\'');
+ });
+ return locales;
+}
+
+// Builds the loader working out from the locales directory the various options that will be available.
+function buildLoader() {
+ const loader = '../paf-mvp-core-js/src/ui/loader.ts';
+ return {
+ input: loader,
+ plugins: [
+ replace({
+ include: loader,
+ preventAssignment: true,
+ __Locales__: '[' + getLocaleCodes().join(',') + ']'
+ }),
+ typescript({
+ tsconfig: '../tsconfig.json'
+ }),
+ commonjs(),
+ nodeResolve({
+ browser: true,
+ preferBuiltins: false
+ })
+ ],
+ treeshake: true,
+ output: [
+ {
+ file: `./dist/${namePrefix}.js`,
+ sourcemap: false,
+ format: 'iife'
+ },
+ {
+ file: `./dist/${namePrefix}.min.js`,
+ format: 'iife',
+ sourcemap: false,
+ plugins: [terser(terserOptions)]
+ },
+ {
+ file: `../paf-mvp-demo-express/public/assets/${namePrefix}.js`,
+ sourcemap: false,
+ format: 'iife'
+ },
+ {
+ file: `../paf-mvp-demo-express/public/assets/${namePrefix}.min.js`,
+ format: 'iife',
+ sourcemap: false,
+ plugins: [terser(terserOptions)]
+ },
+ ]
+ }
+}
+
+// Returns configuration for a specific locale.
+// localeCode the code of the locale being built for. i.e. en-GB
+// localeContent the JSON object with the content
+// mockAuditLogs the JSON object with mock audit logs included
+function buildLocaleConfig(localeCode, localeContent, mockAuditLogs) {
+ const baseOutput = {
+ sourcemap: false,
+ format: 'umd',
+ name: globalName
+ };
+ return {
+ input: './src/controller.ts',
+ plugins: [
+ replace({
+ include: './src/controller.ts',
+ preventAssignment: true,
+ __Locale__: localeContent,
+ __MockAuditLogs__: mockAuditLogs
+ }),
+ replace({
+ preventAssignment: true,
+ 'process.env.NODE_ENV': JSON.stringify(DEV ? 'development' : 'production'),
+ // Needed to ensure the client side version with Promise based functions is used.
+ 'ecdsa-secp256r1': 'ecdsa-secp256r1/browser'
+ }),
+ postHTML({ template: true }),
+ minifyHTML({
+ // Include string literals that contain the div or section element as well as the default check.
+ options: {
+ shouldMinify: (t) => defaultShouldMinify(t) || t.parts.some(p =>
+ p.text.includes('
{
+ configs.push(buildLocaleConfig(locale, value, mockAuditLogs));
+ });
+ return configs;
+}
+
+// Finally export the configurations for each of the locales.
+export default getLocales().then(getConfigs);
\ No newline at end of file
diff --git a/paf-mvp-audit/src/bindings.ts b/paf-mvp-audit/src/bindings.ts
new file mode 100644
index 00000000..fbb76f3b
--- /dev/null
+++ b/paf-mvp-audit/src/bindings.ts
@@ -0,0 +1,540 @@
+import { BindingViewOnly, IView } from '@core/ui/binding';
+import { ILocale } from '@core/ui/ILocale';
+import {
+ VerifiedTransmissionResult,
+ Model,
+ OverallStatus,
+ VerifiedStatus,
+ VerifiedIdsAndPreferences,
+ VerifiedValue,
+ VerifiedIdentifier,
+ DataTabs as DataTabs,
+ ParticipantsTabs,
+} from './model';
+import { View } from './view';
+import statusInProgress from './html/components/statusinprogress.html';
+import statusGood from './html/components/statusgood.html';
+import statusViolation from './html/components/statusviolation.html';
+import statusSuspicious from './html/components/statussuspicious.html';
+import participantComponent from './html/components/participant.html';
+import participantTrusted from './html/components/participantTrusted.html';
+import participantSuspicious from './html/components/participantSuspicious.html';
+import participantViolating from './html/components/participantViolating.html';
+import dataComponent from './html/components/data.html';
+import statusSlugGood from './html/components/statussluggood.html';
+import statusSlugSuspicious from './html/components/statusslugsuspicious.html';
+import statusSlugViolation from './html/components/statusslugviolation.html';
+import { getDate } from '@core/timestamp';
+import { IModel } from '@core/ui/fields';
+import { Identifier, IdsAndPreferences, PreferencesData, TransmissionResult } from '@core/model';
+
+export abstract class BindingVerifiedIdsAndPreferences extends BindingViewOnly<
+ VerifiedIdsAndPreferences,
+ Model,
+ HTMLDivElement
+> {
+ /**
+ * Constructs a new binding to show the preference date of the model.
+ * @param view
+ * @param id
+ * @param locale
+ */
+ constructor(view: View, id: string, protected readonly locale: ILocale) {
+ super(view, id);
+ }
+}
+
+/**
+ * Displays the preference date.
+ */
+export class BindingPreferenceDate extends BindingVerifiedIdsAndPreferences {
+ /**
+ * Sets the elements text to the preferences date text.
+ * @returns
+ */
+ refresh(): HTMLDivElement {
+ const element = super.getElement();
+ if (element !== null) {
+ element.innerText = (this.locale.advertDate).replace(
+ '[Date]',
+ getDate(this.field.value.value.preferences.source.timestamp).toLocaleString()
+ );
+ }
+ return element;
+ }
+}
+
+/**
+ * Shows the overall status for the audit log at the current point in time. Will display an inprogress indicator if
+ * verification is happening, or the result of verification when complete.
+ */
+export class BindingOverallStatus extends BindingViewOnly {
+ /**
+ * Constructs a new binding to show the status of the model.
+ * @param view
+ * @param id
+ * @param locale
+ */
+ constructor(view: View, id: string, private readonly locale: ILocale, private readonly model: Model) {
+ super(view, id);
+ }
+
+ refresh(): HTMLDivElement {
+ const element = super.getElement();
+ if (element !== null) {
+ element.innerHTML = this.getStatusTemplate(this.field.value)(this.locale)
+ .replace('[SuspiciousParticipants]', this.model.count(VerifiedStatus.IdentityNotFound).toLocaleString())
+ .replace('[ViolatingParticipants]', this.model.count(VerifiedStatus.NotValid).toLocaleString());
+ }
+ return element;
+ }
+
+ private getStatusTemplate(status: OverallStatus): Component {
+ switch (status) {
+ case OverallStatus.Good:
+ return statusGood;
+ case OverallStatus.Suspicious:
+ return statusSuspicious;
+ case OverallStatus.Violation:
+ return statusViolation;
+ }
+ return statusInProgress;
+ }
+}
+
+/**
+ * Base class for bindings that display the results of verified fields via HTML insertion.
+ */
+export abstract class BindingVerifiedField> extends BindingViewOnly<
+ F,
+ Model,
+ HTMLElement
+> {
+ /**
+ * Count of bindings constructed.
+ */
+ private static count = 0;
+
+ /**
+ * Unique index of the participant in the user interface.
+ */
+ protected readonly uniqueId: string;
+
+ /**
+ * Constructs a new instance of the verified field binding.
+ * @param view
+ * @param id
+ * @param locale
+ */
+ constructor(view: View, id: string, protected readonly locale: ILocale) {
+ super(view, id);
+ this.uniqueId = `${id}${BindingVerifiedField.count++}`;
+ this.locale = locale;
+ }
+
+ /**
+ * Returns the current element for the item in the collection or adds it if it does not already exist.
+ * @param container
+ */
+ protected abstract getCurrentElement(container: HTMLElement): HTMLElement;
+
+ /**
+ * Refreshes the HTML associated with the element.
+ * @param element element associated with the field bound to
+ */
+ protected abstract refreshVerified(element: HTMLElement): void;
+
+ /**
+ * Adds the transmission provider's text and status to the bound element.
+ */
+ public refresh(): HTMLElement {
+ const container = super.getElement();
+ if (container !== null) {
+ const element = this.getCurrentElement(container);
+ if (element !== null) {
+ if (this.field.value.verifiedStatus === VerifiedStatus.Processing) {
+ // TODO: Add a spinner.
+ } else {
+ this.refreshVerified(element);
+ }
+ }
+ return element;
+ }
+ return null;
+ }
+
+ /**
+ * Adds a field specific event handler to elements with the id provided.
+ * @remarks the method to get the value is passed because it might be time consuming so only worth doing when the user
+ * selects the option.
+ * @param id id of the element to bind to
+ * @param url method to get the url when the click event fires
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ protected addEventListener(id: string, url: (instance: any) => URL) {
+ const element = this.view.root.getElementById(id);
+ if (element) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (element).binding = this;
+ element.addEventListener('click', (e) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ window.open(url((e.currentTarget).binding), '_blank');
+ e.preventDefault();
+ });
+ }
+ }
+
+ /**
+ *
+ * @returns URL for the participants privacy policy
+ */
+ public privacyUrl(instance: BindingVerifiedField): URL {
+ return new URL(instance.field.value.identity.privacy_policy_url);
+ }
+}
+
+/**
+ * Custom UI binding to display the preferences and ids from the audit log.
+ */
+export abstract class BindingIdsAndPreferences> extends BindingVerifiedField {
+ /**
+ * The string value to display.
+ */
+ protected abstract getValueAsString(): string;
+
+ /**
+ * The name of the field.
+ */
+ protected abstract getField(): string;
+
+ /**
+ * The date that the field was setup.
+ */
+ protected abstract getDate(): string;
+
+ /**
+ * The name of the organization that created or captured the value.
+ */
+ protected abstract getName(): string;
+
+ /**
+ * True if the field should be visible based on the current state of the model.
+ */
+ protected abstract visible(): boolean;
+
+ protected getCurrentElement(container: HTMLElement): HTMLElement {
+ let current = this.view.root.getElementById(this.uniqueId);
+ if (!current) {
+ current = document.createElement('ul');
+ current.classList.add('ok-ui-data');
+ current.id = this.uniqueId;
+ container.appendChild(current);
+ }
+ if (this.visible()) {
+ current.classList.remove('ok-ui-hidden');
+ } else {
+ current.classList.add('ok-ui-hidden');
+ }
+ return current;
+ }
+
+ /**
+ * Refreshes the HTML associated with the element and adds event listeners for the participant specific actions.
+ * @param element element associated with the field bound to
+ */
+ protected refreshVerified(element: HTMLElement) {
+ const html = dataComponent({
+ field: this.getField(),
+ value: this.getValueAsString(),
+ statusSlug: this.getStatusSlugHTML(),
+ dataSetupDateField: this.locale.dataSetupDateField,
+ dataSetupDateText: (this.locale.dataSetupDateText)
+ .replace('[Date]', this.getDate())
+ .replace('[Name]', this.getName()),
+ dataTermsUsedField: this.locale.dataTermsUsedField,
+ terms: this.locale.terms,
+ uniqueId: this.uniqueId,
+ });
+ if (element.innerHTML !== html) {
+ element.innerHTML = html;
+ this.addEventListener(`terms-${this.uniqueId}`, this.privacyUrl);
+ }
+ }
+
+ private getStatusSlugHTML(): string {
+ switch (this.field.value.verifiedStatus) {
+ case VerifiedStatus.Valid:
+ return statusSlugGood(this.locale);
+ case VerifiedStatus.NotValid:
+ return statusSlugViolation(this.locale);
+ case VerifiedStatus.IdentityNotFound:
+ return statusSlugSuspicious(this.locale);
+ }
+ return '';
+ }
+}
+
+/**
+ * Binding specifically for the preference part of the ids and preferences.
+ */
+export class BindingPreferences extends BindingIdsAndPreferences {
+ /**
+ * Constructs a new instance of the verified field binding.
+ * @param view
+ * @param id
+ * @param locale
+ * @param model
+ * @param map of preference data value to text strings
+ */
+ constructor(
+ view: View,
+ id: string,
+ locale: ILocale,
+ private readonly model: Model,
+ private readonly map: Map
+ ) {
+ super(view, id, locale);
+ }
+
+ protected getValueAsString(): string {
+ const keyJSON = JSON.stringify(this.field.value.value.preferences.data);
+ for (const item of this.map) {
+ if (JSON.stringify(item[0]) === keyJSON) {
+ return item[1];
+ }
+ }
+ return null;
+ }
+
+ protected getField(): string {
+ return this.locale.dataPreference;
+ }
+
+ protected getDate(): string {
+ return getDate(this.field.value.value.preferences.source.timestamp).toLocaleString();
+ }
+
+ protected getName(): string {
+ return this.field.value.identity
+ ? this.field.value.identity.name
+ : this.field.value.value.preferences.source.domain;
+ }
+
+ protected visible(): boolean {
+ return this.model.dataTab.value === DataTabs.Preferences;
+ }
+}
+
+/**
+ * Binding specifically for the preference part of the ids and preferences.
+ */
+export class BindingIdentifier extends BindingIdsAndPreferences {
+ /**
+ * Constructs a new instance of the verified field binding.
+ * @param view
+ * @param id
+ * @param locale
+ * @param model
+ */
+ constructor(view: View, id: string, locale: ILocale, private readonly model: Model) {
+ super(view, id, locale);
+ }
+
+ protected getValueAsString(): string {
+ return this.field.value.value.value;
+ }
+
+ protected getField(): string {
+ return this.locale.dataRandomIdField;
+ }
+
+ protected getDate(): string {
+ return getDate(this.field.value.value.source.timestamp).toLocaleString();
+ }
+
+ protected getName(): string {
+ return this.field.value.identity ? this.field.value.identity.name : this.field.value.value.source.domain;
+ }
+
+ protected visible(): boolean {
+ return this.model.dataTab.value === DataTabs.Identifiers;
+ }
+}
+
+/**
+ * Custom UI binding to display the providers from the audit log.
+ */
+export class BindingParticipant extends BindingVerifiedField {
+ /**
+ * Constructs a new instance of the verified field binding.
+ * @param view
+ * @param id
+ * @param locale
+ * @param model
+ */
+ constructor(view: View, id: string, locale: ILocale, private readonly model: Model) {
+ super(view, id, locale);
+ }
+
+ protected getCurrentElement(container: HTMLElement): HTMLElement {
+ let current = this.view.root.getElementById(this.uniqueId);
+ if (!current) {
+ current = document.createElement('article');
+ current.classList.add('ok-ui-participant', 'ok-ui-participant--winning');
+ current.id = this.uniqueId;
+ container.appendChild(current);
+ }
+ switch (this.model.participantsTab.value) {
+ case ParticipantsTabs.All:
+ current.classList.remove('ok-ui-hidden');
+ break;
+ case ParticipantsTabs.Suspicious:
+ if (this.field.value.verifiedStatus === VerifiedStatus.Valid) {
+ current.classList.add('ok-ui-hidden');
+ } else {
+ current.classList.remove('ok-ui-hidden');
+ }
+ break;
+ case ParticipantsTabs.This:
+ current.classList.remove('ok-ui-hidden');
+ break;
+ }
+ return current;
+ }
+
+ /**
+ * Refreshes the HTML associated with the element and adds event listeners for the participant specific actions if the
+ * identity is available. If not available the terms and contact buttons are hidden.
+ * @param element element associated with the field bound to
+ */
+ protected refreshVerified(element: HTMLElement) {
+ const meta: string[] = [];
+ if (this.field.value.identity?.type) meta.push(this.field.value.identity.type);
+ if (this.field.value.value?.status) meta.push(this.field.value.value.status);
+ if (this.field.value.value?.details) meta.push(this.field.value.value.details);
+
+ const html = participantComponent({
+ statusHtml: this.getStatusTemplate(this.field.value.verifiedStatus)(this.locale),
+ name: this.field.value.identity ? this.field.value.identity.name : this.field.value.value.source.domain,
+ terms: this.locale.terms,
+ contact: this.locale.contact,
+ uniqueId: this.uniqueId,
+ meta: meta.join(', '),
+ });
+ element.innerHTML = html;
+
+ const termsId = `terms-${this.uniqueId}`;
+ const contactId = `contact-${this.uniqueId}`;
+ if (this.field.value.identity) {
+ this.addEventListener(termsId, this.privacyUrl);
+ this.addEventListener(contactId, BindingParticipant.contactUrl);
+ } else {
+ this.view.root.getElementById(termsId).classList.add('ok-ui-hidden');
+ this.view.root.getElementById(contactId).classList.add('ok-ui-hidden');
+ }
+ }
+
+ /**
+ * URL to use to open the contact email.
+ * @returns the mail to URL
+ */
+ public static contactUrl(instance: BindingParticipant): URL {
+ return new URL(instance.field.value.buildEmailUrl(instance.locale));
+ }
+
+ /**
+ * Gets the status pill template for the participant.
+ * @param status
+ * @returns
+ */
+ private getStatusTemplate(status: VerifiedStatus): Component {
+ switch (status) {
+ case VerifiedStatus.Valid:
+ return participantTrusted;
+ case VerifiedStatus.IdentityNotFound:
+ return participantSuspicious;
+ case VerifiedStatus.NotValid:
+ return participantViolating;
+ }
+ return statusInProgress;
+ }
+}
+
+/**
+ * Binding class for ids and preferences.
+ */
+export class BindingElementIdsAndPreferences extends BindingViewOnly {
+ /**
+ * Array of key value pairs.
+ */
+ protected readonly pairs: [PreferencesData, string][];
+
+ /**
+ * Relates any HTML element with the innerHTML property to a map of keys and locale string values.
+ * @param view that will contain the element with the id
+ * @param id of the id of the element to bind to
+ * @param map of field values to locale strings
+ */
+ constructor(view: IView, id: string, map: Map) {
+ super(view, id);
+ this.pairs = Array.from(map);
+ }
+
+ public refresh(): HTMLElement {
+ const element = super.getElement();
+ if (element !== null) {
+ const text = this.getString(this.field.value.value.preferences.data);
+ if (text !== null) {
+ element.innerHTML = text;
+ } else {
+ element.innerHTML = '';
+ }
+ }
+ return element;
+ }
+
+ protected getString(key: PreferencesData): string | null {
+ const keyJSON = JSON.stringify(key);
+ for (const item of this.pairs) {
+ if (JSON.stringify(item[0]) === keyJSON) {
+ return item[1];
+ }
+ }
+ return null;
+ }
+}
+
+/**
+ * Binding used for the buttons in the tab bar in the data and participants cards.
+ */
+export class BindingTabButton extends BindingViewOnly {
+ /**
+ * Binding for tab buttons in the data and participants cards.
+ * @param view
+ * @param id
+ * @param value associated with the tab
+ * @param visible optional function to determine if the tab should be displayed
+ */
+ constructor(view: IView, id: string, private readonly value: T, private readonly visible?: () => boolean) {
+ super(view, id);
+ }
+
+ public refresh(): HTMLButtonElement {
+ const active = ['ok-ui-button--outlined', 'ok-ui-button--primary'];
+ const waiting = ['ok-ui-button--text'];
+ const element = super.getElement();
+ if (element !== null) {
+ if (this.visible && this.visible() === false) {
+ element.classList.add('ok-ui-hidden');
+ }
+ if (this.value === this.field.value) {
+ waiting.forEach((c) => element.classList.remove(c));
+ active.forEach((c) => element.classList.add(c));
+ } else {
+ active.forEach((c) => element.classList.remove(c));
+ waiting.forEach((c) => element.classList.add(c));
+ }
+ }
+ return element;
+ }
+}
diff --git a/paf-mvp-audit/src/controller.ts b/paf-mvp-audit/src/controller.ts
index 2552641c..346e9c2e 100644
--- a/paf-mvp-audit/src/controller.ts
+++ b/paf-mvp-audit/src/controller.ts
@@ -1,59 +1,146 @@
-import { Locale } from './locale';
-import { AuditLog, TransmissionResult } from '@core/model/generated-model';
+import { ILocale } from '@core/ui/ILocale';
+import { AuditLog, GetIdentityResponse, PreferencesData } from '@core/model/generated-model';
import { Log } from '@core/log';
-import { Model } from './model';
+import { Model, ParticipantsTabs, DataTabs as DataTabs, OverallStatus } from './model';
import { View } from './view';
-import { BindingViewOnly } from '@core/ui/binding';
-import providerComponent from './html/components/provider.html';
-import iconTick from './images/IconTick.svg';
import { Window } from '@frontend/global';
+import { AuditHandler } from '@frontend/lib/paf-lib';
+import { IdentityResolver, IdentityResolverHttp, IdentityResolverMap } from './identity-resolver';
+import {
+ BindingElementIdsAndPreferences,
+ BindingParticipant,
+ BindingPreferenceDate,
+ BindingOverallStatus,
+ BindingTabButton,
+ BindingPreferences,
+ BindingIdentifier,
+} from './bindings';
+import { Marketing } from '@core/model/marketing';
-// TODO: Add back when full audit information is available.
-// import iconCross from './images/iconCross.svg';
+// TODO: Remove the mock audit log code.
+interface Mock {
+ auditLog: AuditLog;
+ resolver: [{ 0: string; 1: GetIdentityResponse }];
+}
/**
* Controller class used with the model and views. Uses paf-lib for data access services.
*/
-export class Controller {
- // The locale that the UI should adopt.
- private readonly locale: Locale;
+export class Controller implements AuditHandler {
+ /**
+ * The language text object populated by rollup.config.js at build time based on the YAML resource language files.
+ */
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore rollup replaces this with the JS object for the language.
+ private readonly locale = __Locale__;
+
+ // TODO: Remove the mock audit log code.
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore rollup replaces this with the JS object for the language.
+ private static readonly MockAuditLogs = <{ [key: string]: Mock }>__MockAuditLogs__;
+
+ // Logger.
+ public static readonly log = new Log('audit', '#18a9e1');
// The view associated with the controller.
- private readonly view: View;
+ private view: View = null;
+
+ // The model that wraps the audit log. Null until the modelPromise resolves.
+ private model: Model = null;
- // The model that wraps the audit log.
- private readonly model: Model;
+ // The model promise that needs to resolve before the this.model property returns a value.
+ private modelPromise: Promise = null;
// The HTML element the audit module instance is listening to.
- private readonly element: HTMLElement;
+ private element: HTMLElement = null;
- // The HTML element that will open the audit view if selected.
- private readonly button: HTMLElement;
+ // The identity resolve to use with the controller.
+ private identityResolver: IdentityResolver = null;
- // Logger.
- private readonly log: Log;
+ /**
+ * Constructs a new instance of the controller.
+ * @param brandName to use to replace the [BrandName] tokens
+ * @param logoUrls to use in the header of the UI
+ */
+ constructor(brandName?: string, logoUrls?: string[]) {
+ this.locale.brandName = brandName;
+ this.locale.logoUrls = logoUrls;
+ }
+
+ /**
+ * Binds the controller to the element with the advert and displays the icon.
+ * @advertElementOrId that contains the advert and audit log.
+ */
+ public async bind(advertElementOrId: HTMLElement | string) {
+ if (this.element) return; // Audit is already registered for this element.
+ this.setElement(advertElementOrId);
+ this.locale.advertHtml = this.element.outerHTML; // Must get the advert HTML *before* adding the audit viewer.
+ this.view = new View(this.element, this.locale, Controller.log);
+ this.view.initView(); // Initializes the view sufficient to display the icon.
+ this.bindActions(); // Needed to bind the open icon before the model is created and verified.
+ Controller.log.Info('Audit registered', this.element.id);
+ }
/**
- * Constructs a new instance of Controller and displays the audit popup.
- * @param locale the language file to use with the UI
- * @param advert to bind the audit viewer to
- * @param log
+ * Starts the process of verifying the audit log data by creating the model.
*/
- constructor(locale: Locale, advert: HTMLElement, log: Log) {
- this.locale = locale;
- this.element = advert;
- this.log = log;
+ private initModel() {
+ if (this.modelPromise === null) {
+ let auditLog: AuditLog;
+ const value = this.element.getAttribute('auditLog');
+
+ // TODO: Replace this with a fetch for the real audit log once available.
+ try {
+ auditLog = JSON.parse(value);
+ } catch {
+ // Do nothing. Try getting a mock audit log by name.
+ }
+ if (auditLog) {
+ // A real audit log is present that should be verified with HTTP identities.
+ this.identityResolver = new IdentityResolverHttp(Controller.log);
+ } else {
+ // No audit log was provided so use a mock one if present.
+ if (value) {
+ Controller.log.Message('using mock audit log', value);
+ const mock = Controller.MockAuditLogs[value];
+ const identityResolver = new IdentityResolverMap(2000);
+ mock.resolver.map((i) => {
+ identityResolver.map.set(i[0], i[1]);
+ });
+ this.identityResolver = identityResolver;
+ auditLog = mock.auditLog;
+ }
+ }
+ if (auditLog) {
+ this.modelPromise = this.verifyModel(auditLog);
+ } else {
+ Controller.log.Warn('invalid audit log', value);
+ }
+ }
+ }
- // TODO: Replace this with a fetch for the real audit log once available.
- const auditLog = JSON.parse(advert.getAttribute('auditLog'));
+ private setElement(advertElementOrId: HTMLElement | string) {
+ if (advertElementOrId instanceof HTMLElement) {
+ this.element = advertElementOrId;
+ } else {
+ this.element = document.getElementById(advertElementOrId);
+ }
+ }
- this.model = new Model(auditLog);
- this.view = new View(advert, locale, log);
+ /**
+ * Verifies the model created from the audit log and updates the UI ready for user interaction.
+ * @param auditLog to create the model from
+ * @returns
+ */
+ private async verifyModel(auditLog: AuditLog): Promise {
+ this.model = new Model(Controller.log, this.identityResolver, auditLog);
this.mapFieldsToUI();
- this.view.display('button');
+ Controller.log.Info('Model verification started', this.element.id);
+ await this.model.verify();
+ Controller.log.Info('Model verification complete', this.element.id);
+ this.model.updateUI();
this.bindActions();
-
- log.Info('Audit registered', advert.id);
+ return this.model;
}
/**
@@ -61,7 +148,77 @@ export class Controller {
* bind method of the model is called.
*/
private mapFieldsToUI(): void {
- this.model.results.forEach((r) => r.addBinding(new BindingProviders(this.view, 'ok-ui-providers', this.locale)));
+ // Bind the model fields to the be advert tab.
+ this.model.idsAndPreferences.addBinding(
+ new BindingElementIdsAndPreferences(
+ this.view,
+ 'advert-preference-title',
+ this.buildMap([this.locale.advertTitlePersonalized, this.locale.advertTitleStandard])
+ )
+ );
+ this.model.idsAndPreferences.addBinding(
+ new BindingElementIdsAndPreferences(
+ this.view,
+ 'advert-preference-thank-you',
+ this.buildMap([this.locale.advertThankYouPersonalized, this.locale.advertThankYouStandard])
+ )
+ );
+ this.model.idsAndPreferences.addBinding(
+ new BindingPreferenceDate(this.view, 'advert-preference-date', this.locale)
+ );
+ this.model.overall.addBinding(new BindingOverallStatus(this.view, 'advert-status', this.locale, this.model));
+
+ // Bind the model fields to the participants tab.
+ this.model.results.forEach((r) =>
+ r.addBinding(new BindingParticipant(this.view, 'participants-tree', this.locale, this.model))
+ );
+ this.model.participantsTab.addBinding(
+ new BindingTabButton(this.view, 'participants-this', ParticipantsTabs.This)
+ );
+ this.model.participantsTab.addBinding(
+ new BindingTabButton(this.view, 'participants-all', ParticipantsTabs.All)
+ );
+ this.model.participantsTab.addBinding(
+ new BindingTabButton(
+ this.view,
+ 'participants-suspicious',
+ ParticipantsTabs.Suspicious,
+ // Only show the suspicious participants if the overall status is not good.
+ () => this.model.overall.value !== OverallStatus.Good
+ )
+ );
+
+ // Bind the your data fields.
+ this.model.dataTab.addBinding(
+ new BindingTabButton(this.view, 'data-identifiers', DataTabs.Identifiers)
+ );
+ this.model.dataTab.addBinding(
+ new BindingTabButton(this.view, 'data-preferences', DataTabs.Preferences)
+ );
+ this.model.idsAndPreferences.addBinding(
+ new BindingPreferences(
+ this.view,
+ 'data',
+ this.locale,
+ this.model,
+ this.buildMap([this.locale.dataPreferencePersonalized, this.locale.dataPreferencePersonalized])
+ )
+ );
+ this.model.identifiers.forEach((i) => {
+ i.addBinding(new BindingIdentifier(this.view, 'data', this.locale, this.model));
+ });
+ }
+
+ /**
+ * Builds a map of marketing preferences to UI text.
+ * @param text array of four text values
+ * @returns
+ */
+ private buildMap(text: string[]): Map {
+ return new Map([
+ [Marketing.personalized, text[0]],
+ [Marketing.standard, text[1]],
+ ]);
}
/**
@@ -72,23 +229,42 @@ export class Controller {
}
/**
- * Binds specific HTML elements to the actions.
+ * Binds specific HTML elements to the actions and removes the attribute to prevent the same element being rebound.
* @param elements to have the event provided bound to
* @param event the name of the event in the addEventListener
*/
private bindActionElements(elements: HTMLElement[], event: string) {
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
+ const card = element.getAttribute('data-card');
+ if (card !== null) {
+ element.addEventListener(event, (e) => {
+ this.processCard(card);
+ e.preventDefault();
+ });
+ element.removeAttribute('data-card');
+ }
const action = element.getAttribute('data-action');
if (action !== null) {
element.addEventListener(event, (e) => {
this.processAction(action);
e.preventDefault();
});
+ element.removeAttribute('data-action');
}
}
}
+ /**
+ * Displays the given card, updates the UI, and then binds the actions.
+ * @param card
+ */
+ private processCard(card: string) {
+ this.view.display(card);
+ this.model.updateUI();
+ this.bindActions();
+ }
+
/**
* Processes the action provided, or outputs a warning if the action is not known.
* @param action the action to perform
@@ -96,54 +272,47 @@ export class Controller {
private processAction(action: string) {
switch (action) {
case 'settings':
- this.view.display('button');
- this.bindActions();
+ this.view.hide();
(window).PAFUI.promptConsent();
break;
- case 'audit':
- this.view.display('audit');
- this.model.updateUI();
- this.bindActions();
+ case 'open':
+ this.initModel();
+ this.processCard('advert');
break;
case 'close':
- this.view.display('button');
- this.bindActions();
+ this.view.hide();
break;
case 'download':
- // TODO: Code the action to download the audit log.
+ this.view.download(this.model);
+ break;
+ case 'participants-this':
+ this.changeParticipantsTab(ParticipantsTabs.This);
+ break;
+ case 'participants-all':
+ this.changeParticipantsTab(ParticipantsTabs.All);
+ break;
+ case 'participants-suspicious':
+ this.changeParticipantsTab(ParticipantsTabs.Suspicious);
+ break;
+ case 'data-identifiers':
+ this.changeDataTab(DataTabs.Identifiers);
+ break;
+ case 'data-preferences':
+ this.changeDataTab(DataTabs.Preferences);
break;
default:
- this.log.Warn(`Action '${action}' is not known`);
+ Controller.log.Warn(`Action '${action}' is not known`);
break;
}
}
-}
-/**
- * Custom UI binding to display the providers from the audit log.
- */
-class BindingProviders extends BindingViewOnly {
- private readonly locale: Locale;
-
- constructor(view: View, id: string, locale: Locale) {
- super(view, id);
- this.locale = locale;
+ private changeParticipantsTab(tab: ParticipantsTabs) {
+ this.model.participantsTab.value = tab;
+ this.bindActions();
}
- /**
- * Adds the transmission provider's text to the bound element.
- */
- public refresh(): HTMLDivElement {
- const element = super.getElement();
- if (element !== null) {
- const item = document.createElement('div');
- item.className = 'ok-ui-provider';
- item.innerHTML = providerComponent({
- ResultSVG: iconTick,
- Name: this.field.value.source.domain,
- });
- element.appendChild(item);
- }
- return element;
+ private changeDataTab(tab: DataTabs) {
+ this.model.dataTab.value = tab;
+ this.bindActions();
}
}
diff --git a/paf-mvp-audit/src/css/README.md b/paf-mvp-audit/src/css/README.md
index e69de29b..e304ca88 100644
--- a/paf-mvp-audit/src/css/README.md
+++ b/paf-mvp-audit/src/css/README.md
@@ -0,0 +1 @@
+See ./paf-mvp-pattern-library-audit for details of CSS creation.
\ No newline at end of file
diff --git a/paf-mvp-audit/src/css/ok-ui.css b/paf-mvp-audit/src/css/ok-ui.css
index 2350e3e5..836bfe22 100644
--- a/paf-mvp-audit/src/css/ok-ui.css
+++ b/paf-mvp-audit/src/css/ok-ui.css
@@ -1 +1 @@
-.ok-ui{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:12px;line-height:1.5;color:#252e38;box-sizing:border-box}.ok-ui *,.ok-ui *::before,.ok-ui *::after{box-sizing:inherit;font-family:inherit;font-size:inherit;line-height:inherit;color:inherit}.ok-ui,.ok-ui *,.ok-ui *::before,.ok-ui *::after{padding:0;margin:0;border:none;outline:none;text-transform:none;transition:none;box-shadow:none;background:transparent}.ok-ui .ok-ui-heading-1{font-family:Verdana,sans-serif;font-size:1.083em;line-height:1.429;font-weight:600}.ok-ui .ok-ui-heading-2{font-family:Verdana,sans-serif;font-size:1em;line-height:1.333;font-weight:600}.ok-ui .ok-ui-button{display:flex;justify-content:center;align-items:center;width:100%;padding:.6666666667em 1em;border-radius:999px;text-align:center;line-height:1.333;font-weight:600;cursor:pointer}.ok-ui .ok-ui-button[disabled]{cursor:default}.ok-ui .ok-ui-button--filled{color:#fff;background-color:#18a9e1}.ok-ui .ok-ui-button--filled:focus{box-shadow:0 0 0 .3333333333em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--filled:focus,.ok-ui .ok-ui-button--filled:hover{background-color:#127faa}.ok-ui .ok-ui-button--filled:active{background-color:#11759c}.ok-ui .ok-ui-button--filled[disabled]{background-color:#c4c6ca}.ok-ui .ok-ui-button--outlined{color:#18a9e1;border:solid .0833333333em #18a9e1}.ok-ui .ok-ui-button--outlined:focus{box-shadow:0 0 0 .3333333333em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--outlined:focus,.ok-ui .ok-ui-button--outlined:hover{color:#127faa;border-color:#127faa}.ok-ui .ok-ui-button--outlined:active{color:#11759c;border-color:#11759c;background-color:rgba(24,169,225,.12)}.ok-ui .ok-ui-button--outlined[disabled]{color:#c4c6ca;border-color:#c4c6ca}.ok-ui .ok-ui-button--text{color:#18a9e1}.ok-ui .ok-ui-button--text:focus{box-shadow:0 0 0 .3333333333em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--text:focus,.ok-ui .ok-ui-button--text:hover{color:#127faa}.ok-ui .ok-ui-button--text:active{color:#11759c;background-color:rgba(24,169,225,.12)}.ok-ui .ok-ui-button--text[disabled]{color:#c4c6ca}.ok-ui .ok-ui-button--small{padding:.3333333333em .6666666667em}.ok-ui .ok-ui-button>svg{fill:currentColor;margin-right:.3333333333em}.ok-ui .ok-ui-button--icon-end>.ok-ui-button__label{order:-1}.ok-ui .ok-ui-button--icon-end>svg{margin-right:0;margin-left:.3333333333em}.ok-ui .ok-ui-button--icon-only{display:inline-flex;width:auto;align-items:center;justify-content:center}.ok-ui .ok-ui-button--icon-only>svg{margin-right:0}.ok-ui .ok-ui-button.ok-ui-button--icon-only{padding:.6666666667em;min-width:2.6666666667em;min-height:2.6666666667em}.ok-ui .ok-ui-button--small.ok-ui-button--icon-only{padding:.3333333333em;min-width:2em;min-height:2em}.ok-ui .ok-ui-button+.ok-ui-button,.ok-ui .ok-ui-spacer+.ok-ui-button{margin-left:.6666666667em}.ok-ui .ok-ui-card{display:flex;flex-direction:column;width:40em;max-width:100vw;max-height:100vh;overflow:hidden;border-radius:8px;background-color:#fff}@media(min-width: 40em){.ok-ui .ok-ui-card{padding:1.3333333333em}}@media(min-width: 352px){.ok-ui .ok-ui-card{max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);margin:16px}}.ok-ui .ok-ui-card__header,.ok-ui .ok-ui-card__body,.ok-ui .ok-ui-card__footer{padding:1.3333333333em}.ok-ui .ok-ui-card__header{text-align:center}.ok-ui .ok-ui-card__header-logo{display:block;width:6em}.ok-ui [class*=ok-ui-heading-] .ok-ui-card__header-logo,.ok-ui [class*=ok-ui-heading-]+.ok-ui-card__header-logo{margin-top:.6666666667em;margin-bottom:0em}.ok-ui .ok-ui-card__header-logo--center{margin-left:auto;margin-right:auto}.ok-ui .ok-ui-card__header .ok-ui-heading-2{color:#505253}.ok-ui .ok-ui-card__header>p,.ok-ui .ok-ui-card__body>p,.ok-ui .ok-ui-card__footer>p{color:#828586;text-align:center}.ok-ui .ok-ui-card__header>p{margin-top:.6666666667em}.ok-ui .ok-ui-card__body{overflow:auto;padding-top:0}.ok-ui .ok-ui-card__body>p{margin-bottom:.6666666667em}.ok-ui .ok-ui-card__body>:last-child{margin-bottom:0}.ok-ui .ok-ui-card__actions{display:flex;align-items:center}.ok-ui .ok-ui-card__header .ok-ui-card__actions{justify-content:space-between;margin-bottom:.6666666667em}.ok-ui .ok-ui-card__header .ok-ui-card__actions .ok-ui-button{width:auto}.ok-ui .ok-ui-card__footer .ok-ui-card__actions>*{flex:1}.ok-ui .ok-ui-link{text-decoration:underline;color:#18a9e1}.ok-ui .ok-ui-popup{display:none;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000}.ok-ui .ok-ui-popup--open{display:flex;align-items:center;justify-content:center}.ok-ui .ok-ui-popup__block{position:absolute;top:0;left:0;right:0;bottom:0;z-index:0;background-color:rgba(0,0,0,.25)}.ok-ui .ok-ui-popup__content{z-index:1}.ok-ui .ok-ui-provider{display:flex;align-items:center;padding:.6666666667em 0;border-bottom:solid .0833333333em #e1e3e8}.ok-ui .ok-ui-provider .ok-ui-heading-1{flex:1;margin-left:.6666666667em;margin-right:.6666666667em}.ok-ui .ok-ui-mt-0{margin-top:0px}.ok-ui .ok-ui-mb-0{margin-bottom:0px}.ok-ui .ok-ui-mt-1{margin-top:8px}.ok-ui .ok-ui-mb-1{margin-bottom:8px}.ok-ui .ok-ui-mt-2{margin-top:16px}.ok-ui .ok-ui-mb-2{margin-bottom:16px}
+.ok-ui{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:14px;line-height:1.429;color:#252e38;box-sizing:border-box}.ok-ui *,.ok-ui *::before,.ok-ui *::after{box-sizing:inherit;font-family:inherit;font-size:inherit;line-height:inherit;color:inherit}.ok-ui,.ok-ui *,.ok-ui *::before,.ok-ui *::after{padding:0;margin:0;border:none;outline:none;text-transform:none;transition:none;box-shadow:none;background:transparent}.ok-ui .ok-ui-heading-1{font-family:Verdana,sans-serif;font-size:1.429em;line-height:1.6;font-weight:700}.ok-ui .ok-ui-heading-2{font-family:Verdana,sans-serif;font-size:1.143em;line-height:1.5;font-weight:700}.ok-ui .ok-ui-heading-3{font-weight:600}@media(min-width: 600px){.ok-ui .ok-ui-heading-3{font-size:1.143em}}@media(min-width: 600px){.ok-ui .ok-ui-lede{font-size:1.143em;line-height:1.5}}.ok-ui .ok-ui-meta{font-size:.857em;line-height:1.5;color:#828586}@media(max-width: 599px){.ok-ui .ok-ui-advert-wrapper .ok-ui-advert{margin-bottom:1.7142857143em}}@media(min-width: 600px){.ok-ui .ok-ui-advert-wrapper:not(.ok-ui-advert-wrapper--landscape){display:flex}.ok-ui .ok-ui-advert-wrapper:not(.ok-ui-advert-wrapper--landscape) .ok-ui-advert{flex:0 0 18.2857142857em;margin-right:2.8571428571em}.ok-ui .ok-ui-advert-wrapper.ok-ui-advert-wrapper--landscape .ok-ui-advert{margin-bottom:1.7142857143em}}.ok-ui .ok-ui-advert{text-align:center}.ok-ui .ok-ui-advert__image-wrapper{display:inline-block}@media(min-width: 600px){.ok-ui .ok-ui-advert__image-wrapper{padding:1.1428571429em;border:solid .0714285714em #c4c6ca;border-radius:8px}}.ok-ui .ok-ui-advert__image img{display:block;max-width:100%;margin:0 auto}.ok-ui .ok-ui-advert--blocked *{height:100%}.ok-ui .ok-ui-advert--blocked .ok-ui-advert__image{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:1.7142857143em;border-radius:8px;background-color:#f7f7f7}.ok-ui .ok-ui-advert--blocked .ok-ui-advert__image *{height:auto}.ok-ui .ok-ui-advert--paused .ok-ui-advert__image{position:relative}.ok-ui .ok-ui-advert--paused .ok-ui-advert__image::before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;z-index:1;background-image:linear-gradient(0deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5)),linear-gradient(0deg, #FFFFFF, #FFFFFF),var(--advert-image);background-blend-mode:normal,saturation,normal;background-repeat:no-repeat;background-size:cover}.ok-ui .ok-ui-advert--paused .ok-ui-advert__image::after{content:"";position:absolute;top:50%;left:50%;z-index:2;width:var(--icon-size);height:var(--icon-size);margin-top:calc(var(--icon-size)/-2);margin-left:calc(var(--icon-size)/-2);border-radius:9999px;background-color:#fff;background-image:url("data:image/svg+xml,%3Csvg width='1.071em' height='0.929em' viewBox='0 0 15 13' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.8133 12.2567L10.5759 10.0193C9.61066 10.4588 8.56045 10.6803 7.49992 10.668C6.4065 10.6816 5.32439 10.4456 4.33592 9.97801C3.56971 9.60417 2.88168 9.08787 2.30859 8.45668C1.70025 7.80458 1.22212 7.04224 0.899919 6.21068L0.833252 6.00134L0.903252 5.79068C1.38513 4.56154 2.19609 3.48879 3.24725 2.69001L1.49992 0.942676L2.44192 0.00134277L13.7546 11.314L12.8146 12.2567H12.8133ZM4.19059 3.63401C3.33854 4.23129 2.66569 5.04982 2.24459 6.00134C3.14753 8.08461 5.2305 9.40579 7.49992 9.33468C8.19981 9.34041 8.89562 9.22771 9.55792 9.00134L8.35792 7.80134C8.09081 7.93228 7.79739 8.00067 7.49992 8.00134C6.39822 7.99443 5.50683 7.10304 5.49992 6.00134C5.50024 5.70319 5.56864 5.40904 5.69992 5.14134L4.19059 3.63401ZM12.7346 8.40801L11.8066 7.48068C12.197 7.03929 12.517 6.54031 12.7553 6.00134C11.8535 3.91706 9.76975 2.5954 7.49992 2.66801C7.33525 2.66801 7.16992 2.67401 7.00992 2.68534L5.83325 1.50734C6.381 1.39017 6.93978 1.33228 7.49992 1.33468C8.59333 1.32111 9.67544 1.55709 10.6639 2.02468C11.4301 2.3985 12.1182 2.91481 12.6913 3.54601C13.2993 4.19732 13.7774 4.95873 14.0999 5.78934L14.1666 6.00134L14.0966 6.21201C13.7845 7.02406 13.3227 7.77031 12.7353 8.41201L12.7346 8.40801Z' fill='rgb(24, 169, 225)'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat}@media(max-width: 599px){.ok-ui .ok-ui-advert--paused .ok-ui-advert__image::after{--icon-size: 32px}}@media(min-width: 600px){.ok-ui .ok-ui-advert--paused .ok-ui-advert__image::after{--icon-size: 48px}}.ok-ui .ok-ui-advert-status{padding:1.1428571429em;border-radius:8px}@media(min-width: 600px){.ok-ui .ok-ui-advert-status{padding:1.7142857143em}}.ok-ui .ok-ui-advert-status--loading{background-color:#f7f7f7}.ok-ui .ok-ui-advert-status--good{background-color:rgba(74,210,62,.12)}.ok-ui .ok-ui-advert-status--good [class*=ok-ui-icon]{color:#4ad23e}.ok-ui .ok-ui-advert-status--suspicious{background-color:rgba(245,137,9,.12)}.ok-ui .ok-ui-advert-status--suspicious [class*=ok-ui-icon]{color:#f58909}.ok-ui .ok-ui-advert-status--violation{background-color:rgba(240,63,63,.12)}.ok-ui .ok-ui-advert-status--violation [class*=ok-ui-icon]{color:#f03f3f}.ok-ui .ok-ui-advert-status__footer{margin-top:1.1428571429em}@media(max-width: 599px){.ok-ui .ok-ui-advert-status__footer .ok-ui-divide{margin:1.1428571429em 0}}@media(min-width: 600px){.ok-ui .ok-ui-advert-status__footer{display:flex;padding-top:1.1428571429em;border-top:solid 1px rgba(37,46,56,.1)}.ok-ui .ok-ui-advert-status__footer .ok-ui-divide{display:none}.ok-ui .ok-ui-advert-status__footer .ok-ui-divide+.ok-ui-button{margin-left:1.1428571429em}}.ok-ui .ok-ui-button{display:flex;justify-content:center;align-items:center;padding:.6666666667em 1em;border-radius:999px;text-align:center;font-size:.8571428571em;line-height:1.333;font-weight:600;cursor:pointer}.ok-ui .ok-ui-button[disabled]{cursor:default}@media(max-width: 599px){.ok-ui .ok-ui-button--full{width:100%}}.ok-ui .ok-ui-button--filled[disabled]{background-color:#c4c6ca}.ok-ui .ok-ui-button--filled.ok-ui-button--primary{color:#fff;background-color:#18a9e1}.ok-ui .ok-ui-button--filled.ok-ui-button--primary:focus{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--filled.ok-ui-button--primary:focus,.ok-ui .ok-ui-button--filled.ok-ui-button--primary:hover{background-color:#127faa}.ok-ui .ok-ui-button--filled.ok-ui-button--primary:active{background-color:#11759c}.ok-ui .ok-ui-button--filled.ok-ui-button--warning{color:#fff;background-color:#f58909}.ok-ui .ok-ui-button--filled.ok-ui-button--warning:focus{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--filled.ok-ui-button--warning:focus,.ok-ui .ok-ui-button--filled.ok-ui-button--warning:hover{background-color:#ba6807}.ok-ui .ok-ui-button--filled.ok-ui-button--warning:active{background-color:#ab6006}.ok-ui .ok-ui-button--filled.ok-ui-button--warning[disabled]{background-color:#c4c6ca}.ok-ui .ok-ui-button--filled.ok-ui-button--danger{color:#fff;background-color:#f03f3f}.ok-ui .ok-ui-button--filled.ok-ui-button--danger:focus{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--filled.ok-ui-button--danger:focus,.ok-ui .ok-ui-button--filled.ok-ui-button--danger:hover{background-color:#e01212}.ok-ui .ok-ui-button--filled.ok-ui-button--danger:active{background-color:#d21010}.ok-ui .ok-ui-button--filled.ok-ui-button--danger[disabled]{background-color:#c4c6ca}.ok-ui .ok-ui-button--outlined[disabled]{color:#c4c6ca;border-color:#c4c6ca}.ok-ui .ok-ui-button--outlined.ok-ui-button--primary{color:#18a9e1;border:solid .0714285714em #18a9e1}.ok-ui .ok-ui-button--outlined.ok-ui-button--primary:focus{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--outlined.ok-ui-button--primary:focus,.ok-ui .ok-ui-button--outlined.ok-ui-button--primary:hover{color:#127faa;border-color:#127faa}.ok-ui .ok-ui-button--outlined.ok-ui-button--primary:active{color:#11759c;border-color:#11759c;background-color:rgba(24,169,225,.12)}.ok-ui .ok-ui-button--outlined.ok-ui-button--warning{color:#f58909;border:solid .0714285714em #f58909}.ok-ui .ok-ui-button--outlined.ok-ui-button--warning:focus{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--outlined.ok-ui-button--warning:focus,.ok-ui .ok-ui-button--outlined.ok-ui-button--warning:hover{color:#ba6807;border-color:#ba6807}.ok-ui .ok-ui-button--outlined.ok-ui-button--warning:active{color:#ab6006;border-color:#ab6006;background-color:rgba(245,137,9,.12)}.ok-ui .ok-ui-button--outlined.ok-ui-button--danger{color:#f03f3f;border:solid .0714285714em #f03f3f}.ok-ui .ok-ui-button--outlined.ok-ui-button--danger:focus{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--outlined.ok-ui-button--danger:focus,.ok-ui .ok-ui-button--outlined.ok-ui-button--danger:hover{color:#e01212;border-color:#e01212}.ok-ui .ok-ui-button--outlined.ok-ui-button--danger:active{color:#d21010;border-color:#d21010;background-color:rgba(240,63,63,.12)}.ok-ui .ok-ui-button--text{color:#252e38}.ok-ui .ok-ui-button--text:focus{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}.ok-ui .ok-ui-button--text:focus,.ok-ui .ok-ui-button--text:hover{color:#127faa}.ok-ui .ok-ui-button--text:active{color:#11759c;background-color:rgba(24,169,225,.12)}.ok-ui .ok-ui-button--text[disabled]{color:#c4c6ca}.ok-ui .ok-ui-button--small{padding:.2857142857em .5714285714em}.ok-ui .ok-ui-button>svg{margin-right:.5714285714em}.ok-ui .ok-ui-button--icon-end>.ok-ui-button__label{order:-1}.ok-ui .ok-ui-button--icon-end>svg{margin-right:0;margin-left:.5714285714em}.ok-ui .ok-ui-button--icon-only{display:inline-flex;width:auto;align-items:center;justify-content:center}.ok-ui .ok-ui-button--icon-only>svg{margin-right:0}.ok-ui .ok-ui-button.ok-ui-button--icon-only{padding:.5714285714em;min-width:2.2857142857em;min-height:2.2857142857em}.ok-ui .ok-ui-button--small.ok-ui-button--icon-only{padding:.2857142857em;min-width:1.7142857143em;min-height:1.7142857143em}.ok-ui .ok-ui-button+.ok-ui-button{margin-left:1.1428571429em}.ok-ui .ok-ui-button-group{display:flex}.ok-ui .ok-ui-card{position:relative;display:flex;flex-direction:column;width:62em;max-width:100vw;max-height:100vh;overflow:hidden;border-radius:16px;background-color:#fff;box-shadow:0px 4px 32px rgba(25,34,68,.24)}.ok-ui .ok-ui-card__header{padding:1.1428571429em}.ok-ui .ok-ui-card__body{padding:1.7142857143em 1.1428571429em}@media(min-width: 600px){.ok-ui .ok-ui-card__header{padding:1.7142857143em}.ok-ui .ok-ui-card__body{padding:2.8571428571em}}.ok-ui .ok-ui-card__header{border-radius:16px;box-shadow:0px .0714285714em .4285714286em rgba(31,32,52,.1)}.ok-ui .ok-ui-card__header-context{display:flex;align-items:center}@media(min-width: 600px){.ok-ui .ok-ui-card__header-context{justify-content:space-between}}@media(max-width: 599px){.ok-ui .ok-ui-card__header-navigation .ok-ui-heading-1{display:none}}@media(min-width: 600px){.ok-ui .ok-ui-card__header-navigation{display:flex;align-items:center;justify-content:space-between;padding-top:1.7142857143em;border-top:solid 1px #e1e3e8;margin-top:1.7142857143em}}.ok-ui .ok-ui-card__header-logo{width:5.1428571429em}@media(min-width: 600px){.ok-ui .ok-ui-card__header-logo{width:7.7142857143em}}.ok-ui .ok-ui-card__header-logos{display:flex;list-style:none}@media(max-width: 599px){.ok-ui .ok-ui-card__header-logos{margin-left:auto}}.ok-ui .ok-ui-card__header-logos>*+*::before{content:"";display:inline;padding-left:.5714285714em;margin-left:.5714285714em;border-left:solid .0714285714em #e1e3e8}@media(min-width: 600px){.ok-ui .ok-ui-card__header-logos>*+*::before{padding-left:1.1428571429em;margin-left:1.1428571429em}}.ok-ui .ok-ui-card__header-close{display:flex;justify-content:end;width:7.7142857143em}@media(max-width: 599px){.ok-ui .ok-ui-card__header-close{display:none}}.ok-ui .ok-ui-card__header-close svg{color:#c4c6ca}.ok-ui .ok-ui-card__body{overflow:auto}.ok-ui .ok-ui-card--blocked{position:relative;overflow:hidden}.ok-ui .ok-ui-card--blocked::before{content:"";position:absolute;top:0;left:0;bottom:0;right:0;z-index:1;background-color:rgba(27,29,65,.2);backdrop-filter:blur(4px)}.ok-ui .ok-ui-card--blocked::before{border-radius:16px}@media(min-width: 600px){.ok-ui .ok-ui-data-header{display:flex;justify-content:space-between}}.ok-ui .ok-ui-data{padding:.5714285714em 1.7142857143em;border:solid .0714285714em #c4c6ca;border-radius:8px;list-style:none;word-break:break-all}.ok-ui .ok-ui-datum{padding:1.1428571429em 0}@media(max-width: 599px){.ok-ui .ok-ui-heading-3{margin-top:.5714285714em;margin-bottom:.2857142857em}}@media(min-width: 600px){.ok-ui .ok-ui-datum__body{display:flex;align-items:center}.ok-ui .ok-ui-datum__name{margin-left:.5714285714em}.ok-ui .ok-ui-datum__value{margin-left:auto}}.ok-ui .ok-ui-datum+.ok-ui-datum{border-top:solid .0714285714em #e1e3e8}.ok-ui .ok-ui-details summary{list-style:none;cursor:pointer}.ok-ui .ok-ui-details summary::-webkit-details-marker{display:none}.ok-ui .ok-ui-details--primary summary{color:#18a9e1}.ok-ui .ok-ui-details__icon{display:inline-block;transition:transform .3s ease-out}.ok-ui .ok-ui-details[open] .ok-ui-details__icon{transform:rotate(-180deg)}.ok-ui .ok-ui-divide{height:1px;margin:1.7142857143em 0;background-color:rgba(37,46,56,.1)}.ok-ui .ok-ui-download-data{display:grid;gap:1.1428571429em}@media(min-width: 600px){.ok-ui .ok-ui-download-data{grid-auto-flow:column;grid-auto-columns:1fr}}.ok-ui .ok-ui-download-datum{padding:1.7142857143em;border:solid .0714285714em #c4c6ca;border-radius:8px}.ok-ui .ok-ui-download-datum .ok-ui-heading-2{margin:.5714285714em 0}.ok-ui .ok-ui-download-data__action{margin-top:1.1428571429em}@media(min-width: 600px){.ok-ui .ok-ui-download-data__action{margin-top:1.7142857143em}.ok-ui .ok-ui-download-data__action .ok-ui-button{margin:0 auto}}.ok-ui .ok-ui-icon--primary{color:#18a9e1}.ok-ui .ok-ui-icon--lg{font-size:1.5714285714em}.ok-ui .ok-ui-icon--xl{font-size:1.8571428571em}.ok-ui .ok-ui-link{font-weight:700;color:#18a9e1;text-decoration:none}.ok-ui .ok-ui-loading-wrapper{display:flex;align-items:center}.ok-ui .ok-ui-loading-wrapper .ok-ui-loading{margin-right:.7142857143em}.ok-ui .ok-ui-loading{display:inline-block;width:1.8571428571em;height:1.8571428571em;border-radius:999px;border-top:.2142857143em solid rgba(24,169,225,.12);border-right:.2142857143em solid rgba(24,169,225,.12);border-bottom:.2142857143em solid rgba(24,169,225,.12);border-left:.2142857143em solid #18a9e1;animation:ok-ui-loading 1.1s infinite linear}.ok-ui .ok-ui-loading--small{width:1.4285714286em;height:1.4285714286em;border-width:.1428571429em}@keyframes ok-ui-loading{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.ok-ui .ok-ui-navigation-toggle-state{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.ok-ui .ok-ui-navigation-wrapper .ok-ui-navigation-toggle{display:none}.ok-ui .ok-ui-navigation__list{display:flex;list-style:none}.ok-ui .ok-ui-navigation__item{font-weight:500;user-select:none}.ok-ui .ok-ui-navigation__icon{display:inline-flex;align-items:center}.ok-ui .ok-ui-navigation__item:not(.ok-ui-navigation__item--selected){cursor:pointer}.ok-ui .ok-ui-navigation__item:not(.ok-ui-navigation__item--selected):focus,.ok-ui .ok-ui-navigation__item:not(.ok-ui-navigation__item--selected):hover{color:#127faa;border-color:#127faa}.ok-ui .ok-ui-navigation__item--selected{color:#18a9e1}@media(max-width: 599px){.ok-ui .ok-ui-card{position:relative}.ok-ui .ok-ui-card__body{margin-bottom:4.7142857143em}.ok-ui .ok-ui-navigation{position:absolute;bottom:0;left:0;right:0;display:flex;align-items:center;height:4.7142857143em;border-radius:8px;background-color:#fff;box-shadow:0px 0px 6px rgba(31,32,52,.1)}.ok-ui .ok-ui-navigation__list{align-items:center;justify-content:space-between;width:100%;text-align:center}.ok-ui .ok-ui-navigation__item{width:25%;hyphens:auto}.ok-ui .ok-ui-navigation__icon{justify-content:center;width:2.2857142857em;height:2.2857142857em;border-radius:9999px}.ok-ui .ok-ui-navigation__label{display:block;font-size:.7142857143em}.ok-ui .ok-ui-navigation__item--selected .ok-ui-navigation__icon{background-color:rgba(24,169,225,.12)}}@media(min-width: 600px)and (max-width: 869px){.ok-ui .ok-ui-navigation-wrapper{position:relative}.ok-ui .ok-ui-navigation-wrapper .ok-ui-navigation-toggle{display:flex;width:11.1428571429em;margin:0 .5714285714em;cursor:pointer}.ok-ui .ok-ui-navigation-wrapper .ok-ui-navigation-toggle:hover{color:#127faa}.ok-ui .ok-ui-navigation-toggle__arrow{margin-left:auto}.ok-ui .ok-ui-navigation{display:none;position:absolute;top:2.9285714286em;width:12.2857142857em;padding:.5714285714em;border-radius:8px;background-color:#fff;box-shadow:0px 8px 12px rgba(0,0,0,.12)}.ok-ui .ok-ui-navigation-toggle-state:checked~.ok-ui-card__header{position:relative;z-index:2}.ok-ui .ok-ui-navigation-toggle-state:checked~.ok-ui-card__header .ok-ui-navigation{display:block}.ok-ui .ok-ui-navigation-toggle-state:checked~.ok-ui-card__header~.ok-ui-card__body{position:relative;overflow:hidden}.ok-ui .ok-ui-navigation-toggle-state:checked~.ok-ui-card__header~.ok-ui-card__body::before{content:"";position:absolute;top:0;left:0;bottom:0;right:0;z-index:1;background-color:rgba(27,29,65,.2);backdrop-filter:blur(4px)}.ok-ui .ok-ui-navigation__list{flex-direction:column}.ok-ui .ok-ui-navigation__item+.ok-ui-navigation__item{margin-top:.5714285714em}}@media(min-width: 600px){.ok-ui .ok-ui-navigation__item{display:flex;align-items:center;padding:.5714285714em .8571428571em;border-radius:999px}.ok-ui .ok-ui-navigation__item--selected{background-color:rgba(24,169,225,.12)}.ok-ui .ok-ui-navigation__label{font-size:.8571428571em}.ok-ui .ok-ui-navigation__icon{margin-right:.4285714286em}}@media(min-width: 870px){.ok-ui .ok-ui-navigation__item{border:solid 1px #e1e3e8}.ok-ui .ok-ui-navigation__item--selected{border-color:transparent}.ok-ui .ok-ui-navigation__item+.ok-ui-navigation__item{margin-left:.5714285714em}}.ok-ui .ok-ui-option{margin-bottom:.5714285714em}.ok-ui .ok-ui-option__input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.ok-ui .ok-ui-option__label{display:flex;padding:.5714285714em .8571428571em;border:solid .0714285714em #f7f7f7;border-radius:8px;color:#505253;background-color:#f7f7f7;cursor:pointer}.ok-ui .ok-ui-option__label:hover{border-color:#c4c6ca;background-color:#fff}.ok-ui .ok-ui-option__label p{color:#828586}.ok-ui .ok-ui-option__input-facade{position:relative;display:block;flex:0 0 1.1428571429em;width:1.1428571429em;height:1.1428571429em;margin-top:.1428571429em;margin-right:.5714285714em;border:solid .0714285714em #c4c6ca;border-radius:999px}.ok-ui .ok-ui-option__input:focus~.ok-ui-option__label .ok-ui-option__input-facade{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}.ok-ui .ok-ui-option__input:checked~.ok-ui-option__label{border-color:#18a9e1;background-color:#fff}.ok-ui .ok-ui-option__input:checked~.ok-ui-option__label .ok-ui-option__input-facade{border-color:#828586}.ok-ui .ok-ui-option__input:checked~.ok-ui-option__label .ok-ui-option__input-facade::before{content:"";position:absolute;top:.1428571429em;left:.1428571429em;width:.7142857143em;height:.7142857143em;border-radius:999px;background-color:#18a9e1}.ok-ui .ok-ui-participant{border:solid .0714285714em #c4c6ca;border-radius:8px}.ok-ui .ok-ui-participant .ok-ui-heading-3{margin-bottom:.2857142857em}.ok-ui .ok-ui-participant+.ok-ui-participant{margin-top:.5714285714em}.ok-ui .ok-ui-participant--winning{border-color:#18a9e1}.ok-ui .ok-ui-participant--winning .ok-ui-meta{color:#18a9e1}.ok-ui .ok-ui-participant__status{display:flex;align-items:center;padding:.6666666667em;border-radius:8px;font-size:.8571428571em}.ok-ui .ok-ui-participant__status>*+*{margin-left:.2857142857em}.ok-ui .ok-ui-participant__status--trusted{color:#4ad23e;background-color:rgba(74,210,62,.12)}.ok-ui .ok-ui-participant__status--suspicious{color:#f58909;background-color:rgba(245,137,9,.12)}.ok-ui .ok-ui-participant__status--violation{color:#f03f3f;background-color:rgba(240,63,63,.12)}@media(max-width: 599px){.ok-ui .ok-ui-participant__body{padding:.5714285714em}.ok-ui .ok-ui-participant__details,.ok-ui .ok-ui-participant__actions{padding:.5714285714em}.ok-ui .ok-ui-participant__details{margin-top:.5714285714em}}@media(min-width: 600px){.ok-ui .ok-ui-participant__body{display:flex;align-items:center;padding:1.1428571429em}.ok-ui .ok-ui-participant__details{order:-1}.ok-ui .ok-ui-participant__status-wrapper{margin-left:auto;padding-right:1.7142857143em;border-right:solid 1px #e1e3e8;margin-right:1.7142857143em}}.ok-ui .ok-ui-participant__actions{display:flex}.ok-ui .ok-ui-participant__footer{display:flex;justify-content:space-between;align-items:center;padding:1.1428571429em;border-top:solid 1px #c4c6ca;color:#828586;font-weight:600;cursor:pointer}.ok-ui .ok-ui-participant__footer svg{transition:transform .3s ease-out}.ok-ui .ok-ui-participant--show-parties .ok-ui-participant__footer svg{transform:rotate(-180deg)}.ok-ui .ok-ui-participant-parties{padding:.5714285714em;padding-right:0;margin-top:.5714285714em;border-left:solid 1px #c4c6ca}.ok-ui .ok-ui-popup{display:none;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000}.ok-ui .ok-ui-popup--open{display:flex;justify-content:center}.ok-ui .ok-ui-popup__block{position:absolute;top:0;left:0;right:0;bottom:0;z-index:0;background-color:rgba(31,32,52,.8)}.ok-ui .ok-ui-popup__content{z-index:1}.ok-ui .ok-ui-popup__footer{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:4.1428571429em;line-height:4.1428571429em}.ok-ui .ok-ui-popup__footer,.ok-ui .ok-ui-popup__footer .ok-ui-button{color:#fff}@media(min-width: 600px){.ok-ui .ok-ui-popup__footer{display:none}}.ok-ui .ok-ui-popup .ok-ui-popup__content>.ok-ui-card{max-height:calc(100vh - 4.1428571429em)}@media(min-width: 352px){.ok-ui .ok-ui-popup .ok-ui-popup__content>.ok-ui-card{max-width:calc(100vw - 32px);max-height:calc(100vh - 16px - 4.1428571429em);margin:16px 16px 0}}@media(min-width: 600px){.ok-ui .ok-ui-popup .ok-ui-popup__content>.ok-ui-card{max-height:calc(100vh - 32px);margin:16px}}@media(min-width: 600px)and (min-height: 950px){.ok-ui .ok-ui-popup .ok-ui-popup__content>.ok-ui-card{max-height:calc(100vh - 200px);margin:100px 16px}}.ok-ui .ok-ui-popover{position:absolute;bottom:0;left:0;right:0;z-index:1;padding:1.1428571429em 1.7142857143em 1.7142857143em;max-width:100%;max-height:87%;overflow:auto}@media(min-width: 870px){.ok-ui .ok-ui-popover{padding:2.6428571429em 4em 4em}}.ok-ui .ok-ui-popover__header{display:flex;justify-content:end}.ok-ui .ok-ui-popover__header .ok-ui-button{color:#c4c6ca}@media(max-width: 599px){.ok-ui .ok-ui-popover .ok-ui-button-group .ok-ui-button{width:50%}}.ok-ui .ok-ui-status-wrapper{display:inline-flex;align-items:center}.ok-ui .ok-ui-status-wrapper .ok-ui-status{margin:0 .5714285714em 0 0}.ok-ui .ok-ui-status-wrapper--icon-end .ok-ui-status{order:2;margin:0 0 0 .5714285714em}.ok-ui .ok-ui-status{display:inline-flex;align-items:center}.ok-ui .ok-ui-status--good{color:#4ad23e}.ok-ui .ok-ui-status--suspicious{color:#f58909}.ok-ui .ok-ui-status--violation{color:#f03f3f}.ok-ui .ok-ui-tabs-container{position:relative;margin:0 -1.1428571429em}.ok-ui .ok-ui-tabs-container::before,.ok-ui .ok-ui-tabs-container::after{content:"";position:absolute;top:0;bottom:0;width:1.7142857143em}.ok-ui .ok-ui-tabs-container::before{left:0;background-image:linear-gradient(270deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 100%)}.ok-ui .ok-ui-tabs-container::after{right:0;background-image:linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 100%)}.ok-ui .ok-ui-tabs-wrapper{overflow:auto;padding-bottom:1.1428571429em}.ok-ui .ok-ui-tabs{display:flex;margin:0 1.1428571429em;border:solid .0714285714em #e1e3e8;border-radius:999px;white-space:nowrap;width:fit-content}.ok-ui .ok-ui-tabs>*{margin:-1px}.ok-ui .ok-ui-website-advert{display:inline-block;border:solid .0714285714em #e1e3e8;border-radius:0 0 .2857142857em .2857142857em}.ok-ui .ok-ui-website-advert>img{display:block}.ok-ui .ok-ui-website-advert__action{display:block;width:100%;padding:.2857142857em;border-radius:0 0 .2857142857em .2857142857em;text-align:center;line-height:1;background-color:#fff;cursor:pointer}.ok-ui .ok-ui-website-advert__action:focus{box-shadow:0 0 0 .2857142857em rgba(24,169,225,.12)}@media(min-width: 600px){.ok-ui .ok-ui-sm{display:none}}@media(max-width: 599px){.ok-ui .ok-ui-lg{display:none}}.ok-ui .ok-ui-hidden{display:none !important}.ok-ui .ok-ui-mt-0\.5{margin-top:4px}.ok-ui .ok-ui-mb-0\.5{margin-bottom:4px}.ok-ui .ok-ui-mt-0{margin-top:0px}.ok-ui .ok-ui-mb-0{margin-bottom:0px}.ok-ui .ok-ui-mt-1{margin-top:8px}.ok-ui .ok-ui-mb-1{margin-bottom:8px}.ok-ui .ok-ui-mt-2{margin-top:16px}.ok-ui .ok-ui-mb-2{margin-bottom:16px}.ok-ui .ok-ui-mt-3{margin-top:24px}.ok-ui .ok-ui-mb-3{margin-bottom:24px}.ok-ui .ok-ui-mt-4{margin-top:32px}.ok-ui .ok-ui-mb-4{margin-bottom:32px}
diff --git a/paf-mvp-audit/src/html/cards/advert.html b/paf-mvp-audit/src/html/cards/advert.html
new file mode 100644
index 00000000..6ff98a1d
--- /dev/null
+++ b/paf-mvp-audit/src/html/cards/advert.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+ ${ _.advertImageTitle }
+
+
+
+
+
+ ${ _.advertHtml }
+
+
+
+
+
+
+
+
+ ${ _.advertHtml }
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/cards/data.html b/paf-mvp-audit/src/html/cards/data.html
new file mode 100644
index 00000000..db6660df
--- /dev/null
+++ b/paf-mvp-audit/src/html/cards/data.html
@@ -0,0 +1,24 @@
+
We don't use any of your personal data, we identify you with a string of characters, that's all
+ advertisers see.
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/cards/download.html b/paf-mvp-audit/src/html/cards/download.html
new file mode 100644
index 00000000..a7cc3b2a
--- /dev/null
+++ b/paf-mvp-audit/src/html/cards/download.html
@@ -0,0 +1,49 @@
+
${ _.downloadTitle }
+
+ ${ _.downloadBodyHTML }
+
+
+
+
+
+
${ _.downloadBox1Title }
+
${ _.downloadBox1Body }
+
+
+
+
${ _.downloadBox2Title }
+
${ _.downloadBox2Body }
+
+
+
+
${ _.downloadBox3Title }
+
${ _.downloadBox3Body }
+
+
+
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/cards/participants.html b/paf-mvp-audit/src/html/cards/participants.html
new file mode 100644
index 00000000..401373e3
--- /dev/null
+++ b/paf-mvp-audit/src/html/cards/participants.html
@@ -0,0 +1,22 @@
+
${ _.participantsTitle }
+
${ _.participantsIntroHTML }
+
+
+
+
+
+
+
${ _.participantsThisContentIntro }
+
+
+
${ _.participantsFooterHTML }
diff --git a/paf-mvp-audit/src/html/components/button.html b/paf-mvp-audit/src/html/components/button.html
index 2dbfa877..4c944680 100644
--- a/paf-mvp-audit/src/html/components/button.html
+++ b/paf-mvp-audit/src/html/components/button.html
@@ -1 +1,5 @@
-${ _.open }
\ No newline at end of file
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/data.html b/paf-mvp-audit/src/html/components/data.html
new file mode 100644
index 00000000..3d9c2ed3
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/data.html
@@ -0,0 +1,34 @@
+
+
+
+
${ _.field }
+
${ _.statusSlug} ${ _.value }
+
+
+
+
+
+
${ _.dataSetupDateField }
+
${ _.dataSetupDateText }
+
+
+
+
+
+
${ _.dataTermsUsedField }
+
+
+
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/participant.html b/paf-mvp-audit/src/html/components/participant.html
new file mode 100644
index 00000000..b9a84eb9
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/participant.html
@@ -0,0 +1,25 @@
+
+
+ ${ _.statusHtml }
+
+
+
${ _.name }
+
+ ${ _.meta }
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/participantSuspicious.html b/paf-mvp-audit/src/html/components/participantSuspicious.html
new file mode 100644
index 00000000..a48ae91c
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/participantSuspicious.html
@@ -0,0 +1,8 @@
+
+ ${ _.participantSuspicious }
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/participantTrusted.html b/paf-mvp-audit/src/html/components/participantTrusted.html
new file mode 100644
index 00000000..6abb8b4d
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/participantTrusted.html
@@ -0,0 +1,8 @@
+
+ ${ _.participantTrusted }
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/participantViolating.html b/paf-mvp-audit/src/html/components/participantViolating.html
new file mode 100644
index 00000000..8dd1acd3
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/participantViolating.html
@@ -0,0 +1,9 @@
+
+ ${ _.participantViolating }
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/provider.html b/paf-mvp-audit/src/html/components/provider.html
deleted file mode 100644
index 7d02c491..00000000
--- a/paf-mvp-audit/src/html/components/provider.html
+++ /dev/null
@@ -1,18 +0,0 @@
-${ _.ResultSVG }
-
${ _.Name }
-
-
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/statusgood.html b/paf-mvp-audit/src/html/components/statusgood.html
new file mode 100644
index 00000000..e21f9010
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/statusgood.html
@@ -0,0 +1,10 @@
+
+
+
${ _.advertGoodTitle }
+ ${ _.advertGoodBodyHTML }
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/statusinprogress.html b/paf-mvp-audit/src/html/components/statusinprogress.html
new file mode 100644
index 00000000..5706921f
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/statusinprogress.html
@@ -0,0 +1,5 @@
+
+
+
${ _.advertInProgressTitle }
+ ${ _.advertInProgressBodyHTML }
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/statussluggood.html b/paf-mvp-audit/src/html/components/statussluggood.html
new file mode 100644
index 00000000..f34eaca0
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/statussluggood.html
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/statusslugsuspicious.html b/paf-mvp-audit/src/html/components/statusslugsuspicious.html
new file mode 100644
index 00000000..1463ad4e
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/statusslugsuspicious.html
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/statusslugviolation.html b/paf-mvp-audit/src/html/components/statusslugviolation.html
new file mode 100644
index 00000000..f0382b09
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/statusslugviolation.html
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/statussuspicious.html b/paf-mvp-audit/src/html/components/statussuspicious.html
new file mode 100644
index 00000000..6ea0bda7
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/statussuspicious.html
@@ -0,0 +1,22 @@
+
+
+
${ _.advertSuspiciousTitle }
+ ${ _.advertSuspiciousBodyHTML }
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/statusviolation.html b/paf-mvp-audit/src/html/components/statusviolation.html
new file mode 100644
index 00000000..afcf9845
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/statusviolation.html
@@ -0,0 +1,23 @@
+
+
+
${ _.advertViolationTitle }
+ ${ _.advertViolationBodyHTML }
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/suspicious.html b/paf-mvp-audit/src/html/components/suspicious.html
new file mode 100644
index 00000000..6f5778f4
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/suspicious.html
@@ -0,0 +1,29 @@
+
+
+
Suspicious transaction detected
+
We detected one or more suspicious transaction in this advertisement's supply chain.
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/components/tabs.html b/paf-mvp-audit/src/html/components/tabs.html
new file mode 100644
index 00000000..e69de29b
diff --git a/paf-mvp-audit/src/html/components/violation.html b/paf-mvp-audit/src/html/components/violation.html
new file mode 100644
index 00000000..bc78e402
--- /dev/null
+++ b/paf-mvp-audit/src/html/components/violation.html
@@ -0,0 +1,31 @@
+
+
+
Violation detected
+
We take your privacy seriously, we removed bad parties from our supply chain and we are reported to the
+ authorites. If you want to take steps yourself this is what you can do:
+
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/html/containers/audit.html b/paf-mvp-audit/src/html/containers/audit.html
index 52e7d63c..f3d2d0e8 100644
--- a/paf-mvp-audit/src/html/containers/audit.html
+++ b/paf-mvp-audit/src/html/containers/audit.html
@@ -1,32 +1,116 @@
-
+
-
\ No newline at end of file
diff --git a/paf-mvp-audit/src/identity-resolver.ts b/paf-mvp-audit/src/identity-resolver.ts
new file mode 100644
index 00000000..9531e0f5
--- /dev/null
+++ b/paf-mvp-audit/src/identity-resolver.ts
@@ -0,0 +1,88 @@
+import { Log } from '@core/log';
+import { GetIdentityResponse } from '@core/model';
+
+/**
+ * Service interface used to get identity information for the domain.
+ */
+export interface IdentityResolver {
+ /**
+ * Returns the identity for the domain, or null if the host has no identity.
+ * @param domain
+ */
+ get(domain: string): Promise;
+}
+
+/**
+ * Returns the identity using a map of host keys to identities.
+ */
+export class IdentityResolverMap implements IdentityResolver {
+ public readonly map: Map;
+
+ /**
+ * New instance of the identity resolver backed with a map.
+ * @param millisecondDelay length of time to wait in milliseconds before responding
+ * @param map of prepared identities or empty of not available
+ */
+ constructor(private readonly millisecondDelay: number, map?: Map) {
+ this.map = map ?? new Map();
+ }
+
+ /**
+ * Waits for the time to pass before responding. Simulates network latency.
+ * @param milliseconds to wait before responding
+ * @returns
+ */
+ private delay(milliseconds: number): Promise {
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
+ }
+
+ /**
+ * Returns the identity for the host, or null if the host has no identity.
+ * @param host
+ */
+ public get(host: string): Promise {
+ if (this.millisecondDelay > 0) {
+ return this.delay(this.millisecondDelay).then(() => this.map.get(host));
+ }
+ return Promise.resolve(this.map.get(host));
+ }
+}
+
+/**
+ * Returns the identity using an HTTP request to the host.
+ */
+export class IdentityResolverHttp implements IdentityResolver {
+ /**
+ * Cache of responses found so far.
+ */
+ private readonly map = new Map();
+
+ /**
+ * Constructs a new HTTP identity resolver.
+ * @param log for recording HTTP response text when an error occurs
+ */
+ constructor(private readonly log: Log) {}
+
+ /**
+ * Returns the identity for the host, or null if the host has no identity.
+ * Uses a map to avoid requesting the same identity information from the network.
+ * @param host
+ */
+ public async get(host: string): Promise {
+ let identity = this.map.get(host);
+ if (identity === null) {
+ const response = await fetch(`https://${host}/paf/v1/identity`, {
+ method: 'GET',
+ mode: 'cors',
+ cache: 'default',
+ });
+ if (response) {
+ identity = await response.json();
+ this.map.set(host, identity);
+ } else {
+ this.log.Warn(response.statusText);
+ }
+ }
+ return identity;
+ }
+}
diff --git a/paf-mvp-audit/src/images/AdvertIcon.svg b/paf-mvp-audit/src/images/AdvertIcon.svg
new file mode 100644
index 00000000..94341a68
--- /dev/null
+++ b/paf-mvp-audit/src/images/AdvertIcon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/images/DownloadIcon.svg b/paf-mvp-audit/src/images/DownloadIcon.svg
new file mode 100644
index 00000000..f73fcd0b
--- /dev/null
+++ b/paf-mvp-audit/src/images/DownloadIcon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/images/IconCross.svg b/paf-mvp-audit/src/images/IconCross.svg
deleted file mode 100644
index 916fa0b3..00000000
--- a/paf-mvp-audit/src/images/IconCross.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
\ No newline at end of file
diff --git a/paf-mvp-audit/src/images/IconTick.svg b/paf-mvp-audit/src/images/IconTick.svg
deleted file mode 100644
index 66ea6bb4..00000000
--- a/paf-mvp-audit/src/images/IconTick.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
\ No newline at end of file
diff --git a/paf-mvp-audit/src/images/OneKeyIcon.svg b/paf-mvp-audit/src/images/OneKeyIcon.svg
new file mode 100644
index 00000000..ec0e7259
--- /dev/null
+++ b/paf-mvp-audit/src/images/OneKeyIcon.svg
@@ -0,0 +1,9 @@
+
diff --git a/paf-mvp-audit/src/images/ParticipantsIcon.svg b/paf-mvp-audit/src/images/ParticipantsIcon.svg
new file mode 100644
index 00000000..68f83034
--- /dev/null
+++ b/paf-mvp-audit/src/images/ParticipantsIcon.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/images/YourDataIcon.svg b/paf-mvp-audit/src/images/YourDataIcon.svg
new file mode 100644
index 00000000..77bde98b
--- /dev/null
+++ b/paf-mvp-audit/src/images/YourDataIcon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/paf-mvp-audit/src/locale.ts b/paf-mvp-audit/src/locale.ts
deleted file mode 100644
index 3df2416f..00000000
--- a/paf-mvp-audit/src/locale.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import en_us from './locales/en-us.yaml';
-import en_gb from './locales/en-gb.yaml';
-
-export class Values {
- // Audit text
- auditHeading = 'NOT SET';
- auditBody: string[];
- auditFooter = 'NOT SET';
-
- // Button text
- download = 'NOT SET';
- cancel = 'NOT SET';
-}
-
-export class Locale extends Values {
- public readonly auditBodyHTML: string;
-
- /**
- * Logo to use with the templates.
- */
- public Logo = '';
-
- constructor(languages: readonly string[]) {
- super();
-
- // Use US english as the default locale.
- Object.assign(this, en_us);
-
- // Replace any values with the users chosen locale.
- Object.assign(this, this.getLocale(languages));
-
- // Extract the arrays into paragraph HTML element strings.
- this.auditBodyHTML = this.toHtml(this.auditBody);
- }
-
- private toHtml(list: string[]): string {
- return `
${list.join('
')}
`;
- }
-
- private getLocale(locales: readonly string[]): Values {
- for (const locale of locales) {
- switch (locale) {
- case 'en-GB':
- return en_gb;
- case 'en-US':
- return en_us;
- }
- }
- return en_us;
- }
-}
diff --git a/paf-mvp-audit/src/locales/en-gb.yaml b/paf-mvp-audit/src/locales/en-gb.yaml
deleted file mode 100644
index 62a05461..00000000
--- a/paf-mvp-audit/src/locales/en-gb.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-# Audit text
-auditHeading: Your ad-funded access (GB)
-auditBody:
- - The following OneKey participating providers funded your access to this site's content. These organizations do not directly identify you, but rely instead on your OneKey ID and preferences for this purpose. Your OneKey ID will automatically reset every 6 months or you can reset your ID or preference at any time by clicking here.
-auditFooter: If you believe an organization didn't honour your preferences, click the email icon to send them this audit for investigation. You may also download the same audit data to send to the appropriate government authority.
-
-# Button text
-download: Download text file
-cancel: Cancel
-open: Ad audit by OneKey
\ No newline at end of file
diff --git a/paf-mvp-audit/src/locales/en-us.yaml b/paf-mvp-audit/src/locales/en-us.yaml
index bd5eece6..aebfb155 100644
--- a/paf-mvp-audit/src/locales/en-us.yaml
+++ b/paf-mvp-audit/src/locales/en-us.yaml
@@ -1,10 +1,120 @@
-# Audit text
-auditHeading: Your ad-funded access (US)
-auditBody:
- - The following OneKey participating providers funded your access to this site's content. These organizations do not directly identify you, but rely instead on your OneKey ID and preferences for this purpose. Your OneKey ID will automatically reset every 6 months or you can reset your ID or preference at any time by clicking here.
-auditFooter: If you believe an organization didn't honour your preferences, click the email icon to send them this audit for investigation. You may also download the same audit data to send to the appropriate government authority.
+# Navigation text
+navAdvertAudit: Audit
+navAdvert: Content
+navYourChoices: Your choices
+navParticipants: Participants
+navDownload: Download
+
+# Advert text
+
+## Advert title text
+advertTitlePersonalized: This content was personalized for you
+advertTitleStandard: This content was not personalized for you
+
+## Advert image title
+advertImageTitle: Show the content image
+
+## Advert date of preferences
+advertDate: As you indicated on [Date]
+
+## Advert intro thank you
+advertThankYouPersonalized: Thank you for supporting our digital property by choosing personalized ads. ❤️
+advertThankYouStandard: Thank you for supporting our digital property by participating.
+
+## Advert statuses
+advertGoodTitle: No suspicious transactions found
+advertGoodBody:
+ - Everything is fine with the participants who helped display this content.
+advertSuspiciousTitle: Suspicious transactions detected
+advertSuspiciousBody:
+ - "[SuspiciousParticipants] participants who helped display this content couldn't be verified."
+advertSuspiciousButton: Check participants
+advertViolationTitle: Violations detected
+advertViolationBody:
+ - The terms and conditions for the use of data were violated by [ViolatingParticipants] . You can contact them directly.
+advertViolationButton: Contact violating participants
+advertInProgressTitle: Fetching the data…
+advertInProgressBody:
+ - Checking the participants involved in selecting this content.
+advertLearnMore: Learn more
+advertLearnMoreUrl: https://portal.onekey.network
+
+## Advert pause
+advertPauseTitle: Pause this Ad
+advertPauseInstruction: If you don’t like this advert, you can let the brand know.
+advertPauseButton: Pause this Ad
+
+# Data
+dataTitle: Data
+dataIntro: This is the data used to select this content.
+
+dataPreference: Marketing preference
+dataPreferenceStandard: Standard
+dataPreferencePersonalized: Personalized
+dataRandomIdField: Random browser ID
+dataSetupDateField: Setup date
+dataSetupDateText: Set up on [Date] by [Name]
+dataTermsUsedField: Terms used
+dataModifyPreferences: Modify preferences
+
+# Participant statuses
+participantTrusted: Trusted
+participantSuspicious: Suspicious
+participantViolating: Violation detected
+
+# Participants
+participantsTitle: Participants
+participantsIntro:
+ - The following OneKey participating providers funded your access to this site's content. These organizations do not directly identify you but rely instead on your OneKey ID and preferences for this purpose. Your OneKey ID will automatically reset every 6 months or you can reset your ID or preference at any time by clicking here.
+participantsFooter:
+ - If you believe an organization didn't honour your preferences, select the contact button to send them this audit for investigation. You may also download the same audit data to send to the appropriate government authority.
+participantsThisContent: This content
+participantsThisContentIntro: The following participants were specifically involved in showing you this content.
+participantsAll: All participants
+participantsAllIntro: The following participants were involved in the selection of this content.
+participantsSuspicious: Suspicious participants
+participantsSuspiciousIntro: The following participants either could not be verified, or failed validation. Click the contact button to send them this audit for investigation.
# Button text
-download: Download text file
-cancel: Cancel
-open: Ad audit by OneKey
\ No newline at end of file
+download: Download data (JSON)
+close: Close
+terms: See Terms
+contact: Contact
+
+# DPO email
+emailPreferencePersonalized: Personalized marketing
+emailPreferenceStandard: Standard marketing
+emailSubject: OneKey Data Protection Request
+emailBody:
+ - To whom it may concern,
+ -
+ - I believe [Name] used my personal information without a valid legal basis on [TimeStamp].
+ -
+ - On the [PreferenceDate] I consented to the use of my data for [PreferenceText].
+ -
+ - You cryptographically signed you received this information, shown at the bottom of this email. We therefore agree that you were in possession of this data.
+ -
+ - You are bound by the privacy policy here.
+ -
+ - "[PrivacyURL]"
+ -
+ - I would be grateful if you can respond by email to this address within 7 working days to explain why my preference was not honoured.
+ -
+ - Regards,
+ -
+ - INSERT YOUR NAME
+ -
+ - --- DO NOT CHANGE THE TEXT BELOW THIS LINE ---
+ - '[Proof]'
+ - --- DO NOT CHANGE THE TEXT ABOVE THIS LINE ---
+
+# Download
+downloadTitle: Download the full audit data
+downloadBody:
+ - Use this data to help keep advertising safe for everyone.
+downloadBox1Title: 3rd party plugins
+downloadBox1Body: Encourage 3rd parties to collect this data to help monitor online safety.
+downloadBox2Title: Report to authorities
+downloadBox2Body: Send to data protection authorities.
+downloadBox3Title: Keep records
+downloadBox3Body: Record all the advertising you've been exposed to.
diff --git a/paf-mvp-audit/src/main.ts b/paf-mvp-audit/src/main.ts
deleted file mode 100644
index 912064fb..00000000
--- a/paf-mvp-audit/src/main.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Locale } from './locale';
-import { Controller } from './controller';
-import { Log } from '@core/log';
-
-const log = new Log('audit', '#18a9e1');
-
-class MonitoredElement extends HTMLDivElement {
- public timer: NodeJS.Timer;
-}
-
-document.querySelectorAll('[auditLog]').forEach((e) => {
- if (e instanceof HTMLDivElement) {
- log.Message('register', e.id);
- const content = e.innerHTML;
- (e).timer = setInterval(() => {
- log.Message('check', e.id);
- if (content !== e.innerHTML) {
- log.Message('adding', e.id);
- clearInterval((e).timer);
- new Controller(new Locale(window.navigator.languages), e, log);
- }
- }, 1000);
- }
-});
diff --git a/paf-mvp-audit/src/model.ts b/paf-mvp-audit/src/model.ts
index b257c923..ef62374f 100644
--- a/paf-mvp-audit/src/model.ts
+++ b/paf-mvp-audit/src/model.ts
@@ -1,15 +1,319 @@
import { Field, IFieldBind, IModel } from '@core/ui/fields';
-import { AuditLog, TransmissionResult } from '@core/model/generated-model';
+import { AuditLog, Identifier, IdsAndPreferences, Seed, TransmissionResult } from '@core/model/generated-model';
+import { GetIdentityResponse } from '@core/model/generated-model';
+import { IdentityResolver } from './identity-resolver';
+import {
+ IdentifierDefinition,
+ IdsAndPreferencesDefinition,
+ IdsAndPreferencesVerifier,
+ SeedSignatureContainer,
+ Verifier,
+} from '@core/crypto';
+import { PublicKeyResolver } from './public-key-resolver';
+import { SeedDefinition, TransmissionContainer, TransmissionDefinition } from './signing-definitions';
+import { ILocale } from '@core/ui/ILocale';
+import { getDate } from '@core/timestamp';
+import { Log } from '@core/log';
/**
- * Field represents the transmission result from the audit log.
+ * The currently selected tab on the participants card.
*/
-export class FieldTransmissionResult extends Field implements IFieldBind {}
+export enum ParticipantsTabs {
+ This,
+ All,
+ Suspicious,
+}
+
+/**
+ * The currently selected tab on the your choices card.
+ */
+export enum DataTabs {
+ Identifiers,
+ Preferences,
+ SyncId, // Not used.
+}
+
+/**
+ * Different status associated with fields in the model.
+ */
+export enum VerifiedStatus {
+ IdentityNotFound = 'IdentityNotFound',
+ Valid = 'Valid',
+ NotValid = 'NotValid',
+ Processing = 'Processing', // model verification is not complete
+}
+
+/**
+ * Overall statuses that are used to determine what is communicated to the user.
+ */
+export enum OverallStatus {
+ Good = 'Good', // all good
+ Suspicious = 'Suspicious', // something suspicious but not a breach. i.e. 404 when fetching identity
+ Violation = 'Violation', // proof that something bad has happened
+ Processing = 'Processing', // model verification is not complete
+}
+
+/**
+ * Used to hold a list of all the verified fields in the model.
+ */
+export interface IVerifiedValue {
+ /**
+ * Identity information associated with the source which is populated when the promise completes.
+ * Returns undefined if the processing to establish identity is not complete.
+ * Returns null if the processing completed but failed.
+ */
+ get identity(): GetIdentityResponse;
+
+ /**
+ * Gets the verified status of the field after the promises have resolved.
+ * @returns the verified status of the field
+ */
+ get verifiedStatus(): VerifiedStatus;
+
+ /**
+ * Boolean field to indicate if the source signature is valid. Might be false if the source identity could not be
+ * found or if the signature is invalid.
+ * Returns undefined if the processing to establish validity is not complete.
+ */
+ get valid(): boolean;
+
+ /**
+ * Completes the verification process.
+ */
+ verify(): Promise;
+}
+
+/**
+ * Base class used by any value that can be signed. All signed values must have a source and therefore an identifier
+ * will need to be fetched. This class handles the fetching of identity that will be the same for all signed values and
+ * the subsequent request to verify the value.
+ * @type T with a source that will be used with the signed value.
+ */
+export abstract class VerifiedValue implements IVerifiedValue {
+ /**
+ * Identity information associated with the source which is populated when the promise completes.
+ * Returns undefined if the processing to establish identity is not complete.
+ * Returns null if the processing completed but failed.
+ */
+ public identity: GetIdentityResponse | null = undefined;
+
+ /**
+ * Boolean field to indicate if the source signature is valid. Might be false if the source identity could not be
+ * found or if the signature is invalid.
+ * Returns undefined if the processing to establish validity is not complete.
+ */
+ public valid: boolean = undefined;
+
+ /**
+ * Gets the verified status of the field after the promises have resolved.
+ * @returns the verified status of the field
+ */
+ public get verifiedStatus(): VerifiedStatus {
+ if (this.valid === undefined) {
+ return VerifiedStatus.Processing;
+ }
+ if (this.identity) {
+ return this.valid ? VerifiedStatus.Valid : VerifiedStatus.NotValid;
+ }
+ return VerifiedStatus.IdentityNotFound;
+ }
+
+ /**
+ * A new instance of a verified value that will have commenced the process of verification.
+ * @param log
+ * @param identityPromise that may not yet be resolved to fetch the identity information
+ * @param value the signed value that will be verified
+ */
+ constructor(
+ protected readonly log: Log,
+ private readonly identityPromise: Promise,
+ public readonly value: T
+ ) {}
+
+ /**
+ * Completes the verification process for the value.
+ */
+ public async verify() {
+ try {
+ this.identity = await this.identityPromise;
+ } catch (e) {
+ this.log.Message('identityPromise', e);
+ this.identity = null;
+ }
+ if (this.identity) {
+ try {
+ this.valid = await this.verifySignature();
+ } catch (e) {
+ this.log.Message('verifySignature', e);
+ this.valid = false;
+ }
+ } else {
+ this.valid = false;
+ }
+ }
+
+ /**
+ * Method called to start the verification process once the identity has been obtained.
+ */
+ protected abstract verifySignature(): Promise;
+}
+
+export class VerifiedSeed extends VerifiedValue {
+ /**
+ * Static instance of the ids and preferences definition for use with the verifier.
+ */
+ private static readonly definition = new SeedDefinition();
+
+ /**
+ * Constructs a new instance of the verified ids and preferences value.
+ * @param log
+ * @param identityResolver used to retrieve identities for host names.
+ * @param idsAndPreferences
+ * @param seed
+ */
+ constructor(
+ log: Log,
+ identityResolver: IdentityResolver,
+ private readonly idsAndPreferences: IdsAndPreferences,
+ seed: Seed
+ ) {
+ super(log, identityResolver.get(seed.source.domain), seed);
+ }
+
+ protected verifySignature(): Promise {
+ const verifier = new Verifier(
+ new PublicKeyResolver(this.log, this.identity, this.value.source.timestamp).provider,
+ VerifiedSeed.definition
+ );
+ return verifier.verifySignature({ seed: this.value, idsAndPreferences: this.idsAndPreferences });
+ }
+}
+
+export class VerifiedIdentifier extends VerifiedValue {
+ /**
+ * Static instance of the identifier definition for use with the verifier.
+ */
+ private static readonly definition = new IdentifierDefinition();
+
+ /**
+ * Constructs a new instance of the verified ids and preferences value.
+ * @param log
+ * @param identityResolver used to retrieve identities for host names.
+ * @param identifier
+ */
+ constructor(log: Log, identityResolver: IdentityResolver, identifier: Identifier) {
+ super(log, identityResolver.get(identifier.source.domain), identifier);
+ }
+
+ protected verifySignature(): Promise {
+ const verifier = new Verifier(
+ new PublicKeyResolver(this.log, this.identity, this.value.source.timestamp).provider,
+ VerifiedIdentifier.definition
+ );
+ return verifier.verifySignature(this.value);
+ }
+}
+
+export class VerifiedIdsAndPreferences extends VerifiedValue {
+ /**
+ * Static instance of the ids and preferences definition for use with the verifier.
+ */
+ private static readonly definition = new IdsAndPreferencesDefinition();
+
+ /**
+ * Constructs a new instance of the verified ids and preferences value.
+ * @param log
+ * @param identityResolver used to retrieve identities for host names.
+ * @param idsAndPreferences
+ */
+ constructor(log: Log, identityResolver: IdentityResolver, idsAndPreferences: IdsAndPreferences) {
+ super(log, identityResolver.get(idsAndPreferences.preferences.source.domain), idsAndPreferences);
+ }
+
+ protected verifySignature(): Promise {
+ const verifier = new IdsAndPreferencesVerifier(
+ new PublicKeyResolver(this.log, this.identity, this.value.preferences.source.timestamp).provider,
+ VerifiedIdsAndPreferences.definition
+ );
+ return verifier.verifySignature(this.value);
+ }
+}
+
+/**
+ * A verified transmission result from the audit log.
+ */
+export class VerifiedTransmissionResult extends VerifiedValue {
+ /**
+ * Static instance of the ids and preferences definition for use with the verifier.
+ */
+ private static readonly definition = new TransmissionDefinition();
+
+ /**
+ * Constructs a new instance of TransmissionResultNode for the audit log record provided.
+ * @param log
+ * @param identityResolver used to retrieve identities for host names.
+ * @param idsAndPreferences
+ * @param seed
+ * @param result returned in the audit log.
+ */
+ constructor(
+ log: Log,
+ identityResolver: IdentityResolver,
+ private readonly idsAndPreferences: IdsAndPreferences,
+ private readonly seed: Seed,
+ result: TransmissionResult
+ ) {
+ super(log, identityResolver.get(result.source.domain), result);
+ }
+
+ protected verifySignature(): Promise {
+ const verifier = new Verifier(
+ new PublicKeyResolver(this.log, this.identity, this.value.source.timestamp).provider,
+ VerifiedTransmissionResult.definition
+ );
+ return verifier.verifySignature({ idsAndPreferences: this.idsAndPreferences, seed: this.seed, result: this.value });
+ }
+
+ /**
+ * Builds the email hyperlink for the complaint method.
+ * @param locale the email to complain about the signer of the transmission result
+ * @returns a mail:>// URL to open an email message
+ */
+ public buildEmailUrl(locale: ILocale): string {
+ const body = encodeURIComponent(
+ (locale.emailBodyText)
+ .replace('[Name]', this.identity.name)
+ .replace('[TimeStamp]', getDate(this.value.source.timestamp).toLocaleString())
+ .replace('[PrivacyURL]', this.identity.privacy_policy_url)
+ .replace('[PreferenceDate]', getDate(this.idsAndPreferences.preferences.source.timestamp).toLocaleString())
+ .replace(
+ '[PreferenceText]',
+ this.idsAndPreferences.preferences.data.use_browsing_for_personalization
+ ? locale.emailPreferencePersonalized
+ : locale.emailPreferenceStandard
+ )
+ .replace('[Proof]', [JSON.stringify(this.seed)].join('\r\n'))
+ .trim()
+ );
+ const subject = encodeURIComponent(locale.emailSubject);
+ return `mailto:${this.identity.dpo_email}?subject=${subject}&body=${body}`;
+ }
+}
/**
* The model used in the module.
*/
export class Model implements IModel {
+ /**
+ * Current tab for the display of participants.
+ */
+ public participantsTab = new Field(this, ParticipantsTabs.This);
+
+ /**
+ * Current tab for the display of choices and data.
+ */
+ public dataTab = new Field(this, DataTabs.Preferences);
+
/**
* Set to true when model update operations are occurring. Results in the methods to update other properties being
* disabled.
@@ -19,7 +323,27 @@ export class Model implements IModel {
/**
* The data fields that relate to each transmission result to be displayed.
*/
- readonly results: FieldTransmissionResult[] = [];
+ readonly results: Field[] = [];
+
+ /**
+ * Verified field for the seed.
+ */
+ readonly seed: Field;
+
+ /**
+ * Verified field for the preferences which must also include the identifiers.
+ */
+ readonly idsAndPreferences: Field;
+
+ /**
+ * Array of identifier fields.
+ */
+ readonly identifiers: Field[];
+
+ /**
+ * All the fields that relate to verified values.
+ */
+ readonly allVerifiedFields: Field[] = [];
/**
* All the fields that need to be bound.
@@ -27,15 +351,101 @@ export class Model implements IModel {
readonly allFields: IFieldBind[] = [];
/**
- * Constructs the data model from the audit log.
- * @param audit
+ * Overall status of the model updated after verify.
*/
- constructor(audit: AuditLog) {
- audit.transmissions?.forEach((t) => {
- const field = new FieldTransmissionResult(this, t);
- this.results.push(field);
+ readonly overall = new Field(this, OverallStatus.Processing);
+
+ /**
+ * Constructs the data model from the audit log starting the promises to retrieve identity and then verify the values.
+ * @remarks The model must not be used until the promise returned from the verify() method resolves.
+ * @param log
+ * @param auditLog the original raw audit log.
+ * @param identityResolver used to retrieve identities for host names.
+ */
+ constructor(log: Log, identityResolver: IdentityResolver, private readonly auditLog: AuditLog) {
+ // Add the simple fields created before the constructor runs.
+ this.allFields.push(this.overall);
+ this.allFields.push(this.participantsTab);
+ this.allFields.push(this.dataTab);
+
+ // Add the identifier fields to the model.
+ this.identifiers = [];
+ auditLog.data.identifiers.forEach((i) => {
+ const field = new Field(this, new VerifiedIdentifier(log, identityResolver, i));
+ this.identifiers.push(field);
+ this.allVerifiedFields.push(field);
this.allFields.push(field);
});
+
+ // Create the field for the seed and add the value to the list of values being verified.
+ this.seed = new Field(
+ this,
+ new VerifiedSeed(log, identityResolver, auditLog.data, auditLog.seed)
+ );
+ this.allVerifiedFields.push(this.seed);
+ this.allFields.push(this.seed);
+
+ // Create the field for the ids and preferences and add the value to the list of values being verified.
+ this.idsAndPreferences = new Field(
+ this,
+ new VerifiedIdsAndPreferences(log, identityResolver, auditLog.data)
+ );
+ this.allVerifiedFields.push(this.idsAndPreferences);
+ this.allFields.push(this.idsAndPreferences);
+
+ // Loop through the transmission results adding fields to the model and adding the value to the values being
+ // verified.
+ auditLog.transmissions?.forEach((result) => {
+ this.AddTransmissionField(log, identityResolver, auditLog, result);
+ });
+ }
+
+ /**
+ * Should be called after the constructor to complete all verification processing.
+ * @remarks All identity and verification promises will have been settled once this promise resolves.
+ * @returns an instance of this model
+ */
+ public async verify(): Promise {
+ if (this.verifyComplete === false) {
+ for (let i = 0; i < this.allVerifiedFields.length; i++) {
+ await this.allVerifiedFields[i].value.verify();
+ }
+ this.overall.value = this.getOverall();
+ this.verifyComplete = true;
+ }
+ return this;
+ }
+ private verifyComplete = false;
+
+ /**
+ * Counts the number of fields with a given status.
+ * @returns
+ */
+ public count(match: VerifiedStatus): number {
+ let count = 0;
+ this.allVerifiedFields.forEach((field) => {
+ if (field.value.verifiedStatus === match) {
+ count++;
+ }
+ });
+ return count;
+ }
+
+ /**
+ * Checks all the verifiable fields and determines the overall status for the audit log.
+ * @returns the overall status for the audit log
+ */
+ public getOverall(): OverallStatus {
+ if (
+ this.allVerifiedFields.every((s) => s.value.verifiedStatus === VerifiedStatus.Valid) &&
+ this.results.every((s) => s.value.value.status === 'success')
+ ) {
+ return OverallStatus.Good;
+ }
+ if (this.allVerifiedFields.some((s) => s.value.verifiedStatus === VerifiedStatus.NotValid)) {
+ return OverallStatus.Violation;
+ }
+ return OverallStatus.Suspicious;
}
/**
@@ -44,4 +454,50 @@ export class Model implements IModel {
public updateUI() {
this.allFields?.forEach((f) => f.updateUI());
}
+
+ /**
+ * File name to use when downloading the audit log as JSON.
+ */
+ public get jsonFileName(): string {
+ return this.fileNameNoExtension + '.json';
+ }
+
+ /**
+ * File name to use when downloading the audit log.
+ * @remarks must replace the colon with hyphen as it is not a valid file character.
+ * Also replaces the dot in the domain name with hyphen to avoid problems with extension identification.
+ */
+ public get fileNameNoExtension(): string {
+ const date = getDate(this.auditLog.seed.source.timestamp).toISOString().replace(':', '-');
+ const publisher = this.auditLog.seed.publisher.replace('.', '-');
+ return `audit-log-${publisher}-${date}`;
+ }
+
+ /**
+ * The audit log as a JSON format string.
+ */
+ public get jsonContent(): string {
+ return JSON.stringify(this.auditLog);
+ }
+
+ /**
+ * Adds the transmission field to the model.
+ * @param identityResolver
+ * @param auditLog
+ * @param result
+ */
+ private AddTransmissionField(
+ log: Log,
+ identityResolver: IdentityResolver,
+ auditLog: AuditLog,
+ result: TransmissionResult
+ ) {
+ const field = new Field(
+ this,
+ new VerifiedTransmissionResult(log, identityResolver, auditLog.data, auditLog.seed, result)
+ );
+ this.results.push(field);
+ this.allFields.push(field);
+ this.allVerifiedFields.push(field);
+ }
}
diff --git a/paf-mvp-audit/src/public-key-resolver.ts b/paf-mvp-audit/src/public-key-resolver.ts
new file mode 100644
index 00000000..ad6e5add
--- /dev/null
+++ b/paf-mvp-audit/src/public-key-resolver.ts
@@ -0,0 +1,66 @@
+import { PublicKeyProvider } from '@core/crypto';
+import { isValidKey, publicKeyFromString } from '@core/crypto/keys';
+import { Log } from '@core/log';
+import { GetIdentityResponse } from '@core/model';
+import { IECDSA } from 'ecdsa-secp256r1';
+/**
+ * Utility class used to take an GetIdentityResponse response and turn it into a public key provider that can be used
+ * for verification.
+ */
+export class PublicKeyResolver {
+ /**
+ * Returns a public key resolver for the identity and time stamp provided.
+ * @param log
+ * @param identity to use for the public key, the domain parameter of the provider method is ignored
+ * @param requiredTimestamp the time stamp for the data that requires the public key
+ */
+ constructor(
+ private readonly log: Log,
+ private readonly identity: GetIdentityResponse,
+ private readonly requiredTimestamp: number
+ ) {}
+
+ /**
+ * Core crypto implementation of the public key provider function.
+ * @param domain is not used as the identity to return is passed to the constructor
+ * @returns
+ */
+ public readonly provider: PublicKeyProvider = async (domain: string) => {
+ let publicKey: IECDSA = null;
+ const publicKeyIndex = this.getValidPublicKeyIndex(this.identity);
+ if (publicKeyIndex >= 0) {
+ try {
+ publicKey = await publicKeyFromString(this.identity.keys[publicKeyIndex].key);
+ } catch (e) {
+ this.log.Warn('PublicKeyResolver', e);
+ }
+ } else {
+ const message = `No valid keys for '${domain}' at timestamp '${
+ this.requiredTimestamp
+ }' with identity '${JSON.stringify(this.identity)}'`;
+ this.log.Warn(message);
+ }
+ return publicKey;
+ };
+
+ /**
+ * The index in the keys array of the first valid entry.
+ * @param identity whose keys member is inspected
+ * @returns
+ */
+ private getValidPublicKeyIndex(identity: GetIdentityResponse): number {
+ for (let index = 0; index < identity.keys.length; index++) {
+ const key = identity.keys[index];
+ if (
+ isValidKey(key) &&
+ // The start of the public key must be the same or earlier than the time stamp being validated.
+ key.start <= this.requiredTimestamp &&
+ // Either the end time stamp is missing or the end is greater than the time stamp being validated.
+ (!key.end || key.end > this.requiredTimestamp)
+ ) {
+ return index;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/paf-mvp-audit/src/signing-definitions.ts b/paf-mvp-audit/src/signing-definitions.ts
new file mode 100644
index 00000000..af69dfc6
--- /dev/null
+++ b/paf-mvp-audit/src/signing-definitions.ts
@@ -0,0 +1,47 @@
+import { SeedSignatureBuilder, SeedSignatureContainer, SigningDefinition } from '@core/crypto';
+import { IdsAndPreferences, Seed, TransmissionResponse, TransmissionResult } from '@core/model';
+
+export interface SignedSeedSignatureContainer extends SeedSignatureContainer {
+ seed: Seed;
+ idsAndPreferences: IdsAndPreferences;
+}
+
+export interface TransmissionContainer extends SignedSeedSignatureContainer {
+ result: TransmissionResult | TransmissionResponse | undefined;
+}
+
+export class SeedDefinition
+ extends SeedSignatureBuilder
+ implements SigningDefinition
+{
+ getSignature(data: SignedSeedSignatureContainer): string {
+ return data.seed.source.signature;
+ }
+ getSignerDomain(data: SignedSeedSignatureContainer): string {
+ return data.seed.source.domain;
+ }
+}
+
+export class TransmissionDefinition implements SigningDefinition {
+ private static readonly seedDefinition = new SeedDefinition();
+
+ getSignature(data: TransmissionContainer): string {
+ TransmissionDefinition.validateData(data);
+ return data.result.source.signature;
+ }
+
+ getSignerDomain(data: TransmissionContainer): string {
+ TransmissionDefinition.validateData(data);
+ return data.result.source.domain;
+ }
+
+ getInputString(data: TransmissionContainer): string {
+ return TransmissionDefinition.seedDefinition.getInputString(data);
+ }
+
+ private static validateData(data: TransmissionContainer) {
+ if (data.result === undefined) {
+ throw 'Data has not yet been signed';
+ }
+ }
+}
diff --git a/paf-mvp-audit/src/view.ts b/paf-mvp-audit/src/view.ts
index dd2993af..deda4ba2 100644
--- a/paf-mvp-audit/src/view.ts
+++ b/paf-mvp-audit/src/view.ts
@@ -1,53 +1,106 @@
/**
* Resources used by the controller for HTML views and CSS.
- * TODO: fix the warning associated with can't find module or type.
*/
-import logoSvg from './images/OneKey.svg';
import css from './css/ok-ui.css';
+import advertTemplate from './html/cards/advert.html';
+import dataTemplate from './html/cards/data.html';
+import downloadTemplate from './html/cards/download.html';
+import participantsTemplate from './html/cards/participants.html';
import auditTemplate from './html/containers/audit.html';
import buttonTemplate from './html/components/button.html';
-import { Locale } from './locale';
+import { ILocale } from '@core/ui/ILocale';
import { IView } from '@core/ui/binding';
import { Log } from '@core/log';
+import { Model } from './model';
export class View implements IView {
// The shadow root for the UI.
public root: ShadowRoot = null;
// The outer container for the UI.
- private outerContainer: HTMLElement = null;
+ private outerContainer: HTMLElement | null = null;
- // The element that contains the advert. Used to add the UI components to the DOM around the advert.
- private readonly advert: HTMLElement;
+ // The inner container under the root element.
+ private innerContainer: HTMLDivElement | null = null;
- // The container element for the UI, or null if the UI has not yet been added to the DOM.
- private auditContainer: HTMLDivElement | null = null;
+ // The child of the audit modal div container.
+ private popupContainer: HTMLDivElement | null = null;
- // Log used to output status messages.
- private readonly log: Log;
+ // The container for the cards within the modal dialog.
+ private cardContainer: HTMLDivElement | null = null;
- // The locale that the UI should adopt.
- public readonly locale: Locale;
+ // The container for the button that opens the audit modal dialog.
+ private buttonContainer: HTMLDivElement | null = null;
+
+ // The list of navigation elements.
+ private navigationList: HTMLUListElement | null = null;
+
+ // The width of the advert before any module changes.
+ private readonly advertWidth: number;
/**
* Constructs a new instance of View.
* @param advert element the module relates to
* @param locale the language file to use with the UI
+ * @param log
+ */
+ constructor(private readonly advert: HTMLElement, public readonly locale: ILocale, private readonly log: Log) {
+ this.advertWidth = advert.clientWidth;
+ }
+
+ /**
+ * Adds the initial CSS, javascript, and the container div for the UI elements.
*/
- constructor(advert: HTMLElement, locale: Locale, log: Log) {
- this.advert = advert;
- this.log = log;
+ public initView() {
+ // Create an outer container to add the shadow root and UI components to.
+ this.outerContainer = this.advert.appendChild(document.createElement('div'));
- // Setup the locale with the text and images to use.
- this.locale = locale;
- this.locale.Logo = logoSvg;
+ // Create the CSS style element.
+ const style = document.createElement('style');
+ // TODO: Fix CSS include to remove the magic character at the beginning of the CSS file.
+ style.innerHTML = (css).trim();
+
+ // Create the new container for the button and add the HTML.
+ this.buttonContainer = document.createElement('div');
+ this.buttonContainer.classList.add('ok-ui');
+ this.buttonContainer.innerHTML = buttonTemplate(this.locale);
+
+ // If the pop up is valid then append the container and store a reference to the pop up element.
+ this.root = this.outerContainer.attachShadow({ mode: 'closed' });
+ this.root.appendChild(style);
+ this.root.appendChild(this.buttonContainer);
}
/**
- * Displays the audit log card ready for the providers to be added.
+ * Displays the audit log card ready for the providers to be added. If the advert is wide then switch to landscape
+ * mode to display the advert card if present.
*/
public display(card: string) {
- this.setContainerCard(card);
+ this.initPopUp();
+ this.setCard(card);
+ this.setNavigation(card);
+ this.popupContainer.classList.add('ok-ui-popup--open');
+
+ // If this is the advert card and the width of the advert requires landscape layout then add the class name to the
+ // card content.
+ if (card === 'advert' && this.advertWidth > 320) {
+ const wrappers = this.cardContainer.getElementsByClassName('ok-ui-advert-wrapper');
+ for (let i = 0; i < wrappers.length; i++) {
+ (wrappers[i]).classList.add('ok-ui-advert-wrapper--landscape');
+ }
+ const images = this.cardContainer.getElementsByClassName('ok-ui-advert__image');
+ for (let i = 0; i < images.length; i++) {
+ (images[i]).style.width = `${this.advertWidth}px`;
+ }
+ }
+ }
+
+ /**
+ * Hides the audit log view container.
+ */
+ public hide() {
+ this.initPopUp();
+ this.popupContainer.classList.remove('ok-ui-popup--open');
}
/**
@@ -55,18 +108,34 @@ export class View implements IView {
* @returns array of HTMLElements that can have events added to them
*/
public getActionElements(): HTMLElement[] {
+ this.initPopUp();
const elements: HTMLElement[] = [];
- View.addElements(elements, this.auditContainer.getElementsByTagName('button'));
- View.addElements(elements, this.auditContainer.getElementsByTagName('a'));
+ View.addElements(elements, this.root.querySelectorAll('button'));
+ View.addElements(elements, this.root.querySelectorAll('a'));
+ View.addElements(elements, this.root.querySelectorAll('li.ok-ui-navigation__item'));
return elements;
}
+ /**
+ * Modifies the view temporarily to trigger the download of the audit log.
+ * @param model with the audit log to download.
+ */
+ public download(model: Model): void {
+ this.initPopUp();
+ const downloadAnchor = document.createElement('a');
+ downloadAnchor.setAttribute('href', 'data:text/json;charset=utf-8,' + encodeURIComponent(model.jsonContent));
+ downloadAnchor.setAttribute('download', model.jsonFileName);
+ this.innerContainer.appendChild(downloadAnchor); // required for firefox
+ downloadAnchor.click();
+ downloadAnchor.remove();
+ }
+
/**
* Adds element from the other collection to the array.
* @param array
* @param other
*/
- private static addElements(array: HTMLElement[], other: HTMLCollectionOf) {
+ private static addElements(array: HTMLElement[], other: NodeListOf) {
for (let i = 0; i < other.length; i++) {
array.push(other[i]);
}
@@ -75,51 +144,92 @@ export class View implements IView {
/**
* Sets the HTML in the container for the template.
*/
- private setContainerCard(card: string): void {
+ private setCard(card: string): void {
let template: Language;
switch (card) {
- case 'audit':
- template = auditTemplate;
+ case 'advert':
+ template = advertTemplate;
break;
- case 'button':
- template = buttonTemplate;
+ case 'data':
+ template = dataTemplate;
+ break;
+ case 'download':
+ template = downloadTemplate;
+ break;
+ case 'participants':
+ template = participantsTemplate;
break;
default:
throw `Card '${card}' is not known`;
}
- this.getContainer().innerHTML = template(this.locale);
+ this.cardContainer.innerHTML = template(this.locale).replace('[BrandName]', this.locale.brandName);
}
/**
- * Returns the container for the entire UI adding it if it does not already exist.
- * @returns
+ * Sets the navigation to the provided card.
+ * @remarks
+ * @param card that
*/
- private getContainer(): HTMLDivElement {
- if (this.auditContainer === null) {
- this.addContainer();
+ private setNavigation(card: string) {
+ const selectedClass = 'ok-ui-navigation__item--selected';
+ for (let index = 0; index < this.navigationList.children.length; index++) {
+ const child = this.navigationList.children[index];
+ if (child instanceof HTMLLIElement) {
+ if (child.getAttribute('data-card') === card) {
+ child.classList.add(selectedClass);
+ } else {
+ child.classList.remove(selectedClass);
+ }
+ }
}
- return this.auditContainer;
}
/**
- * Adds the CSS, javascript, and the container div for the UI elements.
+ * Ensures the popup has been initialized.
*/
- private addContainer() {
- // Create an outer container to add the shadow root and UI components to.
- this.outerContainer = this.advert.appendChild(document.createElement('div'));
+ private initPopUp() {
+ if (this.innerContainer === null) {
+ this.addPopUp();
+ }
+ }
- // Create the CSS style element.
- const style = document.createElement('style');
- // TODO: Fix CSS include to remove the magic character at the beginning of the CSS file.
- style.innerHTML = (css).trim();
+ /**
+ * Adds the popup container and sub containers to the DOM under the root.
+ */
+ private addPopUp() {
+ // Create the new container with the audit pop up template.
+ this.innerContainer = document.createElement('div');
+ this.innerContainer.classList.add('ok-ui');
+ this.innerContainer.innerHTML = auditTemplate(this.locale);
+
+ // Add the logos provided at construction to the controller to the header of the pop up.
+ if (this.locale.logoUrls) {
+ const logos = this.innerContainer.getElementsByClassName('ok-ui-card__header-logos');
+ for (let i = 0; i < logos.length; i++) {
+ (this.locale.logoUrls).forEach((u) => {
+ // TODO: Update pattern library to avoid the need to style the li element.
+ logos[i].innerHTML += `
`;
+ });
+ }
+ }
+ this.root.appendChild(this.innerContainer);
- // Create the new container with the pop up template.
- this.auditContainer = document.createElement('div');
- this.auditContainer.classList.add('ok-ui');
+ // Find the card body from the audit container which is where the cards will be set.
+ this.cardContainer = this.getAuditElementByClassName('ok-ui-card__body');
- // If the pop up is valid then append the container and store a reference to the pop up element.
- this.root = this.outerContainer.attachShadow({ mode: 'closed' });
- this.root.appendChild(style);
- this.root.appendChild(this.auditContainer);
+ // Find the navigation element within the audit container.
+ this.navigationList = this.getAuditElementByClassName('ok-ui-navigation__list');
+
+ // Find the popup container that is used to open and close the container.
+ this.popupContainer = this.getAuditElementByClassName('ok-ui-popup');
+ }
+
+ private getAuditElementByClassName(className: string): Element {
+ const elements = this.innerContainer.getElementsByClassName(className);
+ if (elements !== null && elements.length > 0) {
+ return elements[0];
+ } else {
+ throw `No element with class '${className}' could be found`;
+ }
}
}
diff --git a/paf-mvp-audit/tests/helpers/audit-log-mock.ts b/paf-mvp-audit/tests/helpers/audit-log-mock.ts
new file mode 100644
index 00000000..d0e62f8e
--- /dev/null
+++ b/paf-mvp-audit/tests/helpers/audit-log-mock.ts
@@ -0,0 +1,502 @@
+import { IdentifierDefinition } from '@core/crypto';
+import { PublicKeyInfo } from '@core/crypto/identity';
+import { PublicKeyStore } from '@core/crypto/key-store';
+import { privateKeyFromString } from '@core/crypto/keys';
+import { Signer } from '@core/crypto/signer';
+import { Config } from '@core/express';
+import { Log } from '@core/log';
+import {
+ AuditLog,
+ GetIdentityResponse,
+ IdBuilder,
+ Identifier,
+ IdsAndPreferences,
+ PreferencesData,
+ Seed,
+ Timestamp,
+ TransmissionContents,
+ TransmissionDetails,
+ TransmissionResponse,
+ TransmissionStatus,
+} from '@core/model';
+import { CurrentModelVersion } from '@core/model/model';
+import { getTimeStampInSec } from '@core/timestamp';
+import { OperatorClient } from '@operator-client/operator-client';
+import { v4 as uuidv4 } from 'uuid';
+import { IdentityResolverMap } from '../../src/identity-resolver';
+import { SeedDefinition, SignedSeedSignatureContainer } from '../../src/signing-definitions';
+import ECDSA from 'ecdsa-secp256r1';
+import { IECDSA } from 'ecdsa-secp256r1';
+
+/**
+ * See the documentation for logic details.
+ * https://github.com/prebid/addressability-framework/blob/main/mvp-spec/ad-auction.md
+ */
+
+/**
+ * Parameters passed to the method that builds a new configuration.
+ */
+export interface ConfigParams {
+ name: string; // Human readable name of the organization
+ host: string; // Domain name of the node
+ type: 'vendor' | 'operator'; // Type of the node
+ dpoEmailAddress?: string; // Optional email address. If not provided dpo@[host] will be used
+ privacyPolicyUrl?: URL; // Optional privacy url. If not provided https://[host]/privacy.html will be used
+}
+
+export interface OpenRTBNode extends ConfigParams {
+ children: OpenRTBNode[]; // The children of this node if any
+}
+
+export const AutomobileExample = {
+ name: 'automobiles',
+ host: 'automobiles.example',
+ type: 'operator',
+ children: [
+ {
+ name: 'United States',
+ host: 'united-states.example',
+ type: 'vendor',
+ children: [
+ {
+ name: 'Ford',
+ host: 'ford.example',
+ type: 'vendor',
+ children: [
+ {
+ name: 'Model T',
+ host: 'model-t.example',
+ type: 'vendor',
+ },
+ {
+ name: 'Mustang',
+ host: 'mustang.example',
+ type: 'vendor',
+ },
+ {
+ name: 'Model 18',
+ host: 'model-18.example',
+ type: 'vendor',
+ },
+ ],
+ },
+ {
+ name: 'Duesenberg',
+ host: 'duesenberg.example',
+ type: 'vendor',
+ children: [
+ {
+ name: 'Model SJ',
+ host: 'model-sj.example',
+ type: 'vendor',
+ },
+ ],
+ },
+ {
+ name: 'Jeep',
+ host: 'jeep.example',
+ type: 'vendor',
+ children: [
+ {
+ name: 'MD',
+ host: 'mb.example',
+ type: 'vendor',
+ },
+ ],
+ },
+ {
+ name: 'Oldsmobile',
+ host: 'oldsmobile.example',
+ type: 'vendor',
+ children: [
+ {
+ name: '"Rocket 88"',
+ host: 'rocket-88.example',
+ type: 'vendor',
+ },
+ ],
+ },
+ {
+ name: 'Chevrolet',
+ host: 'chevrolet.example',
+ type: 'vendor',
+ children: [
+ {
+ name: 'Corvette',
+ host: 'corvette.example',
+ type: 'vendor',
+ },
+ ],
+ },
+ {
+ name: 'Chrysler',
+ host: 'Chrysler.example',
+ type: 'vendor',
+ children: [
+ {
+ name: 'Minivan',
+ host: 'minivan.example',
+ type: 'vendor',
+ },
+ ],
+ },
+ {
+ name: 'Tesla',
+ host: 'tesla.example',
+ type: 'vendor',
+ children: [
+ {
+ name: 'Model S',
+ host: 'model-s.example',
+ type: 'vendor',
+ },
+ {
+ name: 'Model X',
+ host: 'model-x.example',
+ type: 'vendor',
+ },
+ {
+ name: 'Model 3',
+ host: 'model-3.example',
+ type: 'vendor',
+ },
+ {
+ name: 'Model Y',
+ host: 'model-y.example',
+ type: 'vendor',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
+
+/**
+ * Used to build a mock audit log for testing and demo purposes.
+ */
+export class AuditLogMock {
+ /**
+ * Used to sign the transmission responses against the seed.
+ */
+ private static readonly seedDefinition = new SeedDefinition();
+
+ /**
+ * Public key store used for validation.
+ */
+ private readonly publicKeyStore = new PublicKeyStore();
+
+ /**
+ * Store of client and configuration information across the mock.
+ */
+ private readonly clientAndConfigStore: ConfigAndClientStore;
+
+ /**
+ * Constructs a new instance of the mock audit log.
+ * @param log
+ */
+ constructor(private readonly log: Log) {
+ this.clientAndConfigStore = new ConfigAndClientStore(log, this);
+ }
+
+ /**
+ *
+ * @returns an identity resolver that can be used for demos and testing
+ */
+ public GetIdentityResolverMap(): IdentityResolverMap {
+ return this.clientAndConfigStore.GetIdentityResolverMap();
+ }
+
+ /**
+ * Builds the config and the client in a single operation for the parameters provided.
+ * @param params instance of new client parameters
+ * @returns
+ */
+ public BuildConfigAndClient(params: ConfigParams): ConfigAndClient {
+ const config = AuditLogMock.BuildConfig(params);
+ const client = this.BuildOperatorClient(config);
+ return { config, client, params };
+ }
+
+ /**
+ * Builds a mock audit log for testing and demo purposes.
+ * @param rootNode for the tree of participants
+ * @param data preference data value for personalized marketing
+ * @returns a valid audit log
+ */
+ public BuildAuditLog(rootNode: OpenRTBNode, data: PreferencesData): AuditLog {
+ // Use a single content and transaction id for a mock audit log.
+ const contents = [{ content_id: uuidv4(), transaction_id: uuidv4() }];
+
+ // Get the root client and config.
+ const root = this.clientAndConfigStore.getOrAdd(rootNode);
+
+ // Build the ids and preferences.
+ const id = AuditLogMock.NewId(root.config);
+ const idsAndPreferences = AuditLogMock.SignIdentifiersAndPreferencesData(root.client, [id], data);
+
+ // Build the seed for the audit log from the transaction ids, and ids and preferences.
+ const seed = AuditLogMock.BuildSeed(root.client, [contents[0].transaction_id], idsAndPreferences);
+
+ // Get the responses for all the children of the root node.
+ const responseTrees: TransmissionResponse[] = [];
+ for (let i = 0; i < rootNode.children.length; i++) {
+ responseTrees.push(
+ this.BuildTransmissionResponses(idsAndPreferences, seed, contents, rootNode.children[i], rootNode)
+ );
+ }
+
+ // Turn them into a list.
+ // TODO: Remove when the audit log can support a hierarchy of responses.
+ const responseList: TransmissionResponse[] = [];
+ responseTrees.forEach((current) => this.AddToList(responseList, current));
+
+ // Finally build the audit log.
+ return {
+ version: CurrentModelVersion,
+ data: idsAndPreferences,
+ seed,
+ transaction_id: contents[0].transaction_id,
+ transmissions: responseList,
+ };
+ }
+
+ /**
+ *
+ * @param list that all the response results will be added to
+ * @param current the current node being added
+ * @returns
+ */
+ private AddToList(list: TransmissionResponse[], current: TransmissionResponse): TransmissionResponse[] {
+ list.push(current);
+ if (current.children?.length > 0) {
+ current.children.forEach((child) => this.AddToList(list, child));
+ }
+ return list;
+ }
+
+ /**
+ * Builds a transmission response for the current node and its parent.
+ * @param idsAndPreferences
+ * @param seed
+ * @param contents
+ * @param currentNode
+ * @param parentNode
+ * @returns
+ */
+ private BuildTransmissionResponses(
+ idsAndPreferences: IdsAndPreferences,
+ seed: Seed,
+ contents: TransmissionContents,
+ currentNode: OpenRTBNode,
+ parentNode: OpenRTBNode
+ ): TransmissionResponse {
+ const children: TransmissionResponse[] = [];
+ if (currentNode.children?.length > 0) {
+ for (let i = 0; i < currentNode.children.length; i++) {
+ children.push(
+ this.BuildTransmissionResponses(idsAndPreferences, seed, contents, currentNode.children[i], currentNode)
+ );
+ }
+ }
+ return AuditLogMock.BuildTransmissionResponse(
+ idsAndPreferences,
+ seed,
+ currentNode?.children?.length === 0
+ ? contents
+ : [
+ {
+ transaction_id: contents[0].transaction_id,
+ content_id: null,
+ },
+ ],
+ this.clientAndConfigStore.getOrAdd(currentNode),
+ this.clientAndConfigStore.getOrAdd(parentNode),
+ 'success',
+ 'mocked',
+ children
+ );
+ }
+
+ /**
+ * Creates a transmission response for inclusion in the audit log.
+ * @param idsAndPreferences
+ * @param seed
+ * @param contents ids
+ * @param sender used to provide the details
+ * @param receiver used to sign the transmission response
+ * @param status string
+ * @param details string
+ * @param children responses if any
+ * @returns a transmission response
+ */
+ private static BuildTransmissionResponse(
+ idsAndPreferences: IdsAndPreferences,
+ seed: Seed,
+ contents: TransmissionContents,
+ sender: ConfigAndClient,
+ receiver: ConfigAndClient,
+ status: TransmissionStatus,
+ details: TransmissionDetails,
+ children: TransmissionResponse[]
+ ): TransmissionResponse {
+ const currentPrivateKey = privateKeyFromString(sender.config.currentPrivateKey);
+ const signer = new Signer(currentPrivateKey, AuditLogMock.seedDefinition);
+ const response = {
+ version: CurrentModelVersion,
+ receiver: receiver.config.host,
+ contents,
+ status,
+ details,
+ source: {
+ timestamp: getTimeStampInSec(),
+ domain: sender.config.host,
+ signature: signer.sign({ idsAndPreferences, seed }),
+ },
+ children,
+ };
+ return response;
+ }
+
+ /**
+ * Builds a new seed from the client for the transaction ids, identifiers and preferences provided.
+ * @param client
+ * @param transaction_ids
+ * @param idsAndPreferences
+ * @returns
+ */
+ private static BuildSeed(
+ client: OperatorClient,
+ transaction_ids: string[],
+ idsAndPreferences: IdsAndPreferences
+ ): Seed {
+ const seed = client.buildSeed(transaction_ids, idsAndPreferences);
+ return seed;
+ }
+
+ /**
+ * Signs the preferences with the identifiers.
+ * @param client to perform the signing
+ * @param identifiers to combine with the preferences
+ * @param data boolean value for personalized marketing
+ * @returns signed identifiers and preferences
+ */
+ private static SignIdentifiersAndPreferencesData(
+ client: OperatorClient,
+ identifiers: Identifier[],
+ data: PreferencesData
+ ): IdsAndPreferences {
+ const preferences = client.buildPreferences(identifiers, data);
+ return { identifiers, preferences };
+ }
+
+ /**
+ * Creates a new Id for the configuration provided.
+ * @param config
+ * @returns
+ */
+ private static NewId(config: Config): Identifier {
+ const privateKey = privateKeyFromString(config.currentPrivateKey);
+ const signer = new Signer(privateKey, new IdentifierDefinition());
+ return new IdBuilder(config.host, config.currentPrivateKey, signer).generateNewId();
+ }
+
+ /**
+ * Builds a new operator client for the config provided.
+ * @param config
+ */
+ private BuildOperatorClient(config: Config): OperatorClient {
+ return new OperatorClient(config.host, config.host, config.currentPrivateKey, this.publicKeyStore);
+ }
+
+ /**
+ * Builds a new client config for the parameters provided.
+ * @param params instance of new client parameters
+ */
+ private static BuildConfig(params: ConfigParams): Config {
+ const privateKey = ECDSA.generateKey();
+ const currentPrivateKey = privateKey.toPEM();
+ const publicKey = privateKey.asPublic().toPEM();
+ return {
+ identity: {
+ name: params.name,
+ publicKeys: [
+ {
+ startTimestampInSec: getTimeStampInSec(),
+ publicKey,
+ },
+ ],
+ type: params.type,
+ dpoEmailAddress: params.dpoEmailAddress ?? `dpo@${params.host}`,
+ privacyPolicyUrl: params.privacyPolicyUrl ?? new URL(`https:\\\\${params.host}\\privacy.html`),
+ },
+ host: params.host,
+ currentPrivateKey,
+ };
+ }
+}
+
+interface ConfigAndClient {
+ config: Config; // The config used to create the client
+ client: OperatorClient; // The client created from the config
+ params: ConfigParams; // The parameters that formed the config and client
+}
+
+interface GetIdentityResponsePublicKey {
+ key: string;
+ start: Timestamp;
+ end?: Timestamp;
+}
+
+class ConfigAndClientStore {
+ // Map of hosts to config and clients.
+ private map = new Map();
+
+ /**
+ *
+ * @param log
+ * @param mock instance for use with the store
+ */
+ constructor(private readonly log: Log, private readonly mock: AuditLogMock) {}
+
+ /**
+ * Returns the exist config and client if available, or adds a new one and returns that
+ * @param params to be used to find the existing data or to add if does not exist
+ * @returns a unique config and client for the parameters across the instance of the store
+ */
+ public getOrAdd(params: ConfigParams): ConfigAndClient {
+ let result = this.map.get(params.host);
+ if (result === undefined) {
+ result = this.mock.BuildConfigAndClient(params);
+ this.map.set(params.host, result);
+ }
+ return result;
+ }
+
+ public GetIdentityResolverMap(): IdentityResolverMap {
+ const map = new Map();
+ this.map.forEach((v, k) =>
+ map.set(k, {
+ version: CurrentModelVersion,
+ name: v.params.name,
+ type: v.params.type,
+ dpo_email: v.config.identity.dpoEmailAddress,
+ privacy_policy_url: v.config.identity.privacyPolicyUrl.toString(),
+ keys: ConfigAndClientStore.TransformPublicKeys(v.config.identity.publicKeys),
+ })
+ );
+ return new IdentityResolverMap(0, map);
+ }
+
+ private static TransformPublicKeys(keys: PublicKeyInfo[]): GetIdentityResponsePublicKey[] {
+ const transformed: GetIdentityResponsePublicKey[] = [];
+ keys.forEach((k) =>
+ transformed.push({
+ key: k.publicKey,
+ start: k.startTimestampInSec,
+ end: k.endTimestampInSec,
+ })
+ );
+ return transformed;
+ }
+}
diff --git a/paf-mvp-audit/tests/test-cases/model.test.ts b/paf-mvp-audit/tests/test-cases/model.test.ts
new file mode 100644
index 00000000..9bac72b4
--- /dev/null
+++ b/paf-mvp-audit/tests/test-cases/model.test.ts
@@ -0,0 +1,148 @@
+import { AutomobileExample, AuditLogMock } from '../helpers/audit-log-mock';
+import { IdentityResolverMap } from '../../src/identity-resolver';
+import { Model, OverallStatus, VerifiedStatus } from '../../src/model';
+import { AuditLog } from '@core/model';
+import { Log } from '@core/log';
+import * as fs from 'fs';
+import path from 'path';
+
+describe('testing model', () => {
+ let mock: AuditLogMock;
+ let auditLog: AuditLog;
+ let resolver: IdentityResolverMap;
+ const log = new Log('audit model test');
+
+ beforeEach(() => {
+ // Create a mock audit log ensuring that the
+ mock = new AuditLogMock(log);
+ auditLog = mock.BuildAuditLog({ ...AutomobileExample }, { use_browsing_for_personalization: true });
+
+ // Check that the audit log appears valid.
+ expect(auditLog).toBeDefined();
+ expect(auditLog.seed.publisher).toBe(AutomobileExample.host);
+ expect(auditLog.data.preferences.data.use_browsing_for_personalization).toBe(true);
+
+ // Check that the resolver from the mock works for the root.
+ resolver = mock.GetIdentityResolverMap();
+ resolver.get(AutomobileExample.host).then((i) => expect(i.name).toBe(AutomobileExample.name));
+ });
+
+ test('check identity resolution works for all nodes', async () => {
+ const model = await new Model(log, resolver, auditLog).verify();
+
+ // Check the properties for all verified fields are as expected.
+ model.allVerifiedFields.forEach((i) => expect(i.value.verifiedStatus).toBe(VerifiedStatus.Valid));
+ model.allVerifiedFields.forEach((i) => expect(i.value.valid).toBe(true));
+ model.allVerifiedFields.forEach((i) => expect(i.value.identity).toBeDefined());
+
+ // Check that the overall status is good.
+ expect(model.overall.value).toBe(OverallStatus.Good);
+
+ saveAuditLog('all-good.json', auditLog, resolver);
+ });
+
+ test("check identity resolution fails when one identity can't be found", async () => {
+ // Remove the 'minivan.example' domain from the list of domains that can be resolved.
+ const toRemove = auditLog.transmissions.find((i) => i.source.domain === 'minivan.example');
+ expect(toRemove).toBeDefined();
+ resolver.map.delete(toRemove.source.domain);
+
+ // Create the model.
+ const model = await new Model(log, resolver, auditLog).verify();
+
+ // Check the seed and ids and preferences fields are valid.
+ expect(model.seed.value.verifiedStatus).toBe(VerifiedStatus.Valid);
+ expect(model.idsAndPreferences.value.verifiedStatus).toBe(VerifiedStatus.Valid);
+
+ // Find the result for the missing domain.
+ const failed = model.results.find((i) => i.value.value.source.domain === toRemove.source.domain);
+ expect(failed).toBeDefined();
+
+ // Get the verified result and check it's identity not found.
+ expect(failed.value.verifiedStatus).toBe(VerifiedStatus.IdentityNotFound);
+
+ // Check the statuses for all the other results are valid.
+ model.results.forEach((result) => {
+ if (result.value.value.source.domain !== toRemove.source.domain) {
+ expect(result.value.verifiedStatus).toBe(VerifiedStatus.Valid);
+ }
+ });
+
+ // Check the count of identity not found status is one.
+ expect(model.count(VerifiedStatus.IdentityNotFound)).toBe(1);
+
+ // Check that the overall status is caution as the identity can't be found.
+ expect(model.overall.value).toBe(OverallStatus.Suspicious);
+
+ saveAuditLog('identity-not-found.json', auditLog, resolver);
+ });
+
+ test('check verification fails when one signature is corrupted', async () => {
+ // Corrupt the 'minivan.example' domain from the list of domains that can be resolved.
+ const toCorrupt = auditLog.transmissions.find((i) => i.source.domain === 'minivan.example');
+ toCorrupt.source.signature = '0' + toCorrupt.source.signature;
+
+ // Create the model.
+ const model = await new Model(log, resolver, auditLog).verify();
+
+ // Check the seed and ids and preferences fields are valid.
+ expect(model.seed.value.verifiedStatus).toBe(VerifiedStatus.Valid);
+ expect(model.idsAndPreferences.value.verifiedStatus).toBe(VerifiedStatus.Valid);
+
+ // Find the result for the missing domain.
+ const failed = model.results.find((i) => i.value.value.source.domain === toCorrupt.source.domain);
+ expect(failed).toBeDefined();
+
+ // Get the verified result and check it's identity not found.
+ expect(failed.value.verifiedStatus).toBe(VerifiedStatus.NotValid);
+
+ // Check the statuses for all the other results are valid.
+ model.results.forEach((result) => {
+ if (result.value.value.source.domain !== toCorrupt.source.domain) {
+ expect(result.value.verifiedStatus).toBe(VerifiedStatus.Valid);
+ }
+ });
+
+ // Check the count of not valid status is one.
+ expect(model.count(VerifiedStatus.NotValid)).toBe(1);
+
+ // Check that the overall status is bad as the signature is invalid.
+ expect(model.overall.value).toBe(OverallStatus.Violation);
+
+ saveAuditLog('corrupt-signature.json', auditLog, resolver);
+ });
+
+ test('check verification when response is not success for one result', async () => {
+ // Corrupt the 'minivan.example' in the list of results.
+ const toCorrupt = auditLog.transmissions.find((i) => i.source.domain === 'minivan.example');
+ toCorrupt.status = 'error_cannot_process';
+
+ // Create the model.
+ const model = await new Model(log, resolver, auditLog).verify();
+
+ // Check that the overall status is bad as the signature is invalid.
+ expect(model.overall.value).toBe(OverallStatus.Suspicious);
+
+ saveAuditLog('result-error.json', auditLog, resolver);
+ });
+});
+
+/**
+ * Saves the audit log and resolver in the name provided if there is a global variable 'mock-audit-log-path'.
+ * @param fileName
+ * @param auditLog
+ * @param resolver
+ */
+function saveAuditLog(fileName: string, auditLog: AuditLog, resolver: IdentityResolverMap) {
+ const mockAuditLogPath = globalThis['mock-audit-log-path'];
+ if (mockAuditLogPath) {
+ const fullPath = path.join(mockAuditLogPath, fileName);
+ fs.writeFileSync(
+ fullPath,
+ JSON.stringify({
+ auditLog: auditLog,
+ resolver: Array.from(resolver.map.entries()),
+ })
+ );
+ }
+}
diff --git a/paf-mvp-cmp/.npmignore b/paf-mvp-cmp/.npmignore
index 50a8e2a5..b78504e8 100644
--- a/paf-mvp-cmp/.npmignore
+++ b/paf-mvp-cmp/.npmignore
@@ -1,2 +1,2 @@
/src
-/test
\ No newline at end of file
+/tests
\ No newline at end of file
diff --git a/paf-mvp-cmp/README.md b/paf-mvp-cmp/README.md
index cdab30b8..192a6fbd 100644
--- a/paf-mvp-cmp/README.md
+++ b/paf-mvp-cmp/README.md
@@ -12,7 +12,7 @@ proxy-host-name domain. A list of CMP providers is available in the
page.
```HTML
-
```
@@ -127,14 +127,14 @@ particularly noticeable when HTTPS/2 is used.
For example to preload the loader the following then following would be used.
```html
-
+
```
If the server has already worked out the correct language bundle the following
would be used.
```html
-
+
```
If this link is not added, then the functionality will still be provided by page
diff --git a/paf-mvp-cmp/rollup.config.js b/paf-mvp-cmp/rollup.config.js
index 9e77d7c0..85375a3f 100644
--- a/paf-mvp-cmp/rollup.config.js
+++ b/paf-mvp-cmp/rollup.config.js
@@ -27,6 +27,9 @@ import * as yaml from 'js-yaml';
// Used to get the TCF core string from the environment.
import { env } from 'process';
+// File name prefix for the bundle files.
+const namePrefix = 'ok-ui-cmp';
+
const DEV = process.env.ROLLUP_WATCH;
// Options to pass to terser.
@@ -131,11 +134,12 @@ function getLocaleCodes() {
// Builds the loader working out from the locales directory the various options that will be available.
function buildLoader() {
+ const loader = '../paf-mvp-core-js/src/ui/loader.ts';
return {
- input: './src/loader.ts',
+ input: loader,
plugins: [
replace({
- include: './src/loader.ts',
+ include: loader,
preventAssignment: true,
__Locales__: '[' + getLocaleCodes().join(',') + ']'
}),
@@ -150,25 +154,25 @@ function buildLoader() {
treeshake: true,
output: [
{
- file: `./dist/ok-ui.js`,
- sourcemap: true,
+ file: `./dist/${namePrefix}.js`,
+ sourcemap: false,
format: 'iife'
},
{
- file: `./dist/ok-ui.min.js`,
+ file: `./dist/${namePrefix}.min.js`,
format: 'iife',
- sourcemap: true,
+ sourcemap: false,
plugins: [terser(terserOptions)]
},
{
- file: `../paf-mvp-demo-express/public/assets/cmp/ok-ui.js`,
- sourcemap: true,
+ file: `../paf-mvp-demo-express/public/assets/${namePrefix}.js`,
+ sourcemap: false,
format: 'iife'
},
{
- file: `../paf-mvp-demo-express/public/assets/cmp/ok-ui.min.js`,
+ file: `../paf-mvp-demo-express/public/assets/${namePrefix}.min.js`,
format: 'iife',
- sourcemap: true,
+ sourcemap: false,
plugins: [terser(terserOptions)]
},
]
@@ -193,10 +197,6 @@ function buildLocaleConfig(localeCode, localeContent, tcfCoreTemplate) {
preventAssignment: true,
'process.env.NODE_ENV': JSON.stringify(DEV ? 'development' : 'production')
}),
- replace({
- preventAssignment: true,
- 'process.env.NODE_ENV': JSON.stringify(DEV ? 'development' : 'production')
- }),
postHTML({ template: true }),
minifyHTML({
// Include string literals that contain the div or section element as well as the default check.
@@ -216,25 +216,25 @@ function buildLocaleConfig(localeCode, localeContent, tcfCoreTemplate) {
treeshake: true,
output: [
{
- file: `./dist/ok-ui-${localeCode}.js`,
- sourcemap: true,
+ file: `./dist/${namePrefix}-${localeCode}.js`,
+ sourcemap: false,
format: 'iife',
},
{
- file: `./dist/ok-ui-${localeCode}.min.js`,
+ file: `./dist/${namePrefix}-${localeCode}.min.js`,
format: 'iife',
- sourcemap: true,
+ sourcemap: false,
plugins: [terser(terserOptions)]
},
{
- file: `../paf-mvp-demo-express/public/assets/cmp/ok-ui-${localeCode}.js`,
- sourcemap: true,
+ file: `../paf-mvp-demo-express/public/assets/${namePrefix}-${localeCode}.js`,
+ sourcemap: false,
format: 'iife',
},
{
- file: `../paf-mvp-demo-express/public/assets/cmp/ok-ui-${localeCode}.min.js`,
+ file: `../paf-mvp-demo-express/public/assets/${namePrefix}-${localeCode}.min.js`,
format: 'iife',
- sourcemap: true,
+ sourcemap: false,
plugins: [terser(terserOptions)]
},
]
diff --git a/paf-mvp-cmp/src/bindings.ts b/paf-mvp-cmp/src/bindings.ts
index 8910b09c..a4691f36 100644
--- a/paf-mvp-cmp/src/bindings.ts
+++ b/paf-mvp-cmp/src/bindings.ts
@@ -1,7 +1,8 @@
import { Config } from './config';
import { BindingViewOnly } from '@core/ui/binding';
import { Identifier, PreferencesData } from '@core/model/generated-model';
-import { Marketing, Model } from './model';
+import { Model } from './model';
+import { Marketing } from '@core/model/marketing';
import { View } from './view';
/**
diff --git a/paf-mvp-cmp/src/controller.ts b/paf-mvp-cmp/src/controller.ts
index 55c6f333..16747aae 100644
--- a/paf-mvp-cmp/src/controller.ts
+++ b/paf-mvp-cmp/src/controller.ts
@@ -13,13 +13,14 @@ import {
updateIdsAndPreferences,
deleteIdsAndPreferences,
} from '@frontend/lib/paf-lib';
-import { Marketing, Model } from './model';
+import { Marketing } from '@core/model/marketing';
+import { Model } from './model';
import { PafStatus } from '@frontend/enums/status.enum';
import { View } from './view';
import { getCookieValue } from '@frontend/utils/cookie';
import { Cookies, getPrebidDataCacheExpiration } from '@core/cookies';
import { TcfCore } from './tcfcore';
-import { ILocale } from './ILocale';
+import { ILocale } from '@core/ui/ILocale';
/**
* Controller class used with the model and views. Uses paf-lib for data access services.
@@ -346,6 +347,7 @@ export class Controller {
this.display(card);
e.preventDefault();
});
+ element.removeAttribute('data-card');
}
const action = element.getAttribute('data-action');
if (action !== null) {
@@ -353,6 +355,7 @@ export class Controller {
this.processAction(action);
e.preventDefault();
});
+ element.removeAttribute('data-action');
}
}
}
diff --git a/paf-mvp-cmp/src/loader.ts b/paf-mvp-cmp/src/loader.ts
deleted file mode 100644
index da154887..00000000
--- a/paf-mvp-cmp/src/loader.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-// The available language codes calculated at build time.
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore this value is populated at build time in rollup.config.js.
-const available = __Locales__;
-
-// The default language to use if the users preferences are not available.
-const defaultLang = 'en-us';
-
-// Record the current script so that the language script can be inserted.
-const thisScript = document.currentScript;
-
-// The base URL that should be used to find the language specific script.
-const baseUrl = thisScript.getAttribute('src');
-
-/**
- * Gets the URL for the language code provided.
- * @param language required
- * @returns URL for the language
- */
-const getUrlForLanguage = (language: string) => baseUrl.replace('ok-ui', `ok-ui-${language}`);
-
-/**
- * Insert the JavaScript at the provided URL into the document next to this loader script copying all the configuration
- * from the loader to the language specific script.
- * @param url
- */
-const insertScript = (url: string) => {
- const script = document.createElement('script');
- const attrNames = thisScript.getAttributeNames();
- for (let i = 0; i < attrNames.length; i++) {
- const qn = attrNames[i];
- script.setAttribute(qn, thisScript.getAttribute(qn));
- }
- script.setAttribute('src', url);
- thisScript.insertAdjacentElement('afterend', script);
-};
-
-/**
- * Checks if there is a bundle for the next language in the list. If so then this is inserted into the document,
- * otherwise if there are more languages remaining then these are loaded. If there are no more languages then use the
- * default.
- */
-const insertLanguage = () => {
- for (let i = 0; i < window.navigator.languages.length; i++) {
- const language = window.navigator.languages[i].toLowerCase();
- if (available.indexOf(language) >= 0) {
- insertScript(getUrlForLanguage(language));
- return;
- }
- }
- insertScript(getUrlForLanguage(defaultLang));
-};
-
-// Find the best language and insert it after this script.
-insertLanguage();
diff --git a/paf-mvp-cmp/src/model.ts b/paf-mvp-cmp/src/model.ts
index 9cf5a990..7a6468f8 100644
--- a/paf-mvp-cmp/src/model.ts
+++ b/paf-mvp-cmp/src/model.ts
@@ -8,46 +8,7 @@ import {
Source,
Version,
} from '@core/model/generated-model';
-
-/**
- * The different states for the marketing preferences field.
- * Can't be an enum as we need the values for the PreferencesData to be comparable.
- */
-
-export class Marketing {
- /**
- * Personalized marketing.
- */
- public static readonly personalized: PreferencesData = {
- use_browsing_for_personalization: true,
- };
-
- /**
- * Standard marketing.
- */
- public static readonly standard: PreferencesData = {
- use_browsing_for_personalization: false,
- };
-
- /**
- * Customized marketing where there is no data held in this field.
- */
- public static readonly custom: PreferencesData = null;
-
- /**
- * No marketing is yet defined. The user has not made a choice.
- */
- public static readonly notSet: PreferencesData = undefined;
-
- /**
- * Determines equality of two different instances of PreferencesData.
- * @remarks
- * Uses the JSON.stringify to compare by value.
- */
- public static equals(a: PreferencesData, b: PreferencesData) {
- return JSON.stringify(a) === JSON.stringify(b);
- }
-}
+import { Marketing } from '@core/model/marketing';
/**
* The source of the data that was used to populate the model.
diff --git a/paf-mvp-cmp/src/view.ts b/paf-mvp-cmp/src/view.ts
index f7fc8749..42f677b6 100644
--- a/paf-mvp-cmp/src/view.ts
+++ b/paf-mvp-cmp/src/view.ts
@@ -1,6 +1,5 @@
/**
* Resources used by the controller for HTML views and CSS.
- * TODO: fix the warning associated with can't find module or type.
*/
import css from './css/ok-ui.css';
import introTemplate from './html/cards/intro.html';
@@ -12,7 +11,7 @@ import snackbarTemplate from './html/cards/snackbar.html';
import popupTemplate from './html/containers/popup.html';
import { Config } from './config';
import { IView } from '@core/ui/binding';
-import { ILocale } from './ILocale';
+import { ILocale } from '@core/ui/ILocale';
import { Tooltip } from './tooltip';
export class View implements IView {
diff --git a/paf-mvp-cmp/tests/test-cases/model.test.ts b/paf-mvp-cmp/tests/test-cases/model.test.ts
index f09827c0..f60d325b 100644
--- a/paf-mvp-cmp/tests/test-cases/model.test.ts
+++ b/paf-mvp-cmp/tests/test-cases/model.test.ts
@@ -1,5 +1,6 @@
-import { FieldSingle, Marketing, Model } from '../../src/model';
+import { FieldSingle, Model } from '../../src/model';
import { Identifier, Preferences, PreferencesData } from '@core/model/generated-model';
+import { Marketing } from '@core/model/marketing';
let model: Model;
diff --git a/paf-mvp-core-js/@types/ec-key/index.d.ts b/paf-mvp-core-js/@types/ec-key/index.d.ts
index 87c88da5..de559adb 100644
--- a/paf-mvp-core-js/@types/ec-key/index.d.ts
+++ b/paf-mvp-core-js/@types/ec-key/index.d.ts
@@ -1,6 +1,12 @@
declare module 'ec-key' {
- declare class ECKey {
+ /**
+ * The only functionality used from the ec-key module is the conversion from PEM format keys to JWK. This type
+ * declaration is only concerned with this transformation.
+ */
+ export declare class ECKey {
constructor(public key: string) {}
+ toJSON: () => ECKey; // This is the only method used from ec-key. It converts PEM to JWK.
}
+ export const ECKey: ECKey = {};
export default ECKey;
}
diff --git a/paf-mvp-core-js/@types/ecdsa-secp256r1/index.d.ts b/paf-mvp-core-js/@types/ecdsa-secp256r1/index.d.ts
index 842112e3..468eda94 100644
--- a/paf-mvp-core-js/@types/ecdsa-secp256r1/index.d.ts
+++ b/paf-mvp-core-js/@types/ecdsa-secp256r1/index.d.ts
@@ -1,5 +1,10 @@
declare module 'ecdsa-secp256r1' {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- declare const ECDSA: any = {};
+ export declare interface IECDSA {
+ verify: (string, string) => boolean | Promise;
+ sign: (string) => string;
+ fromJWK: (ECKey) => IECDSA | Promise;
+ generateKey: () => IECDSA;
+ }
+ declare const ECDSA: IECDSA = {};
export default ECDSA;
}
diff --git a/paf-mvp-core-js/package.json b/paf-mvp-core-js/package.json
index 29ff4e6b..d89bc576 100644
--- a/paf-mvp-core-js/package.json
+++ b/paf-mvp-core-js/package.json
@@ -29,7 +29,6 @@
"cors": "^2.8.5",
"detect-browser": "^5.3.0",
"ec-key": "^0.0.4",
- "ecdsa-secp256r1": "^1.3.3",
"json-schema-to-ts": "^1.6.5",
"mock-fs": "^5.1.2",
"tld-extract": "^2.0.1",
diff --git a/paf-mvp-core-js/src/crypto/key-store.ts b/paf-mvp-core-js/src/crypto/key-store.ts
index 7154dbcd..27e496c7 100644
--- a/paf-mvp-core-js/src/crypto/key-store.ts
+++ b/paf-mvp-core-js/src/crypto/key-store.ts
@@ -1,11 +1,12 @@
import { fromIdentityResponse, PublicKeyInfo } from './identity';
-import { isValidKey, PublicKey, publicKeyFromString } from './keys';
+import { isValidKey, publicKeyFromString } from './keys';
import { GetIdentityRequestBuilder } from '@core/model/identity-request-builder';
import { GetIdentityResponse, Timestamp } from '@core/model/generated-model';
import { getTimeStampInSec } from '@core/timestamp';
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
+import { IECDSA } from 'ecdsa-secp256r1';
-export type PublicKeyWithObject = PublicKeyInfo & { publicKeyObj: PublicKey };
+export type PublicKeyWithObject = PublicKeyInfo & { publicKeyObj: IECDSA };
export class PublicKeyStore {
protected cache: { [domain: string]: PublicKeyWithObject } = {};
@@ -53,9 +54,10 @@ export class PublicKeyStore {
}
// Update cache
+ const fromIdentity = fromIdentityResponse(currentKey);
const keyInfo = {
- ...fromIdentityResponse(currentKey),
- publicKeyObj: publicKeyFromString(fromIdentityResponse(currentKey).publicKey),
+ ...fromIdentity,
+ publicKeyObj: await publicKeyFromString(fromIdentity.publicKey),
};
this.cache[domain] = keyInfo;
diff --git a/paf-mvp-core-js/src/crypto/keys.ts b/paf-mvp-core-js/src/crypto/keys.ts
index bb6f3e2e..2cd29901 100644
--- a/paf-mvp-core-js/src/crypto/keys.ts
+++ b/paf-mvp-core-js/src/crypto/keys.ts
@@ -1,23 +1,28 @@
import ECDSA from 'ecdsa-secp256r1';
-import ECKey from 'ec-key';
+import { IECDSA } from 'ecdsa-secp256r1';
+import ECKey from 'ec-key'; // Used to convert PEM keys to JWK format for use with ECDSA.
import { Timestamp } from '@core/model/generated-model';
import { getTimeStampInSec } from '@core/timestamp';
-// Not provided by ecdsa-secp256r1 unfortunately
-export interface PrivateKey {
- sign: (toSign: string) => string;
-}
-
-export interface PublicKey {
- verify: (toVerify: string, signature: string) => boolean;
-}
-
-export interface PublicKeys {
- [host: string]: PublicKey;
-}
+/**
+ * Needs to support promises for usage in the browser audit module.
+ * @param pem format public key
+ * @returns Promise for an IECDSA instance to use for verification
+ */
+export const publicKeyFromString = (pem: string): Promise => {
+ const result = ECDSA.fromJWK(new ECKey(pem).toJSON());
+ if (result instanceof Promise) {
+ return result;
+ }
+ return Promise.resolve(result);
+};
-export const publicKeyFromString = (keyString: string): PublicKey => ECDSA.fromJWK(new ECKey(keyString));
-export const privateKeyFromString = (keyString: string): PrivateKey => ECDSA.fromJWK(new ECKey(keyString));
+/**
+ * Only used in Node so no need to support promises.
+ * @param pem format private key
+ * @returns IECDSA instance ready for signing
+ */
+export const privateKeyFromString = (pem: string): IECDSA => ECDSA.fromJWK(new ECKey(pem).toJSON());
/**
* Return true if this key is valid according to start and end dates
diff --git a/paf-mvp-core-js/src/crypto/signer.ts b/paf-mvp-core-js/src/crypto/signer.ts
index 2211bb8d..0e726960 100644
--- a/paf-mvp-core-js/src/crypto/signer.ts
+++ b/paf-mvp-core-js/src/crypto/signer.ts
@@ -1,5 +1,5 @@
-import { PrivateKey } from '@core/crypto/keys';
import { Log } from '@core/log';
+import { IECDSA } from 'ecdsa-secp256r1';
export interface SignatureStringBuilder {
/**
@@ -24,11 +24,21 @@ export class Signer implements ISigner {
* @param ecdsaPrivateKey the private key that will be used to sign
* @param definition defines how to get input string for signing
*/
- constructor(private ecdsaPrivateKey: PrivateKey, protected definition: SignatureStringBuilder) {}
+ constructor(private ecdsaPrivateKey: IECDSA, protected definition: SignatureStringBuilder) {}
sign(inputData: U): string {
this.logger.Debug('Sign', inputData);
const toSign = this.definition.getInputString(inputData);
- return this.ecdsaPrivateKey.sign(toSign);
+
+ // TODO: There is a failure in the underlying crypto implementation that can return signatures that will
+ // subsequently fail validation. The change to resign the signature can be removed when the underlying bug is
+ // resolved.
+ let signature = this.ecdsaPrivateKey.sign(toSign);
+ if (this.ecdsaPrivateKey.verify) {
+ while (this.ecdsaPrivateKey.verify(toSign, signature) === false) {
+ signature = this.ecdsaPrivateKey.sign(toSign);
+ }
+ }
+ return signature;
}
}
diff --git a/paf-mvp-core-js/src/crypto/verifier.ts b/paf-mvp-core-js/src/crypto/verifier.ts
index e28fd246..833b5e37 100644
--- a/paf-mvp-core-js/src/crypto/verifier.ts
+++ b/paf-mvp-core-js/src/crypto/verifier.ts
@@ -1,4 +1,3 @@
-import { PublicKey } from '@core/crypto/keys';
import {
IdentifierDefinition,
IdsAndPreferencesDefinition,
@@ -10,9 +9,10 @@ import { Identifier, IdsAndPreferences, MessageBase } from '@core/model/generate
import { getTimeStampInSec } from '@core/timestamp';
import { Unsigned } from '@core/model/model';
import { Log } from '@core/log';
+import { IECDSA } from 'ecdsa-secp256r1';
export interface PublicKeyProvider {
- (domain: string): Promise;
+ (domain: string): Promise;
}
/**
@@ -34,7 +34,8 @@ export class Verifier {
const signature = this.definition.getSignature(signedData);
const toVerify = this.definition.getInputString(signedData);
- const result = publicKey.verify(toVerify, signature);
+ const verifyResult = publicKey.verify(toVerify, signature);
+ const result = verifyResult instanceof Promise ? await verifyResult : verifyResult;
if (result) this.logger.Debug('Verified', signedData);
else this.logger.Error('Verification failed for', signedData);
diff --git a/paf-mvp-core-js/src/model/marketing.ts b/paf-mvp-core-js/src/model/marketing.ts
new file mode 100644
index 00000000..a9db6631
--- /dev/null
+++ b/paf-mvp-core-js/src/model/marketing.ts
@@ -0,0 +1,41 @@
+import { PreferencesData } from './generated-model';
+
+/**
+ * The different states for the marketing preferences field.
+ * Can't be an enum as we need the values for the PreferencesData to be comparable.
+ */
+
+export class Marketing {
+ /**
+ * Personalized marketing.
+ */
+ public static readonly personalized: PreferencesData = {
+ use_browsing_for_personalization: true,
+ };
+
+ /**
+ * Standard marketing.
+ */
+ public static readonly standard: PreferencesData = {
+ use_browsing_for_personalization: false,
+ };
+
+ /**
+ * Customized marketing where there is no data held in this field.
+ */
+ public static readonly custom: PreferencesData = null;
+
+ /**
+ * No marketing is yet defined. The user has not made a choice.
+ */
+ public static readonly notSet: PreferencesData = undefined;
+
+ /**
+ * Determines equality of two different instances of PreferencesData.
+ * @remarks
+ * Uses the JSON.stringify to compare by value.
+ */
+ public static equals(a: PreferencesData, b: PreferencesData) {
+ return JSON.stringify(a) === JSON.stringify(b);
+ }
+}
diff --git a/paf-mvp-cmp/src/ILocale.ts b/paf-mvp-core-js/src/ui/ILocale.ts
similarity index 100%
rename from paf-mvp-cmp/src/ILocale.ts
rename to paf-mvp-core-js/src/ui/ILocale.ts
diff --git a/paf-mvp-core-js/src/ui/binding.ts b/paf-mvp-core-js/src/ui/binding.ts
index 3727f423..71e802cc 100644
--- a/paf-mvp-core-js/src/ui/binding.ts
+++ b/paf-mvp-core-js/src/ui/binding.ts
@@ -52,7 +52,7 @@ export abstract class BindingBase {
/**
* The view that will contain the element with the id.
*/
- private readonly view: IView;
+ protected readonly view: IView;
/**
* Id of the HTML elements that the field is bound to.
@@ -266,7 +266,7 @@ export class BindingElement
return element;
}
- private getString(key: T): string | null {
+ protected getString(key: T): string | null {
const keyJSON = JSON.stringify(key);
for (const item of this.pairs) {
if (JSON.stringify(item[0]) === keyJSON) {
diff --git a/paf-mvp-core-js/src/ui/loader.ts b/paf-mvp-core-js/src/ui/loader.ts
new file mode 100644
index 00000000..dca07adc
--- /dev/null
+++ b/paf-mvp-core-js/src/ui/loader.ts
@@ -0,0 +1,73 @@
+class Loader {
+ // The available language codes calculated at build time.
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore this value is populated at build time in rollup.config.js.
+ private readonly available = __Locales__;
+
+ // The default language to use if the users preferences are not available.
+ private readonly defaultLang = 'en-us';
+
+ // Record the current script so that the language script can be inserted.
+ private readonly currentScript: HTMLOrSVGScriptElement;
+
+ // The base URL that should be used to find the language specific script.
+ private readonly baseUrl: string;
+
+ // The first dot in the file name.
+ private readonly dot: number;
+
+ /**
+ * Constructs a new instance of the loader.
+ */
+ constructor(currentScript: HTMLOrSVGScriptElement) {
+ this.currentScript = currentScript;
+ this.baseUrl = this.currentScript.getAttribute('src');
+ const lastSlash = this.baseUrl.lastIndexOf('/');
+ this.dot = this.baseUrl.indexOf('.', lastSlash);
+ }
+
+ /**
+ * Gets the URL for the language code provided.
+ * @param language required
+ * @returns URL for the language
+ */
+ private getUrlForLanguage(language: string): string {
+ return this.baseUrl.substring(0, this.dot) + '-' + language + this.baseUrl.substring(this.dot);
+ }
+
+ /**
+ * Insert the JavaScript at the provided URL into the document next to this loader script copying all the configuration
+ * from the loader to the language specific script.
+ * @param url
+ */
+ private insertScript(url: string) {
+ const script = document.createElement('script');
+ const attrNames = this.currentScript.getAttributeNames();
+ for (let i = 0; i < attrNames.length; i++) {
+ const qn = attrNames[i];
+ script.setAttribute(qn, this.currentScript.getAttribute(qn));
+ }
+ script.setAttribute('src', url);
+ this.currentScript.insertAdjacentElement('afterend', script);
+ }
+
+ /**
+ * Checks if there is a bundle for the next language in the list. If so then this is inserted into the document,
+ * otherwise if there are more languages remaining then these are loaded. If there are no more languages then use the
+ * default.
+ */
+ public insertLanguage() {
+ for (let i = 0; i < window.navigator.languages.length; i++) {
+ const language = window.navigator.languages[i].toLowerCase();
+ if (this.available.indexOf(language) >= 0) {
+ const languageUrl = this.getUrlForLanguage(language);
+ this.insertScript(languageUrl);
+ return;
+ }
+ }
+ this.insertScript(this.getUrlForLanguage(this.defaultLang));
+ }
+}
+
+// Find the best language and insert it after this script.
+new Loader(document.currentScript).insertLanguage();
diff --git a/paf-mvp-core-js/tests/test-cases/key-store.test.ts b/paf-mvp-core-js/tests/test-cases/key-store.test.ts
index 52a8828f..794bd2bc 100644
--- a/paf-mvp-core-js/tests/test-cases/key-store.test.ts
+++ b/paf-mvp-core-js/tests/test-cases/key-store.test.ts
@@ -58,7 +58,7 @@ h4/WfMRMVh3HIqojt3LIsvUQig1rm9ZkcNx+IHZVhDM+hso2sXlGjF9xOQ==
startTimestampInSec: currentKey.start,
endTimestampInSec: currentKey.end,
publicKey: currentKey.key,
- publicKeyObj: publicKeyFromString(currentKey.key),
+ publicKeyObj: await publicKeyFromString(currentKey.key),
};
// Two consecutive calls
@@ -94,7 +94,7 @@ h4/WfMRMVh3HIqojt3LIsvUQig1rm9ZkcNx+IHZVhDM+hso2sXlGjF9xOQ==
startTimestampInSec: currentKey.start,
// No end date
publicKey: currentKey.key,
- publicKeyObj: publicKeyFromString(currentKey.key),
+ publicKeyObj: await publicKeyFromString(currentKey.key),
};
// Two consecutive calls
@@ -125,7 +125,7 @@ h4/WfMRMVh3HIqojt3LIsvUQig1rm9ZkcNx+IHZVhDM+hso2sXlGjF9xOQ==
startTimestampInSec: oldKey.start,
endTimestampInSec: oldKey.end,
publicKey: oldKey.key,
- publicKeyObj: publicKeyFromString(oldKey.key),
+ publicKeyObj: await publicKeyFromString(oldKey.key),
};
// First call will query it
@@ -139,7 +139,7 @@ h4/WfMRMVh3HIqojt3LIsvUQig1rm9ZkcNx+IHZVhDM+hso2sXlGjF9xOQ==
startTimestampInSec: currentKey.start,
endTimestampInSec: currentKey.end,
publicKey: currentKey.key,
- publicKeyObj: publicKeyFromString(currentKey.key),
+ publicKeyObj: await publicKeyFromString(currentKey.key),
};
expect(await keyStore.getPublicKey('domain.com')).toEqual(expectedNewKey);
diff --git a/paf-mvp-core-js/tests/test-cases/signer.test.ts b/paf-mvp-core-js/tests/test-cases/signer.test.ts
index ebe6add2..1e21d66c 100644
--- a/paf-mvp-core-js/tests/test-cases/signer.test.ts
+++ b/paf-mvp-core-js/tests/test-cases/signer.test.ts
@@ -1,10 +1,10 @@
import { Signer } from '@core/crypto/signer';
-import { PrivateKey } from '@core/crypto/keys';
import { FooSigningDefinition, FooType } from '../helpers/crypto.helper';
+import { IECDSA } from 'ecdsa-secp256r1';
describe('Signer', () => {
test('should sign based on definition', () => {
- const mockPrivateKey: PrivateKey = {
+ const mockPrivateKey: IECDSA = {
sign: (data: string) => `SIGNED[${data}]`,
};
diff --git a/paf-mvp-core-js/tests/test-cases/verifier.test.ts b/paf-mvp-core-js/tests/test-cases/verifier.test.ts
index f39962c2..4b1c9d2b 100644
--- a/paf-mvp-core-js/tests/test-cases/verifier.test.ts
+++ b/paf-mvp-core-js/tests/test-cases/verifier.test.ts
@@ -1,13 +1,13 @@
-import { PublicKey } from '@core/crypto/keys';
import { FooSigningDefinition, FooType } from '../helpers/crypto.helper';
import { PublicKeyProvider, Verifier } from '@core/crypto/verifier';
+import { IECDSA } from 'ecdsa-secp256r1';
import SpyInstance = jest.SpyInstance;
describe('Verifier', () => {
- const publicKeyA: PublicKey = {
+ const publicKeyA: IECDSA = {
verify: (toVerify: string, signature: string) => signature === `SIGNED[${toVerify}]`,
};
- const publicKeyB: PublicKey = {
+ const publicKeyB: IECDSA = {
verify: (toVerify: string, signature: string) => signature === `SIGNED_B[${toVerify}]`,
};
diff --git a/paf-mvp-demo-express/public/assets/cmp/.gitignore b/paf-mvp-demo-express/public/assets/cmp/.gitignore
deleted file mode 100644
index f49098ac..00000000
--- a/paf-mvp-demo-express/public/assets/cmp/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-ok-ui*.*
\ No newline at end of file
diff --git a/paf-mvp-demo-express/src/views/advertiser/index.hbs b/paf-mvp-demo-express/src/views/advertiser/index.hbs
index a1f0d69a..c1b5246b 100644
--- a/paf-mvp-demo-express/src/views/advertiser/index.hbs
+++ b/paf-mvp-demo-express/src/views/advertiser/index.hbs
@@ -5,7 +5,7 @@
{{#if useCmpUI}}
-
+
{{else}}
@@ -30,8 +30,8 @@
-
- --}}
+
-